All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Günther Noack" <gnoack3000@gmail.com>
To: "Mickaël Salaün" <mic@digikod.net>
Cc: "Günther Noack" <gnoack@google.com>,
	"Mikhail Ivanov" <ivanov.mikhail1@huawei-partners.com>,
	"Konstantin Meskhidze" <konstantin.meskhidze@huawei.com>,
	"Paul Moore" <paul@paul-moore.com>,
	"Tahera Fahimi" <fahimitahera@gmail.com>,
	linux-kernel@vger.kernel.org,
	linux-security-module@vger.kernel.org
Subject: Re: [PATCH v1 1/3] landlock: Refactor filesystem access mask management
Date: Sat, 5 Oct 2024 18:57:55 +0200	[thread overview]
Message-ID: <20241005.a69458234f74@gnoack.org> (raw)
In-Reply-To: <20241001141234.397649-2-mic@digikod.net>

On Tue, Oct 01, 2024 at 04:12:32PM +0200, Mickaël Salaün wrote:
> Replace get_raw_handled_fs_accesses() with a generic
> landlock_merge_access_masks(), and replace the get_fs_domain()
> implementation with a call to the new landlock_filter_access_masks()
> helper.  These helpers will also be useful for other types of access.
> 
> Replace struct access_masks with union access_masks that includes a new
> "all" field to simplify mask filtering.
> 
> Cc: Günther Noack <gnoack@google.com>
> Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241001141234.397649-2-mic@digikod.net
> ---
>  security/landlock/fs.c       | 21 ++++-----------
>  security/landlock/ruleset.h  | 51 +++++++++++++++++++++++++++---------
>  security/landlock/syscalls.c |  2 +-
>  3 files changed, 44 insertions(+), 30 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 7d79fc8abe21..a2ef7d151c81 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -388,33 +388,22 @@ static bool is_nouser_or_private(const struct dentry *dentry)
>  		unlikely(IS_PRIVATE(d_backing_inode(dentry))));
>  }
>  
> -static access_mask_t
> -get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
> -{
> -	access_mask_t access_dom = 0;
> -	size_t layer_level;
> -
> -	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> -		access_dom |=
> -			landlock_get_raw_fs_access_mask(domain, layer_level);
> -	return access_dom;
> -}
> -
>  static access_mask_t
>  get_handled_fs_accesses(const struct landlock_ruleset *const domain)
>  {
>  	/* Handles all initially denied by default access rights. */
> -	return get_raw_handled_fs_accesses(domain) |
> +	return landlock_merge_access_masks(domain).fs |
>  	       LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
>  }
>  
>  static const struct landlock_ruleset *
>  get_fs_domain(const struct landlock_ruleset *const domain)
>  {
> -	if (!domain || !get_raw_handled_fs_accesses(domain))
> -		return NULL;
> +	const union access_masks all_fs = {
> +		.fs = ~0,
> +	};
>  
> -	return domain;
> +	return landlock_filter_access_masks(domain, all_fs);
>  }
>  
>  static const struct landlock_ruleset *get_current_fs_domain(void)
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 61bdbc550172..a816042ca8f3 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -41,12 +41,19 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
>  static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
>  
>  /* Ruleset access masks. */
> -struct access_masks {
> -	access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
> -	access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> -	access_mask_t scope : LANDLOCK_NUM_SCOPE;
> +union access_masks {
> +	struct {
> +		access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
> +		access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> +		access_mask_t scope : LANDLOCK_NUM_SCOPE;
> +	};
> +	u32 all;
>  };

More of a style remark:

I wonder whether it is worth turning this into a union.

If this is for performance, I do not think is buys you much.  With
optimization enabled, it does not make much of a difference whether
you are doing the & on .all or whether you are doing it on the
individual fields.  (I tried it out with gcc.  The only difference is
that the & on the individual fields will at the end mask only the bits
that belong to these fields.)

At the same time, in most places where struct access_masks is used,
the union is not necessary and might add to the confusion.


>  
> +/* Makes sure all fields are covered. */
> +static_assert(sizeof(((union access_masks *)NULL)->all) ==
> +	      sizeof(union access_masks));
> +
>  typedef u16 layer_mask_t;
>  /* Makes sure all layers can be checked. */
>  static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
> @@ -229,7 +236,7 @@ struct landlock_ruleset {
>  			 * layers are set once and never changed for the
>  			 * lifetime of the ruleset.
>  			 */
> -			struct access_masks access_masks[];
> +			union access_masks access_masks[];
>  		};
>  	};
>  };
> @@ -260,6 +267,31 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
>  		refcount_inc(&ruleset->usage);
>  }
>  
> +static inline union access_masks
> +landlock_merge_access_masks(const struct landlock_ruleset *const domain)
> +{
> +	size_t layer_level;
> +	union access_masks matches = {};
> +
> +	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> +		matches.all |= domain->access_masks[layer_level].all;
> +
> +	return matches;
> +}
> +
> +static inline const struct landlock_ruleset *
> +landlock_filter_access_masks(const struct landlock_ruleset *const domain,
> +			     const union access_masks masks)

