From mboxrd@z Thu Jan 1 00:00:00 1970 From: Andreas Gruenbacher Subject: Re: [RFC] POSIX ACL kernel infrastructure Date: Sun, 4 Aug 2002 16:14:47 +0200 Sender: linux-fsdevel-owner@vger.kernel.org Message-ID: <200208041614.47152.agruen@suse.de> References: <200208041546.35234.agruen@suse.de> <20020804150407.A27593@infradead.org> Mime-Version: 1.0 Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7BIT Return-path: Received: from Hermes.suse.de (Charybdis.suse.de [213.95.15.201]) by Cantor.suse.de (Postfix) with ESMTP id 8000014309 for ; Sun, 4 Aug 2002 16:14:47 +0200 (MEST) To: "Linux-FSDevel" In-Reply-To: <20020804150407.A27593@infradead.org> List-Id: linux-fsdevel.vger.kernel.org On Sunday 04 August 2002 16:04, you wrote: > On Sun, Aug 04, 2002 at 03:46:35PM +0200, Andreas Gruenbacher wrote: > > Hello, > > > > I want to propose adding some infrastructure to the VFS for POSIX ACL > > ssupport. There are already implementations for XFS and Ext2/Ext3; > > ReiserFS is to follow soon. > > Could you please resend without mime and gzip? Well, sorry the patch is rather large uncompressed. Cheers, Andreas. diff -Nur --exclude-from=linux.excl linux-2.5.30/fs/Config.help linux-2.5.30.patch/fs/Config.help --- linux-2.5.30/fs/Config.help Thu Aug 1 23:16:33 2002 +++ linux-2.5.30.patch/fs/Config.help Sun Aug 4 13:38:57 2002 @@ -1,3 +1,20 @@ +CONFIG_FS_POSIX_ACL + POSIX Access Control Lists (ACLs) support permissions for users and + groups beyond the owner/group/world scheme. + + To learn more about Access Control Lists, visit + . + + You need an additional filesystem patch to get POSIX ACL support + on one of the built-in filesystems; such patches are available at + http://acl.bestbits.at/ for ext2 and ext3, and at + http://oss.sgi.com/projects/xfs/ for XFS. + + The getfacl and setfacl utilities for manipulating POSIX ACLs are also + available at these web sites. + + If you don't know what Access Control Lists are, say N. + CONFIG_QUOTA If you say Y here, you will be able to set per user limits for disk usage (also called disk quotas). Currently, it works for the diff -Nur --exclude-from=linux.excl linux-2.5.30/fs/Config.in linux-2.5.30.patch/fs/Config.in --- linux-2.5.30/fs/Config.in Thu Aug 1 23:16:29 2002 +++ linux-2.5.30.patch/fs/Config.in Sun Aug 4 13:24:12 2002 @@ -4,6 +4,8 @@ mainmenu_option next_comment comment 'File systems' +bool 'POSIX Access Control Lists' CONFIG_FS_POSIX_ACL + bool 'Quota support' CONFIG_QUOTA dep_tristate ' Old quota format support' CONFIG_QFMT_V1 $CONFIG_QUOTA dep_tristate ' Quota format v2 support' CONFIG_QFMT_V2 $CONFIG_QUOTA diff -Nur --exclude-from=linux.excl linux-2.5.30/fs/Makefile linux-2.5.30.patch/fs/Makefile --- linux-2.5.30/fs/Makefile Thu Aug 1 23:16:03 2002 +++ linux-2.5.30.patch/fs/Makefile Sun Aug 4 13:28:38 2002 @@ -7,7 +7,8 @@ O_TARGET := fs.o -export-objs := open.o dcache.o buffer.o bio.o inode.o dquot.o mpage.o +export-objs := open.o dcache.o buffer.o bio.o inode.o dquot.o mpage.o \ + posix_acl.o obj-y := open.o read_write.o devices.o file_table.o buffer.o \ bio.o super.o block_dev.o char_dev.o stat.o exec.o pipe.o \ @@ -36,6 +37,8 @@ obj-$(CONFIG_QFMT_V2) += quota_v2.o obj-$(CONFIG_QUOTACTL) += quota.o +obj-$(CONFIG_FS_POSIX_ACL) += posix_acl.o + obj-$(CONFIG_PROC_FS) += proc/ obj-y += partitions/ obj-y += driverfs/ diff -Nur --exclude-from=linux.excl linux-2.5.30/fs/posix_acl.c linux-2.5.30.patch/fs/posix_acl.c --- linux-2.5.30/fs/posix_acl.c Thu Jan 1 01:00:00 1970 +++ linux-2.5.30.patch/fs/posix_acl.c Sun Aug 4 14:52:42 2002 @@ -0,0 +1,496 @@ +/* + * linux/fs/posix_acl.c + * + * Copyright (C) 2001 by Andreas Gruenbacher + * + * Fixes from William Schumacher incorporated on 15 March 2001. + * (Reported by Charles Bertsch, ). + */ + +/* + * This file contains generic functions for manipulating + * POSIX 1003.1e draft standard 17 ACLs. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +MODULE_AUTHOR("Andreas Gruenbacher "); +MODULE_DESCRIPTION("Generic Posix Access Control List (ACL) Manipulation"); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) +MODULE_LICENSE("GPL"); +#endif + +EXPORT_SYMBOL(posix_acl_alloc); +EXPORT_SYMBOL(posix_acl_dup); +EXPORT_SYMBOL(posix_acl_clone); +EXPORT_SYMBOL(posix_acl_release); +EXPORT_SYMBOL(posix_acl_valid); +EXPORT_SYMBOL(posix_acl_equiv_mode); +EXPORT_SYMBOL(posix_acl_from_mode); +EXPORT_SYMBOL(posix_acl_create_masq); +EXPORT_SYMBOL(posix_acl_chmod_masq); +EXPORT_SYMBOL(posix_acl_masq_nfs_mode); +EXPORT_SYMBOL(posix_acl_permission); + +EXPORT_SYMBOL(get_posix_acl); +EXPORT_SYMBOL(set_posix_acl); + +/* + * Allocate a new ACL with the specified number of entries. + */ +posix_acl_t * +posix_acl_alloc(int count) +{ + const size_t size = sizeof(posix_acl_t) + + count * sizeof(posix_acl_entry_t); + posix_acl_t *acl = kmalloc(size, GFP_KERNEL); + if (acl) { + atomic_set(&acl->a_refcount, 1); + acl->a_count = count; + } + return acl; +} + +/* + * Duplicate an ACL handle. + */ +posix_acl_t * +posix_acl_dup(posix_acl_t *acl) +{ + if (acl) + atomic_inc(&acl->a_refcount); + return acl; +} + +/* + * Clone an ACL. + */ +posix_acl_t * +posix_acl_clone(const posix_acl_t *acl) +{ + posix_acl_t *clone = NULL; + + if (acl) { + int size = sizeof(posix_acl_t) + acl->a_count * + sizeof(posix_acl_entry_t); + clone = kmalloc(size, GFP_KERNEL); + if (clone) { + memcpy(clone, acl, size); + atomic_set(&clone->a_refcount, 1); + } + } + return clone; +} + +/* + * Free an ACL handle. + */ +void +posix_acl_release(posix_acl_t *acl) +{ + if (acl && atomic_dec_and_test(&acl->a_refcount)) + kfree(acl); +} + +/* + * Check if an acl is valid. Returns 0 if it is, or -E... otherwise. + */ +int +posix_acl_valid(const posix_acl_t *acl) +{ + const posix_acl_entry_t *pa, *pe; + int state = ACL_USER_OBJ; + unsigned int id = 0; /* keep gcc happy */ + int needs_mask = 0; + + FOREACH_ACL_ENTRY(pa, acl, pe) { + if (pa->e_perm & ~(ACL_READ|ACL_WRITE|ACL_EXECUTE)) + return -EINVAL; + switch (pa->e_tag) { + case ACL_USER_OBJ: + if (state == ACL_USER_OBJ) { + id = 0; + state = ACL_USER; + break; + } + return -EINVAL; + + case ACL_USER: + if (state != ACL_USER) + return -EINVAL; + if (pa->e_id == ACL_UNDEFINED_ID || + pa->e_id < id) + return -EINVAL; + id = pa->e_id + 1; + needs_mask = 1; + break; + + case ACL_GROUP_OBJ: + if (state == ACL_USER) { + id = 0; + state = ACL_GROUP; + break; + } + return -EINVAL; + + case ACL_GROUP: + if (state != ACL_GROUP) + return -EINVAL; + if (pa->e_id == ACL_UNDEFINED_ID || + pa->e_id < id) + return -EINVAL; + id = pa->e_id + 1; + needs_mask = 1; + break; + + case ACL_MASK: + if (state != ACL_GROUP) + return -EINVAL; + state = ACL_OTHER; + break; + + case ACL_OTHER: + if (state == ACL_OTHER || + (state == ACL_GROUP && !needs_mask)) { + state = 0; + break; + } + return -EINVAL; + + default: + return -EINVAL; + } + } + if (state == 0) + return 0; + return -EINVAL; +} + +/* + * Returns 0 if the acl can be exactly represented in the traditional + * file mode permission bits, or else 1. Returns -E... on error. + */ +int +posix_acl_equiv_mode(const posix_acl_t *acl, mode_t *mode_p) +{ + const posix_acl_entry_t *pa, *pe; + mode_t mode = 0; + int not_equiv = 0; + + FOREACH_ACL_ENTRY(pa, acl, pe) { + switch (pa->e_tag) { + case ACL_USER_OBJ: + mode |= (pa->e_perm & S_IRWXO) << 6; + break; + case ACL_GROUP_OBJ: + mode |= (pa->e_perm & S_IRWXO) << 3; + break; + case ACL_OTHER: + mode |= pa->e_perm & S_IRWXO; + break; + case ACL_MASK: + mode = (mode & ~S_IRWXG) | + ((pa->e_perm & S_IRWXO) << 3); + not_equiv = 1; + break; + case ACL_USER: + case ACL_GROUP: + not_equiv = 1; + break; + default: + return -EINVAL; + } + } + if (mode_p) + *mode_p = (*mode_p & ~S_IRWXUGO) | mode; + return not_equiv; +} + +/* + * Create an ACL representing the file mode permission bits of an inode. + */ +posix_acl_t * +posix_acl_from_mode(mode_t mode) +{ + posix_acl_t *acl = posix_acl_alloc(3); + if (!acl) + return ERR_PTR(-ENOMEM); + + acl->a_entries[0].e_tag = ACL_USER_OBJ; + acl->a_entries[0].e_id = ACL_UNDEFINED_ID; + acl->a_entries[0].e_perm = (mode & S_IRWXU) >> 6; + + acl->a_entries[1].e_tag = ACL_GROUP_OBJ; + acl->a_entries[1].e_id = ACL_UNDEFINED_ID; + acl->a_entries[1].e_perm = (mode & S_IRWXG) >> 3; + + acl->a_entries[2].e_tag = ACL_OTHER; + acl->a_entries[2].e_id = ACL_UNDEFINED_ID; + acl->a_entries[2].e_perm = (mode & S_IRWXO); + return acl; +} + +/* + * Return 0 if current is granted want access to the inode + * by the acl. Returns -E... otherwise. + */ +int +posix_acl_permission(struct inode *inode, const posix_acl_t *acl, int want) +{ + const posix_acl_entry_t *pa, *pe, *mask_obj; + int found = 0; + + FOREACH_ACL_ENTRY(pa, acl, pe) { + switch(pa->e_tag) { + case ACL_USER_OBJ: + /* (May have been checked already) */ + if (inode->i_uid == current->fsuid) + goto check_perm; + break; + case ACL_USER: + if (pa->e_id == current->fsuid) + goto mask; + break; + case ACL_GROUP_OBJ: + if (in_group_p(inode->i_gid)) { + found = 1; + if ((pa->e_perm & want) == want) + goto mask; + } + break; + case ACL_GROUP: + if (in_group_p(pa->e_id)) { + found = 1; + if ((pa->e_perm & want) == want) + goto mask; + } + break; + case ACL_MASK: + break; + case ACL_OTHER: + if (found) + return -EACCES; + else + goto check_perm; + default: + return -EIO; + } + } + return -EIO; + +mask: + for (mask_obj = pa+1; mask_obj != pe; mask_obj++) { + if (mask_obj->e_tag == ACL_MASK) { + if ((pa->e_perm & mask_obj->e_perm & want) == want) + return 0; + return -EACCES; + } + } + +check_perm: + if ((pa->e_perm & want) == want) + return 0; + return -EACCES; +} + +/* + * Modify acl when creating a new inode. The caller must ensure the acl is + * only referenced once. + * + * mode_p initially must contain the mode parameter to the open() / creat() + * system calls. All permissions that are not granted by the acl are removed. + * The permissions in the acl are changed to reflect the mode_p parameter. + */ +int +posix_acl_create_masq(posix_acl_t *acl, mode_t *mode_p) +{ + posix_acl_entry_t *pa, *pe; + posix_acl_entry_t *group_obj = NULL, *mask_obj = NULL; + mode_t mode = *mode_p; + int not_equiv = 0; + + /* assert(atomic_read(acl->a_refcount) == 1); */ + + FOREACH_ACL_ENTRY(pa, acl, pe) { + switch(pa->e_tag) { + case ACL_USER_OBJ: + pa->e_perm &= (mode >> 6) | ~S_IRWXO; + mode &= (pa->e_perm << 6) | ~S_IRWXU; + break; + + case ACL_USER: + case ACL_GROUP: + not_equiv = 1; + break; + + case ACL_GROUP_OBJ: + group_obj = pa; + break; + + case ACL_OTHER: + pa->e_perm &= mode | ~S_IRWXO; + mode &= pa->e_perm | ~S_IRWXO; + break; + + case ACL_MASK: + mask_obj = pa; + not_equiv = 1; + break; + + default: + return -EIO; + } + } + + if (mask_obj) { + mask_obj->e_perm &= (mode >> 3) | ~S_IRWXO; + mode &= (mask_obj->e_perm << 3) | ~S_IRWXG; + } else { + if (!group_obj) + return -EIO; + group_obj->e_perm &= (mode >> 3) | ~S_IRWXO; + mode &= (group_obj->e_perm << 3) | ~S_IRWXG; + } + + *mode_p = (*mode_p & ~S_IRWXUGO) | mode; + return not_equiv; +} + +/* + * Modify the ACL for the chmod syscall. + */ +int +posix_acl_chmod_masq(posix_acl_t *acl, mode_t mode) +{ + posix_acl_entry_t *group_obj = NULL, *mask_obj = NULL; + posix_acl_entry_t *pa, *pe; + + /* assert(atomic_read(acl->a_refcount) == 1); */ + + FOREACH_ACL_ENTRY(pa, acl, pe) { + switch(pa->e_tag) { + case ACL_USER_OBJ: + pa->e_perm = (mode & S_IRWXU) >> 6; + break; + + case ACL_USER: + case ACL_GROUP: + break; + + case ACL_GROUP_OBJ: + group_obj = pa; + break; + + case ACL_MASK: + mask_obj = pa; + break; + + case ACL_OTHER: + pa->e_perm = (mode & S_IRWXO); + break; + + default: + return -EIO; + } + } + + if (mask_obj) { + mask_obj->e_perm = (mode & S_IRWXG) >> 3; + } else { + if (!group_obj) + return -EIO; + group_obj->e_perm = (mode & S_IRWXG) >> 3; + } + + return 0; +} + +/* + * Adjust the mode parameter so that NFSv2 grants nobody permissions + * that may not be granted by the ACL. This is necessary because NFSv2 + * may compute access permissions on the client side, and may serve cached + * data whenever it assumes access would be granted. Since ACLs may also + * be used to deny access to specific users, the minimal permissions + * for secure operation over NFSv2 are very restrictive. Permissions + * granted to users via Access Control Lists will not be effective over + * NFSv2. + * + * Privilege escalation can only happen for read operations, as writes are + * always carried out on the NFS server, where the proper access checks are + * implemented. + */ +int +posix_acl_masq_nfs_mode(posix_acl_t *acl, mode_t *mode_p) +{ + posix_acl_entry_t *pa, *pe; int min_perm = S_IRWXO; + + FOREACH_ACL_ENTRY(pa, acl, pe) { + switch(pa->e_tag) { + case ACL_USER_OBJ: + break; + + case ACL_USER: + case ACL_GROUP_OBJ: + case ACL_GROUP: + case ACL_MASK: + case ACL_OTHER: + min_perm &= pa->e_perm; + break; + + default: + return -EIO; + } + } + *mode_p = (*mode_p & ~(S_IRWXG|S_IRWXO)) | (min_perm << 3) | min_perm; + + return 0; +} + +/* + * Get the POSIX ACL of an inode. + */ +posix_acl_t * +get_posix_acl(struct inode *inode, int type) +{ + posix_acl_t *acl; + + if (!inode->i_op || !inode->i_op->get_posix_acl) + return ERR_PTR(-ENOTSUP); + + down(&inode->i_sem); + lock_kernel(); /* goes away in 2.5.x */ + acl = inode->i_op->get_posix_acl(inode, type); + unlock_kernel(); /* goes away in 2.5.x */ + up(&inode->i_sem); + + return acl; +} + +/* + * Set the POSIX ACL of an inode. + */ +int +set_posix_acl(struct inode *inode, int type, posix_acl_t *acl) +{ + int error; + + if (!inode->i_op || !inode->i_op->set_posix_acl) + return -ENOTSUP; + + down(&inode->i_sem); + lock_kernel(); /* goes away in 2.5.x */ + error = inode->i_op->set_posix_acl(inode, type, acl); + unlock_kernel(); /* goes away in 2.5.x */ + up(&inode->i_sem); + + return error; +} diff -Nur --exclude-from=linux.excl linux-2.5.30/include/linux/errno.h linux-2.5.30.patch/include/linux/errno.h --- linux-2.5.30/include/linux/errno.h Sun Aug 4 14:59:40 2002 +++ linux-2.5.30.patch/include/linux/errno.h Sun Aug 4 14:59:26 2002 @@ -11,6 +11,8 @@ #define ERESTARTNOHAND 514 /* restart if no handler.. */ #define ENOIOCTLCMD 515 /* No ioctl command */ +#define ENOTSUP -1 /* UNRESOLVED ISSUE -- see other proposal */ + /* Defined for the NFSv3 protocol */ #define EBADHANDLE 521 /* Illegal NFS file handle */ #define ENOTSYNC 522 /* Update synchronization mismatch */ diff -Nur --exclude-from=linux.excl linux-2.5.30/include/linux/fs.h linux-2.5.30.patch/include/linux/fs.h --- linux-2.5.30/include/linux/fs.h Thu Aug 1 23:16:15 2002 +++ linux-2.5.30.patch/include/linux/fs.h Sun Aug 4 13:29:31 2002 @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -787,6 +788,8 @@ ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t); ssize_t (*listxattr) (struct dentry *, char *, size_t); int (*removexattr) (struct dentry *, const char *); + posix_acl_t *(*get_posix_acl) (struct inode *, int); + int (*set_posix_acl) (struct inode *, int, posix_acl_t *); }; struct seq_file; diff -Nur --exclude-from=linux.excl linux-2.5.30/include/linux/posix_acl.h linux-2.5.30.patch/include/linux/posix_acl.h --- linux-2.5.30/include/linux/posix_acl.h Thu Jan 1 01:00:00 1970 +++ linux-2.5.30.patch/include/linux/posix_acl.h Sun Aug 4 13:24:12 2002 @@ -0,0 +1,69 @@ +/* + File: linux/posix_acl.h + + (C) 2000 Andreas Gruenbacher, +*/ + + +#ifndef __LINUX_POSIX_ACL_H +#define __LINUX_POSIX_ACL_H + + +#define ACL_UNDEFINED_ID (-1) + +/* a_type field in acl_user_posix_entry_t */ +#define ACL_TYPE_ACCESS (0x8000) +#define ACL_TYPE_DEFAULT (0x4000) + +/* e_tag entry in posix_acl_entry_t */ +#define ACL_USER_OBJ (0x01) +#define ACL_USER (0x02) +#define ACL_GROUP_OBJ (0x04) +#define ACL_GROUP (0x08) +#define ACL_MASK (0x10) +#define ACL_OTHER (0x20) + +/* permissions in the e_perm field */ +#define ACL_READ (0x04) +#define ACL_WRITE (0x02) +#define ACL_EXECUTE (0x01) +//#define ACL_ADD (0x08) +//#define ACL_DELETE (0x10) + +#ifdef __KERNEL__ + +typedef struct { + short e_tag; + unsigned short e_perm; + unsigned int e_id; +} posix_acl_entry_t; + +typedef struct { + atomic_t a_refcount; + unsigned int a_count; + posix_acl_entry_t a_entries[0]; +} posix_acl_t; + +#define FOREACH_ACL_ENTRY(pa, acl, pe) \ + for(pa=(acl)->a_entries, pe=pa+(acl)->a_count; pa