diff mbox

[RFC,v3,14/45] richacl: Permission check algorithm

Message ID 380de2cdb4a129e4b5d7b586260b79d71c7d241d.1429868795.git.agruenba@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Andreas Grünbacher April 24, 2015, 11:04 a.m. UTC
A richacl grants a requested access if the NFSv4 acl in the richacl grants the
requested permissions (according to the NFSv4 permission check algorithm) and
the file mask that applies to the process includes the requested permissions.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/Makefile             |   2 +-
 fs/richacl_inode.c      | 130 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |   3 ++
 3 files changed, 134 insertions(+), 1 deletion(-)
 create mode 100644 fs/richacl_inode.c

Comments

J. Bruce Fields May 22, 2015, 9:08 p.m. UTC | #1
On Fri, Apr 24, 2015 at 01:04:11PM +0200, Andreas Gruenbacher wrote:
> A richacl grants a requested access if the NFSv4 acl in the richacl
> grants the requested permissions (according to the NFSv4 permission
> check algorithm) and the file mask that applies to the process
> includes the requested permissions.

Is that right?  Based on that I'd have thought that an acl like

	owner  :r::mask
	group  :-::mask
	other  :-::mask
	bfields:r::allow

would permit read to bfields in the case bfields is the file owner,
because both the mask entries and the NFSv4 ACL would permit access.

But I think it doesn't (because the "bfields" entry is subject to the
group mask).

But if I'm right, I'm not sure how to describe the algorithm concisely.

