userns: Convert vfs posix_acl support to use kuids and kgids
authorEric W. Biederman <ebiederm@xmission.com>
Wed, 8 Feb 2012 02:52:57 +0000 (18:52 -0800)
committerEric W. Biederman <ebiederm@xmission.com>
Tue, 18 Sep 2012 08:01:35 +0000 (01:01 -0700)
- In setxattr if we are setting a posix acl convert uids and gids from
  the current user namespace into the initial user namespace, before
  the xattrs are passed to the underlying filesystem.

  Untranslatable uids and gids are represented as -1 which
  posix_acl_from_xattr will represent as INVALID_UID or INVALID_GID.
  posix_acl_valid will fail if an acl from userspace has any
  INVALID_UID or INVALID_GID values.  In net this guarantees that
  untranslatable posix acls will not be stored by filesystems.

- In getxattr if we are reading a posix acl convert uids and gids from
  the initial user namespace into the current user namespace.

  Uids and gids that can not be tranlsated into the current user namespace
  will be represented as -1.

- Replace e_id in struct posix_acl_entry with an anymouns union of
  e_uid and e_gid.  For the short term retain the e_id field
  until all of the users are converted.

- Don't set struct posix_acl.e_id in the cases where the acl type
  does not use e_id.  Greatly reducing the use of ACL_UNDEFINED_ID.

- Rework the ordering checks in posix_acl_valid so that I use kuid_t
  and kgid_t types throughout the code, and so that I don't need
  arithmetic on uid and gid types.

Cc: Theodore Tso <tytso@mit.edu>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Andreas Dilger <adilger.kernel@dilger.ca>
Cc: Jan Kara <jack@suse.cz>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
fs/posix_acl.c
fs/xattr.c
fs/xattr_acl.c
include/linux/posix_acl.h
include/linux/posix_acl_xattr.h

