Some remote file systems like nfs may return user or group identifiers
that cannot be mapped to local uids / gids.  Allow to represent such
unmapped identifiers in richacls.  (We still cannot represent unmapped
owners and owning groups, however.)

In the in-memory representation, the richacl is followed by a list of
NUL-terminated strings, with no padding.  Entries with an unmapped
identifier have the RICHACE_UNMAPPED_WHO flag set, and ace->e_id.offs
specifies the offset into this list.  Multiple entries can refer to the
same offset.

The xattr representation is similar, but ace->e_id is ignored, and the
list of unmapped identifier strings contains a string for each acl entry
whose RICHACE_UNMAPPED_WHO flag is set.

Signed-off-by: Andreas Gruenbacher <agrue...@redhat.com>
---
 fs/richacl_base.c       | 139 ++++++++++++++++++++++++++++++++++++++++++++----
 fs/richacl_compat.c     |  18 +++----
 fs/richacl_inode.c      |   4 +-
 fs/richacl_xattr.c      |  69 ++++++++++++++++++++----
 include/linux/richacl.h |  32 +++++++++--
 5 files changed, 226 insertions(+), 36 deletions(-)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 3a97a82..f88d19b 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -23,22 +23,25 @@
 MODULE_LICENSE("GPL");
 
 /**
- * richacl_alloc  -  allocate a richacl
+ * __richacl_alloc  -  allocate a richacl
  * @count:     number of entries
+ * @unmapped_size:     size to reserve for unmapped identifiers
  */
 struct richacl *
-richacl_alloc(int count, gfp_t gfp)
+__richacl_alloc(unsigned int count, size_t unmapped_size, gfp_t gfp)
 {
-       size_t size = sizeof(struct richacl) + count * sizeof(struct richace);
+       size_t size = sizeof(struct richacl) + count * sizeof(struct richace) +
+                     unmapped_size;
        struct richacl *acl = kzalloc(size, gfp);
 
        if (acl) {
                atomic_set(&acl->a_base.ba_refcount, 1);
                acl->a_count = count;
+               acl->a_unmapped_size = unmapped_size;
        }
        return acl;
 }
-EXPORT_SYMBOL_GPL(richacl_alloc);
+EXPORT_SYMBOL_GPL(__richacl_alloc);
 
 /**
  * richacl_clone  -  create a copy of a richacl
@@ -47,7 +50,8 @@ struct richacl *
 richacl_clone(const struct richacl *acl, gfp_t gfp)
 {
        int count = acl->a_count;
-       size_t size = sizeof(struct richacl) + count * sizeof(struct richace);
+       size_t size = sizeof(struct richacl) + count * sizeof(struct richace) +
+                     acl->a_unmapped_size;
        struct richacl *dup = kmalloc(size, gfp);
 
        if (dup) {
@@ -59,6 +63,9 @@ richacl_clone(const struct richacl *acl, gfp_t gfp)
 
 /**
  * richace_copy  -  copy an acl entry
+ *
+ * If @from has an unmapped who value (from->e_flags & RICHACE_UNMAPPED_WHO),
+ * it can only be copied within the same acl!
  */
 void
 richace_copy(struct richace *to, const struct richace *from)
@@ -66,6 +73,82 @@ richace_copy(struct richace *to, const struct richace *from)
        memcpy(to, from, sizeof(struct richace));
 }
 
