[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