linux/security/landlock/access.h
Günther Noack 65b691f84d
landlock: Transpose the layer masks data structure
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@gmail.com>
Link: https://lore.kernel.org/r/20260206151154.97915-5-gnoack3000@gmail.com
[mic: Cosmetic fixes, moved struct layer_access_masks definition]
Signed-off-by: Mickaël Salaün <mic@digikod.net>
2026-02-10 16:46:50 +01:00

123 lines
3.9 KiB
C

/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Landlock - Access types and helpers
*
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2018-2020 ANSSI
* Copyright © 2024-2025 Microsoft Corporation
*/
#ifndef _SECURITY_LANDLOCK_ACCESS_H
#define _SECURITY_LANDLOCK_ACCESS_H
#include <linux/bitops.h>
#include <linux/build_bug.h>
#include <linux/kernel.h>
#include <uapi/linux/landlock.h>
#include "limits.h"
/*
* All access rights that are denied by default whether they are handled or not
* by a ruleset/layer. This must be ORed with all ruleset->access_masks[]
* entries when we need to get the absolute handled access masks, see
* landlock_upgrade_handled_access_masks().
*/
/* clang-format off */
#define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
LANDLOCK_ACCESS_FS_REFER)
/* clang-format on */
/* clang-format off */
#define _LANDLOCK_ACCESS_FS_OPTIONAL ( \
LANDLOCK_ACCESS_FS_TRUNCATE | \
LANDLOCK_ACCESS_FS_IOCTL_DEV)
/* clang-format on */
typedef u16 access_mask_t;
/* Makes sure all filesystem access rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
/* Makes sure all network access rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
/* Makes sure all scoped rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
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_all {
struct access_masks masks;
u32 all;
};
/* Makes sure all fields are covered. */
static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
sizeof(typeof_member(union access_masks_all, all)));
/**
* 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];
};
/*
* 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;
/*
* Makes sure all optional access rights can be tied to a layer index (cf.
* get_deny_mask).
*/
static_assert(BITS_PER_TYPE(deny_masks_t) >=
(HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1) *
HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL)));
/* LANDLOCK_MAX_NUM_LAYERS must be a power of two (cf. deny_masks_t assert). */
static_assert(HWEIGHT(LANDLOCK_MAX_NUM_LAYERS) == 1);
/* Upgrades with all initially denied by default access rights. */
static inline struct access_masks
landlock_upgrade_handled_access_masks(struct access_masks access_masks)
{
/*
* All access rights that are denied by default whether they are
* explicitly handled or not.
*/
if (access_masks.fs)
access_masks.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
return access_masks;
}
/* Checks the subset relation between access masks. */
static inline bool access_mask_subset(access_mask_t subset,
access_mask_t superset)
{
return (subset | superset) == superset;
}
#endif /* _SECURITY_LANDLOCK_ACCESS_H */