--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/Makefile             |   2 +-
>  fs/richacl_inode.c      | 130 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |   3 ++
>  3 files changed, 134 insertions(+), 1 deletion(-)
>  create mode 100644 fs/richacl_inode.c
> 
> diff --git a/fs/Makefile b/fs/Makefile
> index 8f0a59c..bb96ad7 100644
> --- a/fs/Makefile
> +++ b/fs/Makefile
> @@ -48,7 +48,7 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
>  
>  obj-$(CONFIG_FHANDLE)		+= fhandle.o
>  obj-$(CONFIG_FS_RICHACL)	+= richacl.o
> -richacl-y			:= richacl_base.o
> +richacl-y			:= richacl_base.o richacl_inode.o
>  
>  obj-y				+= quota/
>  
> diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
> new file mode 100644
> index 0000000..33e591a
> --- /dev/null
> +++ b/fs/richacl_inode.c
> @@ -0,0 +1,130 @@
> +/*
> + * Copyright (C) 2010  Novell, Inc.
> + * Copyright (C) 2015  Red Hat, Inc.
> + * Written by Andreas Gruenbacher <agruen@kernel.org>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2, or (at your option) any
> + * later version.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#include <linux/sched.h>
> +#include <linux/module.h>
> +#include <linux/fs.h>
> +#include <linux/slab.h>
> +#include <linux/richacl.h>
> +
> +/**
> + * richacl_permission  -  richacl permission check algorithm
> + * @inode:	inode to check
> + * @acl:	rich acl of the inode
> + * @want:	requested access (MAY_* flags)
> + *
> + * Checks if the current process is granted @mask flags in @acl.
> + */
> +int
> +richacl_permission(struct inode *inode, const struct richacl *acl,
> +		   int want)
> +{
> +	const struct richace *ace;
> +	unsigned int mask = richacl_want_to_mask(want);
> +	unsigned int requested = mask, denied = 0;
> +	int in_owning_group = in_group_p(inode->i_gid);
> +	int in_owner_or_group_class = in_owning_group;
> +
> +	/*
> +	 * We don't need to know which class the process is in when the acl is
> +	 * not masked.
> +	 */
> +	if (!(acl->a_flags & RICHACL_MASKED))
> +		in_owner_or_group_class = 1;
> +
> +	/*
> +	 * A process is
> +	 *   - in the owner file class if it owns the file,
> +	 *   - in the group file class if it is in the file's owning group or
> +	 *     it matches any of the user or group entries, and
> +	 *   - in the other file class otherwise.
> +	 */
> +
> +	/*
> +	 * Check if the acl grants the requested access and determine which
> +	 * file class the process is in.
> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		unsigned int ace_mask = ace->e_mask;
> +
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_owner(ace)) {
> +			if (!uid_eq(current_fsuid(), inode->i_uid))
> +				continue;
> +			goto is_owner;
> +		} else if (richace_is_group(ace)) {
> +			if (!in_owning_group)
> +				continue;
> +		} else if (richace_is_unix_id(ace)) {
> +			if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
> +				if (!in_group_p(ace->e_id.gid))
> +					continue;
> +			} else {
> +				if (!uid_eq(current_fsuid(), ace->e_id.uid))
> +					continue;
> +			}
> +		} else
> +			goto is_everyone;
> +
> +		/*
> +		 * Apply the group file mask to entries other than OWNER@ and
> +		 * EVERYONE@. This is not required for correct access checking
> +		 * but ensures that we grant the same permissions as the acl
> +		 * computed by richacl_apply_masks() would grant.
> +		 */
> +		if ((acl->a_flags & RICHACL_MASKED) && richace_is_allow(ace))
> +			ace_mask &= acl->a_group_mask;
> +
> +is_owner:
> +		/* The process is in the owner or group file class. */
> +		in_owner_or_group_class = 1;
> +
> +is_everyone:
> +		/* Check which mask flags the ACE allows or denies. */
> +		if (richace_is_deny(ace))
> +			denied |= ace_mask & mask;
> +		mask &= ~ace_mask;
> +
> +		/*
> +		 * Keep going until we know which file class
> +		 * the process is in.
> +		 */
> +		if (!mask && in_owner_or_group_class)
> +			break;
> +	}
> +	denied |= mask;
> +
> +	if (acl->a_flags & RICHACL_MASKED) {
> +		unsigned int file_mask;
> +
> +		/*
> +		 * The file class a process is in determines which file mask
> +		 * applies.  Check if that file mask also grants the requested
> +		 * access.
> +		 */
> +		if (uid_eq(current_fsuid(), inode->i_uid))
> +			file_mask = acl->a_owner_mask;
> +		else if (in_owner_or_group_class)
> +			file_mask = acl->a_group_mask;
> +		else
> +			file_mask = acl->a_other_mask;
> +		denied |= requested & ~file_mask;
> +	}
> +
> +	return denied ? -EACCES : 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_permission);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index a27ff10..d11e8f3 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -292,4 +292,7 @@ extern unsigned int richacl_want_to_mask(unsigned int);
>  extern void richacl_compute_max_masks(struct richacl *);
>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>  
> +/* richacl_inode.c */
> +extern int richacl_permission(struct inode *, const struct richacl *, int);
> +
>  #endif /* __RICHACL_H */
> -- 
> 2.1.0
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Andreas Grünbacher May 27, 2015, 11:25 a.m. UTC | #2
2015-05-22 23:08 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Fri, Apr 24, 2015 at 01:04:11PM +0200, Andreas Gruenbacher wrote:
>> A richacl grants a requested access if the NFSv4 acl in the richacl
>> grants the requested permissions (according to the NFSv4 permission
>> check algorithm) and the file mask that applies to the process
>> includes the requested permissions.
>
> Is that right?  Based on that I'd have thought that an acl like
>
>         owner  :r::mask
>         group  :-::mask
>         other  :-::mask
>         bfields:r::allow
>
> would permit read to bfields in the case bfields is the file owner,
> because both the mask entries and the NFSv4 ACL would permit access.
>
> But I think it doesn't (because the "bfields" entry is subject to the
> group mask).

The problem at cause here that if we treat the acl and masks completely
separately, we can end up with acl/mask combinations that don't have a
representation as "normal" acls. For example, this:

   owner:r::mask
   group:-::mask
   other:-::mask
  group@:r::allow