+/**
+ * richacl_add_unmapped_identifier
+ * @pacl:      Pointer to an acl
+ * @pace:      acl entry within @acl
+ * @who:       unmapped identifier
+ * @len:       length of @who
+ * @gfp:       memory allocation flags
+ *
+ * Add an unmapped identifier to an acl, possibly reallocating the acl.
+ */
+int richacl_add_unmapped_identifier(struct richacl **pacl,
+                                   struct richace **pace,
+                                   const char *who,
+                                   unsigned int len, gfp_t gfp)
+{
+       struct richacl *acl = *pacl;
+       size_t size = sizeof(struct richacl) +
+                     acl->a_count * sizeof(struct richace) +
+                     acl->a_unmapped_size + len + 1;
+       unsigned int index = *pace - acl->a_entries;
+
+       acl = krealloc(*pacl, size, gfp);
+       if (acl) {
+               char *unmapped = (char *)(acl->a_entries + acl->a_count);
+               struct richace *ace = acl->a_entries + index;
+
+               ace->e_flags |= RICHACE_UNMAPPED_WHO;
+               ace->e_flags &= ~RICHACE_SPECIAL_WHO;
+               ace->e_id.offs = acl->a_unmapped_size;
+               memcpy(unmapped + ace->e_id.offs, who, len);
+               unmapped[ace->e_id.offs + len] = 0;
+               acl->a_unmapped_size += len + 1;
+               *pace = ace;
+               *pacl = acl;
+               return 0;
+       }
+       return -1;
+}
+EXPORT_SYMBOL_GPL(richacl_add_unmapped_identifier);
+
+/**
+ * richace_unmapped_identifier  -  get unmapped identifier
+ * @acl:       acl containing @ace
+ * @ace:       acl entry
+ *
+ * Get the unmapped identifier of @ace as a NUL-terminated string, or NULL if
+ * @ace doesn't have an unmapped identifier.
+ */
+const char *richace_unmapped_identifier(const struct richace *ace,
+                                       const struct richacl *acl)
+{
+       const char *unmapped = (char *)(acl->a_entries + acl->a_count);
+
+       if (!(ace->e_flags & RICHACE_UNMAPPED_WHO))
+               return NULL;
+       return unmapped + ace->e_id.offs;
+}
+EXPORT_SYMBOL(richace_unmapped_identifier);
+
+/**
+ * richacl_has_unmapped_identifiers
+ *
+ * Check if an acl has unmapped identifiers.
+ */
+bool richacl_has_unmapped_identifiers(struct richacl *acl)
+{
+       struct richace *ace;
+
+       richacl_for_each_entry(ace, acl) {
+               if (ace->e_flags & RICHACE_UNMAPPED_WHO)
+                       return true;
+       }
+       return false;
+}
+EXPORT_SYMBOL_GPL(richacl_has_unmapped_identifiers);
+
 /*
  * richacl_mask_to_mode  -  compute the file permission bits from mask
  * @mask:      %RICHACE_* permission mask
@@ -214,7 +297,7 @@ static unsigned int richacl_allowed_to_who(struct richacl 
*acl,
        richacl_for_each_entry_reverse(ace, acl) {
                if (richace_is_inherit_only(ace))
                        continue;
-               if (richace_is_same_identifier(ace, who) ||
+               if (richace_is_same_identifier(acl, ace, who) ||
                    richace_is_everyone(ace)) {
                        if (richace_is_allow(ace))
                                allowed |= ace->e_mask;
@@ -505,45 +588,72 @@ richacl_inherit(const struct richacl *dir_acl, int isdir)
        const struct richace *dir_ace;
        struct richacl *acl = NULL;
        struct richace *ace;
-       int count = 0;
+       unsigned int count = 0, unmapped_size = 0, offset = 0;
+       const char *dir_unmapped;
+       char *unmapped;
 
        if (isdir) {
                richacl_for_each_entry(dir_ace, dir_acl) {
                        if (!richace_is_inheritable(dir_ace))
                                continue;
+
                        count++;
+                       dir_unmapped =
+                               richace_unmapped_identifier(dir_ace, dir_acl);
+                       if (dir_unmapped)
+                               unmapped_size += strlen(dir_unmapped) + 1;
                }
                if (!count)
                        return NULL;
-               acl = richacl_alloc(count, GFP_KERNEL);
+               acl = __richacl_alloc(count, unmapped_size, GFP_KERNEL);
                if (!acl)
                        return ERR_PTR(-ENOMEM);
                ace = acl->a_entries;
+               unmapped = (char *)(acl->a_entries + acl->a_count);
                richacl_for_each_entry(dir_ace, dir_acl) {
                        if (!richace_is_inheritable(dir_ace))
                                continue;
+
                        richace_copy(ace, dir_ace);
                        if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
                                ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
                        else if (!(dir_ace->e_flags & 
RICHACE_DIRECTORY_INHERIT_ACE))
                                ace->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+
+                       dir_unmapped =
+                               richace_unmapped_identifier(dir_ace, dir_acl);
+                       if (dir_unmapped) {
+                               size_t sz = strlen(dir_unmapped) + 1;
+
+                               ace->e_id.offs = offset;
+                               memcpy(unmapped, dir_unmapped, sz);
+                               unmapped += sz;
+                               offset += sz;
+                       }
                        ace++;
                }
        } else {
                richacl_for_each_entry(dir_ace, dir_acl) {
                        if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
                                continue;
+
                        count++;
+                       dir_unmapped =
+                               richace_unmapped_identifier(dir_ace, dir_acl);
+                       if (dir_unmapped)
+                               unmapped_size += strlen(dir_unmapped) + 1;
                }
                if (!count)
                        return NULL;
-               acl = richacl_alloc(count, GFP_KERNEL);
+               acl = __richacl_alloc(count, unmapped_size, GFP_KERNEL);
                if (!acl)
                        return ERR_PTR(-ENOMEM);
                ace = acl->a_entries;
+               unmapped = (char *)(acl->a_entries + acl->a_count);
                richacl_for_each_entry(dir_ace, dir_acl) {
                        if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
                                continue;
+
                        richace_copy(ace, dir_ace);
                        ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
                        /*
@@ -551,6 +661,17 @@ richacl_inherit(const struct richacl *dir_acl, int isdir)
                         * non-directories, so clear it.
                         */
                        ace->e_mask &= ~RICHACE_DELETE_CHILD;
