Message ID | 1467294433-3222-16-git-send-email-agruenba@redhat.com (mailing list archive) |
---|---|
State | Not Applicable, archived |
Headers | show |
On Thu, 2016-06-30 at 15:47 +0200, Andreas Gruenbacher wrote: > ACLs are considered equivalent to file modes if they only consist of > owner@, group@, and everyone@ entries, the owner@ permissions do not > depend on whether the owner is a member in the owning group, and no > inheritance flags are set. This test is used to avoid storing richacls > if the acl can be computed from the file permission bits. > > Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com> > Reviewed-by: J. Bruce Fields <bfields@redhat.com> > --- > fs/richacl.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ > include/linux/richacl.h | 1 + > 2 files changed, 105 insertions(+) > > diff --git a/fs/richacl.c b/fs/richacl.c > index ba110a6..e8a383b 100644 > --- a/fs/richacl.c > +++ b/fs/richacl.c > @@ -618,3 +618,107 @@ richacl_chmod(struct inode *inode, umode_t mode) > return retval; > } > EXPORT_SYMBOL(richacl_chmod); > + > +/** > + * richacl_equiv_mode - compute the mode equivalent of @acl > + * > + * An acl is considered equivalent to a file mode if it only consists of > + * owner@, group@, and everyone@ entries and the owner@ permissions do not > + * depend on whether the owner is a member in the owning group. > + */ > +int > +richacl_equiv_mode(const struct richacl *acl, umode_t *mode_p) > +{ > + umode_t mode = *mode_p; > + > + /* > + * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so > + * we ignore it. > + */ > + unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD; > + struct { > + unsigned int allowed; > + unsigned int defined; /* allowed or denied */ > + } owner = { > + .defined = RICHACE_POSIX_ALWAYS_ALLOWED | > + RICHACE_POSIX_OWNER_ALLOWED | x, > + }, group = { > + .defined = RICHACE_POSIX_ALWAYS_ALLOWED | x, > + }, everyone = { > + .defined = RICHACE_POSIX_ALWAYS_ALLOWED | x, > + }; > + const struct richace *ace; > + > + if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED)) > + return -1; > + > + richacl_for_each_entry(ace, acl) { > + if (ace->e_flags & ~RICHACE_SPECIAL_WHO) > + return -1; > + > + if (richace_is_owner(ace) || richace_is_everyone(ace)) { > + x = ace->e_mask & ~owner.defined; > + if (richace_is_allow(ace)) { > + unsigned int group_denied = > + group.defined & ~group.allowed; > + > + if (x & group_denied) > + return -1; > + owner.allowed |= x; > + } else /* if (richace_is_deny(ace)) */ { > + if (x & group.allowed) > + return -1; > + } > + owner.defined |= x; > + > + if (richace_is_everyone(ace)) { > + x = ace->e_mask; > + if (richace_is_allow(ace)) { > + group.allowed |= > + x & ~group.defined; > + everyone.allowed |= > + x & ~everyone.defined; > + } > + group.defined |= x; > + everyone.defined |= x; > + } > + } else if (richace_is_group(ace)) { > + x = ace->e_mask & ~group.defined; > + if (richace_is_allow(ace)) > + group.allowed |= x; > + group.defined |= x; > + } else > + return -1; > + } > + > + if (group.allowed & ~owner.defined) > + return -1; > + > + if (acl->a_flags & RICHACL_MASKED) { > + if (acl->a_flags & RICHACL_WRITE_THROUGH) { > + owner.allowed = acl->a_owner_mask; > + everyone.allowed = acl->a_other_mask; > + } else { > + owner.allowed &= acl->a_owner_mask; > + everyone.allowed &= acl->a_other_mask; > + } > + group.allowed &= acl->a_group_mask; > + } > + > + mode = (mode & ~S_IRWXUGO) | > + (richacl_mask_to_mode(owner.allowed) << 6) | > + (richacl_mask_to_mode(group.allowed) << 3) | > + richacl_mask_to_mode(everyone.allowed); > + > + /* Mask flags we can ignore */ > + x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD; > + > + if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed) & ~x) || > + ((richacl_mode_to_mask(mode >> 3) ^ group.allowed) & ~x) || > + ((richacl_mode_to_mask(mode) ^ everyone.allowed) & ~x)) > + return -1; > + > + *mode_p = mode; > + return 0; > +} > +EXPORT_SYMBOL_GPL(richacl_equiv_mode); > diff --git a/include/linux/richacl.h b/include/linux/richacl.h > index db82fab..9212edb 100644 > --- a/include/linux/richacl.h > +++ b/include/linux/richacl.h > @@ -191,5 +191,6 @@ extern unsigned int richacl_want_to_mask(unsigned int); > extern void richacl_compute_max_masks(struct richacl *); > extern int richacl_permission(struct inode *, const struct richacl *, int); > extern int richacl_chmod(struct inode *, umode_t); > +extern int richacl_equiv_mode(const struct richacl *, umode_t *); > > #endif /* __RICHACL_H */ Reviewed-by: Jeff Layton <jlayton@redhat.com>
diff --git a/fs/richacl.c b/fs/richacl.c index ba110a6..e8a383b 100644 --- a/fs/richacl.c +++ b/fs/richacl.c @@ -618,3 +618,107 @@ richacl_chmod(struct inode *inode, umode_t mode) return retval; } EXPORT_SYMBOL(richacl_chmod); + +/** + * richacl_equiv_mode - compute the mode equivalent of @acl + * + * An acl is considered equivalent to a file mode if it only consists of + * owner@, group@, and everyone@ entries and the owner@ permissions do not + * depend on whether the owner is a member in the owning group. + */ +int +richacl_equiv_mode(const struct richacl *acl, umode_t *mode_p) +{ + umode_t mode = *mode_p; + + /* + * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so + * we ignore it. + */ + unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD; + struct { + unsigned int allowed; + unsigned int defined; /* allowed or denied */ + } owner = { + .defined = RICHACE_POSIX_ALWAYS_ALLOWED | + RICHACE_POSIX_OWNER_ALLOWED | x, + }, group = { + .defined = RICHACE_POSIX_ALWAYS_ALLOWED | x, + }, everyone = { + .defined = RICHACE_POSIX_ALWAYS_ALLOWED | x, + }; + const struct richace *ace; + + if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED)) + return -1; + + richacl_for_each_entry(ace, acl) { + if (ace->e_flags & ~RICHACE_SPECIAL_WHO) + return -1; + + if (richace_is_owner(ace) || richace_is_everyone(ace)) { + x = ace->e_mask & ~owner.defined; + if (richace_is_allow(ace)) { + unsigned int group_denied = + group.defined & ~group.allowed; + + if (x & group_denied) + return -1; + owner.allowed |= x; + } else /* if (richace_is_deny(ace)) */ { + if (x & group.allowed) + return -1; + } + owner.defined |= x; + + if (richace_is_everyone(ace)) { + x = ace->e_mask; + if (richace_is_allow(ace)) { + group.allowed |= + x & ~group.defined; + everyone.allowed |= + x & ~everyone.defined; + } + group.defined |= x; + everyone.defined |= x; + } + } else if (richace_is_group(ace)) { + x = ace->e_mask & ~group.defined; + if (richace_is_allow(ace)) + group.allowed |= x; + group.defined |= x; + } else + return -1; + } + + if (group.allowed & ~owner.defined) + return -1; + + if (acl->a_flags & RICHACL_MASKED) { + if (acl->a_flags & RICHACL_WRITE_THROUGH) { + owner.allowed = acl->a_owner_mask; + everyone.allowed = acl->a_other_mask; + } else { + owner.allowed &= acl->a_owner_mask; + everyone.allowed &= acl->a_other_mask; + } + group.allowed &= acl->a_group_mask; + } + + mode = (mode & ~S_IRWXUGO) | + (richacl_mask_to_mode(owner.allowed) << 6) | + (richacl_mask_to_mode(group.allowed) << 3) | + richacl_mask_to_mode(everyone.allowed); + + /* Mask flags we can ignore */ + x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD; + + if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed) & ~x) || + ((richacl_mode_to_mask(mode >> 3) ^ group.allowed) & ~x) || + ((richacl_mode_to_mask(mode) ^ everyone.allowed) & ~x)) + return -1; + + *mode_p = mode; + return 0; +} +EXPORT_SYMBOL_GPL(richacl_equiv_mode); diff --git a/include/linux/richacl.h b/include/linux/richacl.h index db82fab..9212edb 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -191,5 +191,6 @@ extern unsigned int richacl_want_to_mask(unsigned int); extern void richacl_compute_max_masks(struct richacl *); extern int richacl_permission(struct inode *, const struct richacl *, int); extern int richacl_chmod(struct inode *, umode_t); +extern int richacl_equiv_mode(const struct richacl *, umode_t *); #endif /* __RICHACL_H */