would grant the owner read access when in the owning group, but
wouldn't grant the owning group access. This is handled by applying
the group mask to all group-class entries; see this comment in the
code:

                /*
                 * Apply the group file mask to entries other than OWNER@ and
                 * EVERYONE@. This is not required for correct access checking
                 * but ensures that we grant the same permissions as the acl
                 * computed by richacl_apply_masks() would grant.
                 */

There is no necessity to also apply this rule to user entries matching the
current owner though; we can change that here and in richacl_apply_masks().

> But if I'm right, I'm not sure how to describe the algorithm concisely.

Right, it's all a bit messy.

Thanks,
Andreas
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
J. Bruce Fields May 27, 2015, 3:05 p.m. UTC | #3
On Wed, May 27, 2015 at 01:25:18PM +0200, Andreas Grünbacher wrote:
> 2015-05-22 23:08 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> > On Fri, Apr 24, 2015 at 01:04:11PM +0200, Andreas Gruenbacher wrote:
> >> A richacl grants a requested access if the NFSv4 acl in the richacl
> >> grants the requested permissions (according to the NFSv4 permission
> >> check algorithm) and the file mask that applies to the process
> >> includes the requested permissions.
> >
> > Is that right?  Based on that I'd have thought that an acl like
> >
> >         owner  :r::mask
> >         group  :-::mask
> >         other  :-::mask
> >         bfields:r::allow
> >
> > would permit read to bfields in the case bfields is the file owner,
> > because both the mask entries and the NFSv4 ACL would permit access.
> >
> > But I think it doesn't (because the "bfields" entry is subject to the
> > group mask).
> 
> The problem at cause here that if we treat the acl and masks completely
> separately, we can end up with acl/mask combinations that don't have a
> representation as "normal" acls. For example, this:
> 
>    owner:r::mask
>    group:-::mask
>    other:-::mask
>   group@:r::allow
> 
> would grant the owner read access when in the owning group, but
> wouldn't grant the owning group access. This is handled by applying
> the group mask to all group-class entries; see this comment in the
> code:

OK, good, that makes sense, so I think all that's needed is some updates
to documentation; for example:

>                 /*
>                  * Apply the group file mask to entries other than OWNER@ and
>                  * EVERYONE@. This is not required for correct access checking

This is a strange way to put it; it makes it sound like the logic here
might not affect the access check result at all.

And this routine is basically the definition of the access check
algorithm, so the logic here is required.

>                  * but ensures that we grant the same permissions as the acl
>                  * computed by richacl_apply_masks() would grant.

I mean, I do understand what you're saying here, but it's a slightly
backwards way to put it....

>                  */
> 
> There is no necessity to also apply this rule to user entries matching the
> current owner though; we can change that here and in richacl_apply_masks().
> 
> > But if I'm right, I'm not sure how to describe the algorithm concisely.
> 
> Right, it's all a bit messy.

We need to put some thought into how this will be explained in man pages
and such, if you don't have that already.  It's inherently a complicated
ACL model, and hopefully this sort of corner case can be ignored most of
the time, but we need a good complete explanation somewhere that a
stressed sysadmin can follow if they need it....

I wish we could do something a little dumber and simpler, e.g., like

	http://docs.openafs.org/UserGuide/HDRWQ59.html

but maybe it's not practical.

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

Patch

diff --git a/fs/Makefile b/fs/Makefile
index 8f0a59c..bb96ad7 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -48,7 +48,7 @@  obj-$(CONFIG_SYSCTL)		+= drop_caches.o
 
 obj-$(CONFIG_FHANDLE)		+= fhandle.o
 obj-$(CONFIG_FS_RICHACL)	+= richacl.o
