Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934262AbbGVNKZ (ORCPT ); Wed, 22 Jul 2015 09:10:25 -0400 Received: from mail-wi0-f174.google.com ([209.85.212.174]:36166 "EHLO mail-wi0-f174.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756986AbbGVNEq (ORCPT ); Wed, 22 Jul 2015 09:04:46 -0400 From: Andreas Gruenbacher To: linux-kernel@vger.kernel.org Cc: linux-fsdevel@vger.kernel.org, linux-nfs@vger.kernel.org, linux-api@vger.kernel.org, samba-technical@lists.samba.org, linux-security-module@vger.kernel.org, Andreas Gruenbacher Subject: [PATCH v5 32/39] richacl: Add support for unmapped identifiers Date: Wed, 22 Jul 2015 15:03:22 +0200 Message-Id: <1437570209-29832-33-git-send-email-andreas.gruenbacher@gmail.com> X-Mailer: git-send-email 2.4.3 In-Reply-To: <1437570209-29832-1-git-send-email-andreas.gruenbacher@gmail.com> References: <1437570209-29832-1-git-send-email-andreas.gruenbacher@gmail.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 18186 Lines: 551 From: Andreas Gruenbacher 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 --- fs/richacl_base.c | 139 ++++++++++++++++++++++++++++++++++++++++++++---- fs/richacl_compat.c | 18 +++---- fs/richacl_xattr.c | 52 +++++++++++++++--- include/linux/richacl.h | 33 ++++++++++-- 4 files changed, 212 insertions(+), 30 deletions(-) diff --git a/fs/richacl_base.c b/fs/richacl_base.c index 7d17279..8c52d4b 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 which correspond to @mask * @mask: %RICHACE_* permission mask @@ -209,7 +292,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; @@ -488,46 +571,73 @@ 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) richace_clear_inheritance_flags(ace); if ((dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE) && !(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); richace_clear_inheritance_flags(ace); /* @@ -535,6 +645,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 b7d3990..eea964e 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); @@ -260,12 +258,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; @@ -587,7 +585,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) && richace_is_deny(ace)) deny &= ~ace->e_mask; } @@ -604,7 +602,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)) break; } else if (richace_is_allow(ace) && (ace->e_mask & deny)) { diff --git a/fs/richacl_xattr.c b/fs/richacl_xattr.c index d145915..1d40000 100644 --- a/fs/richacl_xattr.c +++ b/fs/richacl_xattr.c @@ -33,7 +33,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 || @@ -43,10 +44,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); @@ -61,6 +63,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); @@ -72,6 +84,14 @@ 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) { ace->e_id.gid = make_kgid(user_ns, le32_to_cpu(xattr_ace->e_id)); if (!gid_valid(ace->e_id.gid)) @@ -84,10 +104,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: @@ -103,8 +125,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); @@ -123,6 +152,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) @@ -139,13 +169,22 @@ 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) + else if ((who = richace_unmapped_identifier(ace, acl))) { + 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) xattr_ace->e_id = cpu_to_le32(from_kgid(user_ns, ace->e_id.gid)); else @@ -180,7 +219,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)) { kgid_t gid = make_kgid(from, le32_to_cpu(xattr_ace->e_id)); diff --git a/include/linux/richacl.h b/include/linux/richacl.h index eeb5bd9..39f5ce4 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -29,6 +29,7 @@ struct richace { kuid_t uid; kgid_t gid; unsigned int special; + unsigned short offs; /* unmapped offset */ } e_id; }; @@ -39,6 +40,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]; }; @@ -75,6 +77,7 @@ struct richacl { #define RICHACE_INHERIT_ONLY_ACE 0x0008 #define RICHACE_IDENTIFIER_GROUP 0x0040 #define RICHACE_INHERITED_ACE 0x0080 +#define RICHACE_UNMAPPED_WHO 0x2000 #define RICHACE_SPECIAL_WHO 0x4000 #define RICHACE_VALID_FLAGS ( \ @@ -84,6 +87,7 @@ struct richacl { RICHACE_INHERIT_ONLY_ACE | \ RICHACE_IDENTIFIER_GROUP | \ RICHACE_INHERITED_ACE | \ + RICHACE_UNMAPPED_WHO | \ RICHACE_SPECIAL_WHO) /* e_mask bitflags */ @@ -314,14 +318,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 *); @@ -331,6 +349,11 @@ extern void richacl_compute_max_masks(struct richacl *, kuid_t); extern struct richacl *richacl_chmod(struct richacl *, mode_t); extern int richacl_equiv_mode(const struct richacl *, mode_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.4.3 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/