+
+                       dir_unmapped =
+                               richace_unmapped_identifier(dir_ace, dir_acl);
+                       if (dir_unmapped) {
+                               size_t sz = strlen(dir_unmapped) + 1;
+
+                               ace->e_id.offs = offset;
+                               memcpy(unmapped, dir_unmapped, sz);
+                               unmapped += sz;
+                               offset += sz;
+                       }
                        ace++;
                }
        }
diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index e513958..c5c670e 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -71,11 +71,13 @@ richacl_insert_entry(struct richacl_alloc *alloc, struct 
richace **ace)
 {
        struct richacl *acl = alloc->acl;
        unsigned int index = *ace - acl->a_entries;
-       size_t tail_size = (acl->a_count - index) * sizeof(struct richace);
+       size_t tail_size = (acl->a_count - index) * sizeof(struct richace) +
+                          acl->a_unmapped_size;
 
        if (alloc->count == acl->a_count) {
                size_t new_size = sizeof(struct richacl) +
-                       (acl->a_count + 1) * sizeof(struct richace);
+                       (acl->a_count + 1) * sizeof(struct richace) +
+                       acl->a_unmapped_size;
 
                acl = krealloc(acl, new_size, GFP_KERNEL);
                if (!acl)
@@ -103,10 +105,6 @@ struct richace *richacl_append_entry(struct richacl_alloc 
*alloc)
        struct richacl *acl = alloc->acl;
        struct richace *ace = acl->a_entries + acl->a_count;
 
-       if (alloc->count > alloc->acl->a_count) {
-               acl->a_count++;
-               return ace;
-       }
        return richacl_insert_entry(alloc, &ace) ? NULL : ace;
 }
 EXPORT_SYMBOL_GPL(richacl_append_entry);
@@ -261,12 +259,12 @@ __richacl_propagate_everyone(struct richacl_alloc *alloc, 
struct richace *who,
                if (richace_is_inherit_only(ace))
                        continue;
                if (richace_is_allow(ace)) {
-                       if (richace_is_same_identifier(ace, who)) {
+                       if (richace_is_same_identifier(acl, ace, who)) {
                                allow &= ~ace->e_mask;
                                allow_last = ace;
                        }
                } else if (richace_is_deny(ace)) {
-                       if (richace_is_same_identifier(ace, who))
+                       if (richace_is_same_identifier(acl, ace, who))
                                allow &= ~ace->e_mask;
                        else if (allow & ace->e_mask)
                                allow_last = NULL;
@@ -613,7 +611,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct 
richace *who,
        richacl_for_each_entry(ace, acl) {
                if (richace_is_inherit_only(ace))
                        continue;
-               if (richace_is_same_identifier(ace, who))
+               if (richace_is_same_identifier(acl, ace, who))
                        deny &= ~ace->e_mask;
        }
        if (!deny)
@@ -629,7 +627,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct 
richace *who,
                if (richace_is_inherit_only(ace))
                        continue;
                if (richace_is_deny(ace)) {
-                       if (richace_is_same_identifier(ace, who))
+                       if (richace_is_same_identifier(acl, ace, who))
                                return richace_change_mask(alloc, &ace,
                                                           ace->e_mask | deny);
                } else if (richace_is_allow(ace) &&
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
index b88a2f1..2f50389 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -161,8 +161,10 @@ richacl_permission(struct inode *inode, const struct 
richacl *acl,
                } else if (richace_is_unix_group(ace)) {
                        if (!in_group_p(ace->e_id.gid))
                                continue;
-               } else
+               } else if (richace_is_everyone(ace))
                        goto entry_matches_everyone;
+               else
+                       continue;
 
                /*
                 * Apply the group file mask to entries other than owner@ and
diff --git a/fs/richacl_xattr.c b/fs/richacl_xattr.c
index dc529dc..7ae5348 100644
--- a/fs/richacl_xattr.c
+++ b/fs/richacl_xattr.c
@@ -35,7 +35,8 @@ richacl_from_xattr(struct user_namespace *user_ns,
        const struct richace_xattr *xattr_ace = (void *)(xattr_acl + 1);
        struct richacl *acl;
        struct richace *ace;
-       int count;
+       unsigned int count, offset;
+       char *unmapped;
 
        if (size < sizeof(*xattr_acl) ||
            xattr_acl->a_version != RICHACL_XATTR_VERSION ||
@@ -45,10 +46,11 @@ richacl_from_xattr(struct user_namespace *user_ns,
        count = le16_to_cpu(xattr_acl->a_count);
        if (count > RICHACL_XATTR_MAX_COUNT)
                return ERR_PTR(-EINVAL);
-       if (size != count * sizeof(*xattr_ace))
+       if (size < count * sizeof(*xattr_ace))
                return ERR_PTR(-EINVAL);
+       size -= count * sizeof(*xattr_ace);
 
-       acl = richacl_alloc(count, GFP_NOFS);
+       acl = __richacl_alloc(count, size, GFP_NOFS);
        if (!acl)
                return ERR_PTR(-ENOMEM);
 
@@ -63,6 +65,16 @@ richacl_from_xattr(struct user_namespace *user_ns,
        if (acl->a_other_mask & ~RICHACE_VALID_MASK)
                goto fail_einval;
 
+       unmapped = (char *)(acl->a_entries + count);
+       if (size) {
+               char *xattr_unmapped = (char *)(xattr_ace + count);
+
+               if (xattr_unmapped[size - 1] != 0)
+                       goto fail_einval;
+               memcpy(unmapped, xattr_unmapped, size);
+       }
+       offset = 0;
+
        richacl_for_each_entry(ace, acl) {
                ace->e_type  = le16_to_cpu(xattr_ace->e_type);
                ace->e_flags = le16_to_cpu(xattr_ace->e_flags);
@@ -74,6 +86,15 @@ richacl_from_xattr(struct user_namespace *user_ns,
                        ace->e_id.special = le32_to_cpu(xattr_ace->e_id);
                        if (ace->e_id.special > RICHACE_EVERYONE_SPECIAL_ID)
                                goto fail_einval;
+               } else if (ace->e_flags & RICHACE_UNMAPPED_WHO) {
+                       size_t sz;
+
+                       if (offset == size)
+                               goto fail_einval;
+                       ace->e_id.offs = offset;
+                       sz = strlen(unmapped) + 1;
+                       unmapped += sz;
+                       offset += sz;
                } else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
                        u32 id = le32_to_cpu(xattr_ace->e_id);
 
@@ -90,10 +111,12 @@ richacl_from_xattr(struct user_namespace *user_ns,
                if (ace->e_type > RICHACE_ACCESS_DENIED_ACE_TYPE ||
                    (ace->e_mask & ~RICHACE_VALID_MASK))
                        goto fail_einval;
-
                xattr_ace++;
        }
 
+       if (offset != size)
+               goto fail_einval;
+
        return acl;
 
 fail_einval:
@@ -109,8 +132,15 @@ size_t
 richacl_xattr_size(const struct richacl *acl)
 {
        size_t size = sizeof(struct richacl_xattr);
+       const struct richace *ace;
 
        size += sizeof(struct richace_xattr) * acl->a_count;
+       richacl_for_each_entry(ace, acl) {
+               const char *unmapped = richace_unmapped_identifier(ace, acl);
+
+               if (unmapped)
+                       size += strlen(unmapped) + 1;
+       }
        return size;
 }
 EXPORT_SYMBOL_GPL(richacl_xattr_size);
@@ -129,6 +159,7 @@ richacl_to_xattr(struct user_namespace *user_ns,
        struct richace_xattr *xattr_ace;
        const struct richace *ace;
        size_t real_size;
+       char *xattr_unmapped;
 
        real_size = richacl_xattr_size(acl);
        if (!buffer)
@@ -145,18 +176,33 @@ richacl_to_xattr(struct user_namespace *user_ns,
        xattr_acl->a_other_mask = cpu_to_le32(acl->a_other_mask);
 
        xattr_ace = (void *)(xattr_acl + 1);
+       xattr_unmapped = (char *)(xattr_ace + acl->a_count);
        richacl_for_each_entry(ace, acl) {
+               const char *who;
+
                xattr_ace->e_type = cpu_to_le16(ace->e_type);
                xattr_ace->e_flags = cpu_to_le16(ace->e_flags);
                xattr_ace->e_mask = cpu_to_le32(ace->e_mask);
                if (ace->e_flags & RICHACE_SPECIAL_WHO)
                        xattr_ace->e_id = cpu_to_le32(ace->e_id.special);
-               else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
-                       xattr_ace->e_id =
-                               cpu_to_le32(from_kgid(user_ns, ace->e_id.gid));
-               else
-                       xattr_ace->e_id =
-                               cpu_to_le32(from_kuid(user_ns, ace->e_id.uid));
+               else {
+                       who = richace_unmapped_identifier(ace, acl);
+                       if (who) {
+                               size_t sz = strlen(who) + 1;
+
+                               xattr_ace->e_id = 0;
+                               memcpy(xattr_unmapped, who, sz);
+                               xattr_unmapped += sz;
+                       } else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
+                               u32 id = from_kgid(user_ns, ace->e_id.gid);
+
+                               xattr_ace->e_id = cpu_to_le32(id);
+                       } else {
+                               u32 id = from_kuid(user_ns, ace->e_id.uid);
+
+                               xattr_ace->e_id = cpu_to_le32(id);
+                       }
+               }
                xattr_ace++;
        }
        return real_size;
@@ -262,7 +308,8 @@ static void richacl_fix_xattr_userns(
                return;
        count = size / sizeof(*xattr_ace);
        for (; count; count--, xattr_ace++) {
-               if (xattr_ace->e_flags & cpu_to_le16(RICHACE_SPECIAL_WHO))
+               if (xattr_ace->e_flags & cpu_to_le16(RICHACE_SPECIAL_WHO |
+                                                    RICHACE_UNMAPPED_WHO))
                        continue;
                if (xattr_ace->e_flags &
                    cpu_to_le16(RICHACE_IDENTIFIER_GROUP)) {
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 6d2e5bd..a9751a7 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -27,6 +27,7 @@ struct richace {
                kuid_t          uid;
                kgid_t          gid;
                unsigned int    special;
+               unsigned short  offs;  /* unmapped offset */
        } e_id;
 };
 
@@ -37,6 +38,7 @@ struct richacl {
        unsigned int    a_other_mask;
        unsigned short  a_count;
        unsigned short  a_flags;
+       unsigned short  a_unmapped_size;
        struct richace  a_entries[0];
 };
 
@@ -54,6 +56,7 @@ struct richacl {
        RICHACE_INHERIT_ONLY_ACE |                              \
        RICHACE_IDENTIFIER_GROUP |                              \
        RICHACE_INHERITED_ACE |                                 \
+       RICHACE_UNMAPPED_WHO |                                  \
        RICHACE_SPECIAL_WHO)
 
 #define RICHACE_INHERITANCE_FLAGS (                            \
@@ -224,14 +227,28 @@ richace_is_deny(const struct richace *ace)
  * richace_is_same_identifier  -  are both identifiers the same?
  */
 static inline bool
-richace_is_same_identifier(const struct richace *a, const struct richace *b)
+richace_is_same_identifier(const struct richacl *acl,
+                          const struct richace *ace1,
+                          const struct richace *ace2)
 {
-       return !((a->e_flags ^ b->e_flags) &
-                (RICHACE_SPECIAL_WHO | RICHACE_IDENTIFIER_GROUP)) &&
-              !memcmp(&a->e_id, &b->e_id, sizeof(a->e_id));
+       const char *unmapped = (char *)(acl->a_entries + acl->a_count);
+
+       return !((ace1->e_flags ^ ace2->e_flags) &
+                (RICHACE_SPECIAL_WHO |
+                 RICHACE_IDENTIFIER_GROUP |
+                 RICHACE_UNMAPPED_WHO)) &&
+              ((ace1->e_flags & RICHACE_UNMAPPED_WHO) ?
+               !strcmp(unmapped + ace1->e_id.offs,
+                       unmapped + ace2->e_id.offs) :
+               !memcmp(&ace1->e_id, &ace2->e_id, sizeof(ace1->e_id)));
+}
+
+extern struct richacl *__richacl_alloc(unsigned int, size_t, gfp_t);
+static inline struct richacl *richacl_alloc(unsigned int count, gfp_t gfp)
+{
+       return __richacl_alloc(count, 0, gfp);
 }
 
-extern struct richacl *richacl_alloc(int, gfp_t);
 extern struct richacl *richacl_clone(const struct richacl *, gfp_t);
 extern void richace_copy(struct richace *, const struct richace *);
 extern int richacl_masks_to_mode(const struct richacl *);
@@ -241,6 +258,11 @@ extern void richacl_compute_max_masks(struct richacl *);
 extern struct richacl *__richacl_chmod(struct richacl *, umode_t);
 extern int richacl_equiv_mode(const struct richacl *, umode_t *);
 extern struct richacl *richacl_inherit(const struct richacl *, int);
+extern int richacl_add_unmapped_identifier(struct richacl **, struct richace 
**,
+                                          const char *, unsigned int, gfp_t);
+extern const char *richace_unmapped_identifier(const struct richace *,
+                                              const struct richacl *);
+extern bool richacl_has_unmapped_identifiers(struct richacl *);
 
 /* richacl_inode.c */
 extern int richacl_permission(struct inode *, const struct richacl *, int);
-- 
2.5.0

--
To unsubscribe from this list: send the line "unsubscribe linux-api" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to