-richacl-y			:= richacl_base.o
+richacl-y			:= richacl_base.o richacl_inode.o
 
 obj-y				+= quota/
 
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
new file mode 100644
index 0000000..33e591a
--- /dev/null
+++ b/fs/richacl_inode.c
@@ -0,0 +1,130 @@ 
+/*
+ * Copyright (C) 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/richacl.h>
+
+/**
+ * richacl_permission  -  richacl permission check algorithm
+ * @inode:	inode to check
+ * @acl:	rich acl of the inode
+ * @want:	requested access (MAY_* flags)
+ *
+ * Checks if the current process is granted @mask flags in @acl.
+ */
+int
+richacl_permission(struct inode *inode, const struct richacl *acl,
+		   int want)
+{
+	const struct richace *ace;
+	unsigned int mask = richacl_want_to_mask(want);
+	unsigned int requested = mask, denied = 0;
+	int in_owning_group = in_group_p(inode->i_gid);
+	int in_owner_or_group_class = in_owning_group;
+
+	/*
+	 * We don't need to know which class the process is in when the acl is
+	 * not masked.
+	 */
+	if (!(acl->a_flags & RICHACL_MASKED))
+		in_owner_or_group_class = 1;
+
+	/*
+	 * A process is
+	 *   - in the owner file class if it owns the file,
+	 *   - in the group file class if it is in the file's owning group or
+	 *     it matches any of the user or group entries, and
+	 *   - in the other file class otherwise.
+	 */
+
+	/*
+	 * Check if the acl grants the requested access and determine which
+	 * file class the process is in.
+	 */
+	richacl_for_each_entry(ace, acl) {
+		unsigned int ace_mask = ace->e_mask;
+
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_owner(ace)) {
+			if (!uid_eq(current_fsuid(), inode->i_uid))
+				continue;
+			goto is_owner;
+		} else if (richace_is_group(ace)) {
+			if (!in_owning_group)
+				continue;
+		} else if (richace_is_unix_id(ace)) {
+			if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
+				if (!in_group_p(ace->e_id.gid))
+					continue;
+			} else {
+				if (!uid_eq(current_fsuid(), ace->e_id.uid))
+					continue;
+			}
+		} else
+			goto is_everyone;
+
+		/*
+		 * Apply the group file mask to entries other than OWNER@ and
+		 * EVERYONE@. This is not required for correct access checking
+		 * but ensures that we grant the same permissions as the acl
+		 * computed by richacl_apply_masks() would grant.
+		 */
+		if ((acl->a_flags & RICHACL_MASKED) && richace_is_allow(ace))
+			ace_mask &= acl->a_group_mask;
+
+is_owner:
+		/* The process is in the owner or group file class. */
+		in_owner_or_group_class = 1;
+
+is_everyone:
+		/* Check which mask flags the ACE allows or denies. */
+		if (richace_is_deny(ace))
+			denied |= ace_mask & mask;
+		mask &= ~ace_mask;
+
+		/*
+		 * Keep going until we know which file class
+		 * the process is in.
+		 */
+		if (!mask && in_owner_or_group_class)
+			break;
+	}
+	denied |= mask;
+
+	if (acl->a_flags & RICHACL_MASKED) {
+		unsigned int file_mask;
+
+		/*
+		 * The file class a process is in determines which file mask
+		 * applies.  Check if that file mask also grants the requested
+		 * access.
+		 */
+		if (uid_eq(current_fsuid(), inode->i_uid))
+			file_mask = acl->a_owner_mask;
+		else if (in_owner_or_group_class)
+			file_mask = acl->a_group_mask;
+		else
+			file_mask = acl->a_other_mask;
+		denied |= requested & ~file_mask;
+	}
+
+	return denied ? -EACCES : 0;
+}
+EXPORT_SYMBOL_GPL(richacl_permission);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index a27ff10..d11e8f3 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -292,4 +292,7 @@  extern unsigned int richacl_want_to_mask(unsigned int);
 extern void richacl_compute_max_masks(struct richacl *);
 extern struct richacl *richacl_chmod(struct richacl *, mode_t);
 
+/* richacl_inode.c */
+extern int richacl_permission(struct inode *, const struct richacl *, int);
+
 #endif /* __RICHACL_H */