index 5e325a42e33d03bb91d32edeb212ac28fb979ec2..8bd2135b7f82ce181445f8e4804d4b226dca4891 100644 (file)
@@ -78,7 +78,8 @@ posix_acl_valid(const struct posix_acl *acl)
 {
        const struct posix_acl_entry *pa, *pe;
        int state = ACL_USER_OBJ;
-       unsigned int id = 0;  /* keep gcc happy */
+       kuid_t prev_uid = INVALID_UID;
+       kgid_t prev_gid = INVALID_GID;
        int needs_mask = 0;
 
        FOREACH_ACL_ENTRY(pa, acl, pe) {
@@ -87,7 +88,6 @@ posix_acl_valid(const struct posix_acl *acl)
                switch (pa->e_tag) {
                        case ACL_USER_OBJ:
                                if (state == ACL_USER_OBJ) {
-                                       id = 0;
                                        state = ACL_USER;
                                        break;
                                }
@@ -96,16 +96,17 @@ posix_acl_valid(const struct posix_acl *acl)
                        case ACL_USER:
                                if (state != ACL_USER)
                                        return -EINVAL;
-                               if (pa->e_id == ACL_UNDEFINED_ID ||
-                                   pa->e_id < id)
+                               if (!uid_valid(pa->e_uid))
                                        return -EINVAL;
-                               id = pa->e_id + 1;
+                               if (uid_valid(prev_uid) &&
+                                   uid_lte(pa->e_uid, prev_uid))
+                                       return -EINVAL;
+                               prev_uid = pa->e_uid;
                                needs_mask = 1;
                                break;
 
                        case ACL_GROUP_OBJ:
                                if (state == ACL_USER) {
-                                       id = 0;
                                        state = ACL_GROUP;
                                        break;
                                }
@@ -114,10 +115,12 @@ posix_acl_valid(const struct posix_acl *acl)
                        case ACL_GROUP:
                                if (state != ACL_GROUP)
                                        return -EINVAL;
-                               if (pa->e_id == ACL_UNDEFINED_ID ||
-                                   pa->e_id < id)
+                               if (!gid_valid(pa->e_gid))
+                                       return -EINVAL;
+                               if (gid_valid(prev_gid) &&
+                                   gid_lte(pa->e_gid, prev_gid))
                                        return -EINVAL;
-                               id = pa->e_id + 1;
+                               prev_gid = pa->e_gid;
                                needs_mask = 1;
                                break;
 
@@ -195,15 +198,12 @@ posix_acl_from_mode(umode_t mode, gfp_t flags)
                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;
 }
@@ -224,11 +224,11 @@ posix_acl_permission(struct inode *inode, const struct posix_acl *acl, int want)
                 switch(pa->e_tag) {
                         case ACL_USER_OBJ:
                                /* (May have been checked already) */
-                               if (inode->i_uid == current_fsuid())
+                               if (uid_eq(inode->i_uid, current_fsuid()))
                                         goto check_perm;
                                 break;
                         case ACL_USER:
-                               if (pa->e_id == current_fsuid())
+                               if (uid_eq(pa->e_uid, current_fsuid()))
                                         goto mask;
                                break;
                         case ACL_GROUP_OBJ:
@@ -239,7 +239,7 @@ posix_acl_permission(struct inode *inode, const struct posix_acl *acl, int want)
                                 }
                                break;
                         case ACL_GROUP:
-                                if (in_group_p(pa->e_id)) {
+                               if (in_group_p(pa->e_gid)) {
                                        found = 1;
                                        if ((pa->e_perm & want) == want)
                                                goto mask;
index 4d45b7189e7eaa352da0567b8fe4a2d1b738c999..c111745c2da9ed449a4bb16be2a4293a646aaeb9 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/fsnotify.h>
 #include <linux/audit.h>
 #include <linux/vmalloc.h>
+#include <linux/posix_acl_xattr.h>
 
 #include <asm/uaccess.h>
 
@@ -347,6 +348,9 @@ setxattr(struct dentry *d, const char __user *name, const void __user *value,
                        error = -EFAULT;
                        goto out;
                }
+               if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
+                   (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
+                       posix_acl_fix_xattr_from_user(kvalue, size);
        }
 
        error = vfs_setxattr(d, kname, kvalue, size, flags);
@@ -450,6 +454,9 @@ getxattr(struct dentry *d, const char __user *name, void __user *value,
 
        error = vfs_getxattr(d, kname, kvalue, size);
        if (error > 0) {
+               if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
+                   (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
+                       posix_acl_fix_xattr_to_user(kvalue, size);
                if (size && copy_to_user(value, kvalue, error))
                        error = -EFAULT;
        } else if (error == -ERANGE && size >= XATTR_SIZE_MAX) {
index 69d06b07b169d183f126497770743cb6dedc8256..bf472ca1b3485ed661d0fdd567d4f7710ecfb901 100644 (file)
@@ -9,7 +9,65 @@
 #include <linux/fs.h>
 #include <linux/posix_acl_xattr.h>
 #include <linux/gfp.h>
+#include <linux/user_namespace.h>
 
+/*
+ * Fix up the uids and gids in posix acl extended attributes in place.
+ */
+static void posix_acl_fix_xattr_userns(
+       struct user_namespace *to, struct user_namespace *from,
+       void *value, size_t size)
+{
+       posix_acl_xattr_header *header = (posix_acl_xattr_header *)value;
+       posix_acl_xattr_entry *entry = (posix_acl_xattr_entry *)(header+1), *end;
+       int count;
+       kuid_t uid;
+       kgid_t gid;
+
+       if (!value)
+               return;
+       if (size < sizeof(posix_acl_xattr_header))
+               return;
+       if (header->a_version != cpu_to_le32(POSIX_ACL_XATTR_VERSION))
+               return;
+
+       count = posix_acl_xattr_count(size);
+       if (count < 0)
+               return;
+       if (count == 0)
+               return;
+
+       for (end = entry + count; entry != end; entry++) {
+               switch(le16_to_cpu(entry->e_tag)) {
+               case ACL_USER:
+                       uid = make_kuid(from, le32_to_cpu(entry->e_id));
+                       entry->e_id = cpu_to_le32(from_kuid(to, uid));
+                       break;
+               case ACL_GROUP:
+                       gid = make_kgid(from, le32_to_cpu(entry->e_id));
+                       entry->e_id = cpu_to_le32(from_kuid(to, uid));
+                       break;
+               default:
+                       break;
+               }
+       }
+}
+
+void posix_acl_fix_xattr_from_user(void *value, size_t size)
+{
+       struct user_namespace *user_ns = current_user_ns();
+       if (user_ns == &init_user_ns)
+               return;
+       posix_acl_fix_xattr_userns(&init_user_ns, user_ns, value, size);
+}
+
+void posix_acl_fix_xattr_to_user(void *value, size_t size)
+{
+       struct user_namespace *user_ns = current_user_ns();
+       if (user_ns == &init_user_ns)
+               return;
+       posix_acl_fix_xattr_userns(user_ns, &init_user_ns, value, size);
+}
 
 /*
  * Convert from extended attribute to in-memory representation.
@@ -50,12 +108,21 @@ posix_acl_from_xattr(const void *value, size_t size)
                        case ACL_GROUP_OBJ:
                        case ACL_MASK:
                        case ACL_OTHER:
-                               acl_e->e_id = ACL_UNDEFINED_ID;
                                break;
 
                        case ACL_USER:
+                               acl_e->e_uid =
+                                       make_kuid(&init_user_ns,
+                                                 le32_to_cpu(entry->e_id));
+                               if (!uid_valid(acl_e->e_uid))
+                                       goto fail;
+                               break;
                        case ACL_GROUP:
-                               acl_e->e_id = le32_to_cpu(entry->e_id);
+                               acl_e->e_gid =
+                                       make_kgid(&init_user_ns,
+                                                 le32_to_cpu(entry->e_id));
+                               if (!gid_valid(acl_e->e_gid))
+                                       goto fail;
                                break;
 
                        default:
@@ -89,9 +156,22 @@ posix_acl_to_xattr(const struct posix_acl *acl, void *buffer, size_t size)
        ext_acl->a_version = cpu_to_le32(POSIX_ACL_XATTR_VERSION);
 
        for (n=0; n < acl->a_count; n++, ext_entry++) {
-               ext_entry->e_tag  = cpu_to_le16(acl->a_entries[n].e_tag);
-               ext_entry->e_perm = cpu_to_le16(acl->a_entries[n].e_perm);
-               ext_entry->e_id   = cpu_to_le32(acl->a_entries[n].e_id);
+               const struct posix_acl_entry *acl_e = &acl->a_entries[n];
+               ext_entry->e_tag  = cpu_to_le16(acl_e->e_tag);
+               ext_entry->e_perm = cpu_to_le16(acl_e->e_perm);
+               switch(acl_e->e_tag) {
+               case ACL_USER:
+                       ext_entry->e_id =
+                               cpu_to_le32(from_kuid(&init_user_ns, acl_e->e_uid));
+                       break;
+               case ACL_GROUP:
+                       ext_entry->e_id =
+                               cpu_to_le32(from_kgid(&init_user_ns, acl_e->e_gid));
+                       break;
+               default:
+                       ext_entry->e_id = cpu_to_le32(ACL_UNDEFINED_ID);
+                       break;
+               }
        }
        return real_size;
 }
index 11bad91c443391d82521cfa1a48153d81e0a7034..7931efe7117553d00a920cfd4aef757f72578658 100644 (file)
 struct posix_acl_entry {
        short                   e_tag;
        unsigned short          e_perm;
-       unsigned int            e_id;
+       union {
+               kuid_t          e_uid;
+               kgid_t          e_gid;
+#ifndef CONFIG_UIDGID_STRICT_TYPE_CHECKS
+               unsigned int    e_id;
+#endif
+       };
 };
 
 struct posix_acl {
index 6e53c34035cd5b77c7c3e86878a00a3b1861070e..8bd5fcf0691630936193549898171022db0dcd23 100644 (file)
@@ -52,6 +52,18 @@ posix_acl_xattr_count(size_t size)
        return size / sizeof(posix_acl_xattr_entry);
 }
 
+#ifdef CONFIG_FS_POSIX_ACL
+void posix_acl_fix_xattr_from_user(void *value, size_t size);
+void posix_acl_fix_xattr_to_user(void *value, size_t size);
+#else
+static inline void posix_acl_fix_xattr_from_user(void *value, size_t size)
+{
+}
+static inline void posix_acl_fix_xattr_to_user(void *value, size_t size)
+{
+}
+#endif
+
 struct posix_acl *posix_acl_from_xattr(const void *value, size_t size);
 int posix_acl_to_xattr(const struct posix_acl *acl, void *buffer, size_t size);