With this function name, the return type of this function is
unintuitive to me.  Judging by the name, I would have expected a
function that returns a "access_masks" value as well, similar to the
function one above (the remaining access rights after filtering)?

In the places where the result of this function is returned directly,
I find myself jumping back to the function implementation to
understand what this means.

As a constructive suggestion, how about calling this function
differently, e.g.

bool landlock_any_access_rights_handled(
    const struct landlock_ruleset *const domain,
    struct access_masks masks);

Then the callers who previously did

   return landlock_filter_access_masks(dom, masks);

would now do

   if (landlock_any_access_rights_handled(dom, masks))
       return dom;
   return NULL;

This is more verbose, but IMHO verbose code is not inherently bad,
if it is also clearer.  And it's only two lines more.

> +{
> +	if (!domain)
> +		return NULL;
> +
> +	if (landlock_merge_access_masks(domain).all & masks.all)
> +		return domain;
> +
> +	return NULL;
> +}

Function documentation for both functions would be good :)

> +
>  static inline void
>  landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
>  			    const access_mask_t fs_access_mask,
> @@ -295,19 +327,12 @@ landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
>  	ruleset->access_masks[layer_level].scope |= mask;
>  }
>  
> -static inline access_mask_t
> -landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
> -				const u16 layer_level)
> -{
> -	return ruleset->access_masks[layer_level].fs;
> -}
> -
>  static inline access_mask_t
>  landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>  			    const u16 layer_level)
>  {
>  	/* Handles all initially denied by default access rights. */
> -	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
> +	return ruleset->access_masks[layer_level].fs |
>  	       LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
>  }
>  
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index f5a0e7182ec0..c097d356fa45 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -329,7 +329,7 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>  		return -ENOMSG;
>  
>  	/* Checks that allowed_access matches the @ruleset constraints. */
> -	mask = landlock_get_raw_fs_access_mask(ruleset, 0);
> +	mask = ruleset->access_masks[0].fs;
>  	if ((path_beneath_attr.allowed_access | mask) != mask)
>  		return -EINVAL;
>  
> -- 
> 2.46.1
> 

Reviewed-by: Günther Noack <gnoack3000@gmail.com>

–Günther

  reply	other threads:[~2024-10-05 16:57 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-10-01 14:12 [PATCH v1 0/3] Refactor Landlock access mask management Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 1/3] landlock: Refactor filesystem " Mickaël Salaün
2024-10-05 16:57   ` Günther Noack [this message]
2024-10-07 13:00     ` Mickaël Salaün
2024-10-10  9:10       ` Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 2/3] landlock: Refactor network " Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 3/3] landlock: Optimize scope enforcement Mickaël Salaün

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20241005.a69458234f74@gnoack.org \
    --to=gnoack3000@gmail.com \
    --cc=fahimitahera@gmail.com \
    --cc=gnoack@google.com \
    --cc=ivanov.mikhail1@huawei-partners.com \
    --cc=konstantin.meskhidze@huawei.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-security-module@vger.kernel.org \
    --cc=mic@digikod.net \
    --cc=paul@paul-moore.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.