[PATCH v3 3/3] landlock: transpose the layer masks data structure

Mickaël Salaün mic at digikod.net
Sat Feb 7 10:17:34 UTC 2026


On Fri, Feb 06, 2026 at 04:11:55PM +0100, Günther Noack wrote:
> The layer masks data structure tracks the requested but unfulfilled
> access rights during an operation's security check.  It stores one bit
> for each combination of access right and layer index.  If the bit is
> set, that access right is not granted (yet) in the given layer and we
> have to traverse the path further upwards to grant it.
> 
> Previously, the layer masks were stored as arrays mapping from access
> right indices to layer_mask_t.  The layer_mask_t value then indicates
> all layers in which the given access right is still (tentatively)
> denied.
> 
> This patch introduces struct layer_access_masks instead: This struct
> contains an array with the access_mask_t of each (tentatively) denied
> access right in that layer.
> 
> The hypothesis of this patch is that this simplifies the code enough
> so that the resulting code will run faster:
> 
> * We can use bitwise operations in multiple places where we previously
>   looped over bits individually with macros.  (Should require less
>   branch speculation and lends itself to better loop unrolling.)
> 
> * Code is ~75 lines smaller.
> 
> Other noteworthy changes:
> 
> * In no_more_access(), call a new helper function may_refer(), which
>   only solves the asymmetric case.  Previously, the code interleaved
>   the checks for the two symmetric cases in RENAME_EXCHANGE.  It feels
>   that the code is clearer when renames without RENAME_EXCHANGE are
>   more obviously the normal case.
> 
> Tradeoffs:
> 
> This change improves performance, at a slight size increase to the
> layer masks data structure.
> 
> This fixes the size of the data structure at 32 bytes for all types of
> access rights. (64, once we introduce a 17th filesystem access right).
> 
> For filesystem access rights, at the moment, the data structure has
> the same size as before, but once we introduce the 17th filesystem
> access right, it will double in size (from 32 to 64 bytes), as
> access_mask_t grows from 16 to 32 bit. [1]
> 
> Link: https://lore.kernel.org/all/20260120.haeCh4li9Vae@digikod.net/ [1]
> Signed-off-by: Günther Noack <gnoack3000 at gmail.com>
> ---
>  security/landlock/access.h  |  15 +-
>  security/landlock/audit.c   |  81 +++------
>  security/landlock/audit.h   |   3 +-
>  security/landlock/domain.c  |  45 ++---
>  security/landlock/domain.h  |   4 +-
>  security/landlock/fs.c      | 348 ++++++++++++++++--------------------
>  security/landlock/net.c     |   9 +-
>  security/landlock/ruleset.c |  89 ++++-----
>  security/landlock/ruleset.h |  21 ++-
>  9 files changed, 274 insertions(+), 341 deletions(-)
> 
> diff --git a/security/landlock/access.h b/security/landlock/access.h
> index bab403470a6c..f0a9afeb4a2a 100644
> --- a/security/landlock/access.h
> +++ b/security/landlock/access.h
> @@ -61,14 +61,15 @@ union access_masks_all {
>  static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
>  	      sizeof(typeof_member(union access_masks_all, all)));
>  
> -typedef u16 layer_mask_t;
> -
> -/* Makes sure all layers can be checked. */
> -static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
> -
>  /*
> - * Tracks domains responsible of a denied access.  This is required to avoid
> - * storing in each object the full layer_masks[] required by update_request().
> + * Tracks domains responsible of a denied access.  This avoids storing in each
> + * object the full matrix of per-layer unfulfilled access rights, which is
> + * required by update_request().
> + *
> + * Each nibble represents the layer index of the newest layer which denied a
> + * certain access right.  For file system access rights, the upper four bits are
> + * the index of the layer which denies LANDLOCK_ACCESS_FS_IOCTL_DEV and the
> + * lower nibble represents LANDLOCK_ACCESS_FS_TRUNCATE.
>   */
>  typedef u8 deny_masks_t;
>  

> diff --git a/security/landlock/domain.c b/security/landlock/domain.c
> index a647b68e8d06..d2a4354feeb4 100644
> --- a/security/landlock/domain.c
> +++ b/security/landlock/domain.c
> @@ -7,6 +7,7 @@
>   * Copyright © 2024-2025 Microsoft Corporation
>   */
>  
> +#include "ruleset.h"

To avoid this new include (which makes a dependency on ruleset
definitions for domain-specific definitions), I moved the struct
layer_access_masks definition to access.h, which makes more sense
anyway.


>  #include <kunit/test.h>
>  #include <linux/bitops.h>
>  #include <linux/bits.h>

> diff --git a/security/landlock/domain.h b/security/landlock/domain.h
> index 621f054c9a2b..227066d667f7 100644
> --- a/security/landlock/domain.h
> +++ b/security/landlock/domain.h
> @@ -10,6 +10,7 @@
>  #ifndef _SECURITY_LANDLOCK_DOMAIN_H
>  #define _SECURITY_LANDLOCK_DOMAIN_H
>  
> +#include "ruleset.h"
>  #include <linux/limits.h>
>  #include <linux/mm.h>
>  #include <linux/path.h>

> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 1a78cba662b2..1ceb5fd674c9 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -301,15 +301,28 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
>  	return ruleset->access_masks[layer_level].scope;
>  }
>  
> +/**
> + * struct layer_access_masks - A boolean matrix of layers and access rights
> + *
> + * This has a bit for each combination of layer numbers and access rights.
> + * During access checks, it is used to represent the access rights for each
> + * layer which still need to be fulfilled.  When all bits are 0, the access
> + * request is considered to be fulfilled.
> + */
> +struct layer_access_masks {
> +	/**
> +	 * @access: The unfulfilled access rights for each layer.
> +	 */
> +	access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
> +};
> +
>  bool landlock_unmask_layers(const struct landlock_rule *const rule,
> -			    const access_mask_t access_request,
> -			    layer_mask_t (*const layer_masks)[],
> -			    const size_t masks_array_size);
> +			    struct layer_access_masks *masks);
>  
>  access_mask_t
>  landlock_init_layer_masks(const struct landlock_ruleset *const domain,
>  			  const access_mask_t access_request,
> -			  layer_mask_t (*const layer_masks)[],
> +			  struct layer_access_masks *masks,
>  			  const enum landlock_key_type key_type);
>  
>  #endif /* _SECURITY_LANDLOCK_RULESET_H */
> -- 
> 2.52.0
> 
> 



More information about the Linux-security-module-archive mailing list