landlock: Move domain query functions to domain.c

Grouping domain-specific code in one compilation unit reduces coupling
between domain and ruleset implementations.

Move the access-check functions that only operate on a domain (rule
lookup, layer unmasking, layer-mask init, access-mask union) from
ruleset.[ch] to domain.[ch].  They evaluate whether a domain grants a
requested access during the pathwalk and network checks and do not
modify the domain.

The merge and inherit chain stays in ruleset.c for now because it calls
the static create_ruleset() allocator; a following commit moves it once
the domain type switch eliminates that dependency.

No behavioral change.  The functions move with unchanged signatures and
bodies.

Cc: Günther Noack <gnoack@google.com>
Cc: Tingmao Wang <m@maowtm.org>
Link: https://patch.msgid.link/20260811094338.288094-3-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
Mickaël Salaün
2026-08-11 11:43:16 +02:00
parent e76ef456bb
commit 2bbba0905a
5 changed files with 201 additions and 188 deletions

View File

@@ -11,11 +11,15 @@
#include <kunit/test.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/cred.h>
#include <linux/err.h>
#include <linux/file.h>
#include <linux/mm.h>
#include <linux/overflow.h>
#include <linux/path.h>
#include <linux/pid.h>
#include <linux/rbtree.h>
#include <linux/refcount.h>
#include <linux/sched.h>
#include <linux/signal.h>
@@ -27,6 +31,7 @@
#include "common.h"
#include "domain.h"
#include "id.h"
#include "limits.h"
#include "ruleset.h"
static void free_domain(struct landlock_domain *const domain)
@@ -60,6 +65,163 @@ void landlock_put_domain_deferred(struct landlock_domain *const domain)
}
}
/* The returned access has the same lifetime as the domain. */
const struct landlock_rule *
landlock_find_rule(const struct landlock_ruleset *const ruleset,
const struct landlock_id id)
{
const struct rb_root *root;
const struct rb_node *node;
root = landlock_get_rule_root((struct landlock_rules *)&ruleset->rules,
id.type);
if (IS_ERR(root))
return NULL;
node = root->rb_node;
while (node) {
struct landlock_rule *this =
rb_entry(node, struct landlock_rule, node);
if (this->key.data == id.key.data)
return this;
if (this->key.data < id.key.data)
node = node->rb_right;
else
node = node->rb_left;
}
return NULL;
}
/**
* landlock_unmask_layers - Remove the access rights in @masks which are
* granted in @rule
*
* Updates the set of (per-layer) unfulfilled access rights @masks so that all
* the access rights granted in @rule are removed from it (because they are now
* fulfilled).
*
* @rule: A rule that grants a set of access rights for each layer.
* @masks: A matrix of unfulfilled access rights for each layer.
*
* Return: True if the request is allowed (i.e. the access rights granted all
* remaining unfulfilled access rights and masks has no leftover set bits).
*/
bool landlock_unmask_layers(const struct landlock_rule *const rule,
struct layer_masks *masks)
{
if (!masks)
return true;
if (!rule)
return false;
/*
* An access is granted if, for each policy layer, at least one rule
* encountered on the pathwalk grants the requested access, regardless
* of its position in the layer stack. We must then check the remaining
* layers for each inode, from the first added layer to the last one.
* When there are multiple requested accesses, for each policy layer,
* the full set of requested accesses may not be granted by only one
* rule, but by the union (binary OR) of multiple rules. For example,
* /a/b <execute> + /a <read> grants /a/b <execute + read>.
*
* This function is called once per matching rule during the pathwalk,
* progressively clearing bits in @masks. The overall access decision
* is per-layer: access is granted iff masks->layers[l].access == 0 for
* all layers l. When two independent mechanisms can each grant access
* within a layer (e.g. a path rule OR a scope exception), the
* composition must evaluate per-layer: FOR-ALL l (A(l) OR B(l)), not
* (FOR-ALL l A(l)) OR (FOR-ALL l B(l)), to prevent bypass when
* different layers grant via different mechanisms.
*/
for (size_t i = 0; i < rule->num_layers; i++) {
const struct landlock_layer *const layer = &rule->layers[i];
/* Clear the bits where the layer in the rule grants access. */
masks->layers[layer->level - 1].access &= ~layer->access;
#ifdef CONFIG_AUDIT
/* Collect rule flags for each layer. */
if (layer->flags.quiet)
masks->layers[layer->level - 1].quiet = true;
#endif /* CONFIG_AUDIT */
}
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
if (masks->layers[i].access)
return false;
}
return true;
}
typedef access_mask_t
get_access_mask_t(const struct landlock_ruleset *const ruleset,
const u16 layer_level);
/**
* landlock_init_layer_masks - Initialize layer masks from an access request
*
* Populates @masks such that for each access right in @access_request, the bits
* for all the layers are set where this access right is handled. Rule flags
* are also zeroed.
*
* @domain: The domain that defines the current restrictions.
* @access_request: The requested access rights to check.
* @masks: Layer access masks to populate.
* @key_type: The key type to switch between access masks of different types.
*
* Return: An access mask where each access right bit is set which is handled in
* any of the active layers in @domain.
*/
access_mask_t
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
const access_mask_t access_request,
struct layer_masks *const masks,
const enum landlock_key_type key_type)
{
access_mask_t handled_accesses = 0;
get_access_mask_t *get_access_mask;
switch (key_type) {
case LANDLOCK_KEY_INODE:
get_access_mask = landlock_get_fs_access_mask;
break;
#if IS_ENABLED(CONFIG_INET)
case LANDLOCK_KEY_NET_PORT:
get_access_mask = landlock_get_net_access_mask;
break;
#endif /* IS_ENABLED(CONFIG_INET) */
default:
WARN_ON_ONCE(1);
return 0;
}
/* An empty access request can happen because of O_WRONLY | O_RDWR. */
if (!access_request)
return 0;
for (size_t i = 0; i < domain->num_layers; i++) {
const access_mask_t handled = get_access_mask(domain, i);
masks->layers[i].access = access_request & handled;
handled_accesses |= masks->layers[i].access;
#ifdef CONFIG_AUDIT
masks->layers[i].quiet = false;
#endif /* CONFIG_AUDIT */
}
for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
i++) {
masks->layers[i].access = 0;
#ifdef CONFIG_AUDIT
masks->layers[i].quiet = false;
#endif /* CONFIG_AUDIT */
}
return handled_accesses;
}
#ifdef CONFIG_AUDIT
/**

View File

@@ -234,12 +234,50 @@ struct landlock_domain {
};
};
/**
* landlock_union_access_masks - Return all access rights handled in the
* domain
*
* @domain: Landlock ruleset (used as a domain)
*
* Return: An access_masks result of the OR of all the domain's access masks.
*/
static inline struct access_masks
landlock_union_access_masks(const struct landlock_ruleset *const domain)
{
union access_masks_all matches = {};
size_t layer_level;
for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
union access_masks_all layer = {
.masks = domain->access_masks[layer_level],
};
matches.all |= layer.all;
}
return matches.masks;
}
void landlock_put_domain(struct landlock_domain *const domain);
void landlock_put_domain_deferred(struct landlock_domain *const domain);
DEFINE_FREE(landlock_put_domain, struct landlock_domain *,
if (!IS_ERR_OR_NULL(_T)) landlock_put_domain(_T))
const struct landlock_rule *
landlock_find_rule(const struct landlock_ruleset *const ruleset,
const struct landlock_id id);
bool landlock_unmask_layers(const struct landlock_rule *const rule,
struct layer_masks *masks);
access_mask_t
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
const access_mask_t access_request,
struct layer_masks *masks,
const enum landlock_key_type key_type);
static inline void landlock_get_domain(struct landlock_domain *const domain)
{
if (domain)

View File

@@ -15,6 +15,7 @@
#include "audit.h"
#include "common.h"
#include "cred.h"
#include "domain.h"
#include "limits.h"
#include "net.h"
#include "ruleset.h"

View File

@@ -591,153 +591,3 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
return no_free_ptr(new_dom);
}
/*
* The returned access has the same lifetime as @ruleset.
*/
const struct landlock_rule *
landlock_find_rule(const struct landlock_ruleset *const ruleset,
const struct landlock_id id)
{
const struct rb_root *root;
const struct rb_node *node;
root = landlock_get_rule_root((struct landlock_rules *)&ruleset->rules,
id.type);
if (IS_ERR(root))
return NULL;
node = root->rb_node;
while (node) {
struct landlock_rule *this =
rb_entry(node, struct landlock_rule, node);
if (this->key.data == id.key.data)
return this;
if (this->key.data < id.key.data)
node = node->rb_right;
else
node = node->rb_left;
}
return NULL;
}
/**
* landlock_unmask_layers - Remove the access rights in @masks
* which are granted in @rule
*
* Updates the set of (per-layer) unfulfilled access rights @masks
* so that all the access rights granted in @rule are removed from it
* (because they are now fulfilled).
*
* @rule: A rule that grants a set of access rights for each layer
* @masks: A matrix of unfulfilled access rights for each layer
*
* Return: True if the request is allowed (i.e. the access rights granted all
* remaining unfulfilled access rights and masks has no leftover set bits).
*/
bool landlock_unmask_layers(const struct landlock_rule *const rule,
struct layer_masks *masks)
{
if (!masks)
return true;
if (!rule)
return false;
/*
* An access is granted if, for each policy layer, at least one rule
* encountered on the pathwalk grants the requested access,
* regardless of its position in the layer stack. We must then check
* the remaining layers for each inode, from the first added layer to
* the last one. When there is multiple requested accesses, for each
* policy layer, the full set of requested accesses may not be granted
* by only one rule, but by the union (binary OR) of multiple rules.
* E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
*/
for (size_t i = 0; i < rule->num_layers; i++) {
const struct landlock_layer *const layer = &rule->layers[i];
/* Clear the bits where the layer in the rule grants access. */
masks->layers[layer->level - 1].access &= ~layer->access;
#ifdef CONFIG_AUDIT
/* Collect rule flags for each layer. */
if (layer->flags.quiet)
masks->layers[layer->level - 1].quiet = true;
#endif /* CONFIG_AUDIT */
}
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
if (masks->layers[i].access)
return false;
}
return true;
}
typedef access_mask_t
get_access_mask_t(const struct landlock_ruleset *const ruleset,
const u16 layer_level);
/**
* landlock_init_layer_masks - Initialize layer masks from an access request
*
* Populates @masks such that for each access right in @access_request, the bits
* for all the layers are set where this access right is handled. Rule flags
* are also zeroed.
*
* @domain: The domain that defines the current restrictions.
* @access_request: The requested access rights to check.
* @masks: Layer access masks to populate.
* @key_type: The key type to switch between access masks of different types.
*
* Return: An access mask where each access right bit is set which is handled
* in any of the active layers in @domain.
*/
access_mask_t
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
const access_mask_t access_request,
struct layer_masks *const masks,
const enum landlock_key_type key_type)
{
access_mask_t handled_accesses = 0;
get_access_mask_t *get_access_mask;
switch (key_type) {
case LANDLOCK_KEY_INODE:
get_access_mask = landlock_get_fs_access_mask;
break;
#if IS_ENABLED(CONFIG_INET)
case LANDLOCK_KEY_NET_PORT:
get_access_mask = landlock_get_net_access_mask;
break;
#endif /* IS_ENABLED(CONFIG_INET) */
default:
WARN_ON_ONCE(1);
return 0;
}
/* An empty access request can happen because of O_WRONLY | O_RDWR. */
if (!access_request)
return 0;
for (size_t i = 0; i < domain->num_layers; i++) {
const access_mask_t handled = get_access_mask(domain, i);
masks->layers[i].access = access_request & handled;
handled_accesses |= masks->layers[i].access;
#ifdef CONFIG_AUDIT
masks->layers[i].quiet = false;
#endif /* CONFIG_AUDIT */
}
for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
i++) {
masks->layers[i].access = 0;
#ifdef CONFIG_AUDIT
masks->layers[i].quiet = false;
#endif /* CONFIG_AUDIT */
}
return handled_accesses;
}

View File

@@ -235,10 +235,6 @@ struct landlock_ruleset *
landlock_merge_ruleset(struct landlock_ruleset *const parent,
struct landlock_ruleset *const ruleset);
const struct landlock_rule *
landlock_find_rule(const struct landlock_ruleset *const ruleset,
const struct landlock_id id);
/**
* landlock_get_rule_root - Get the root of a rule tree by key type
*
@@ -272,31 +268,6 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
refcount_inc(&ruleset->usage);
}
/**
* landlock_union_access_masks - Return all access rights handled in the
* domain
*
* @domain: Landlock ruleset (used as a domain)
*
* Return: An access_masks result of the OR of all the domain's access masks.
*/
static inline struct access_masks
landlock_union_access_masks(const struct landlock_ruleset *const domain)
{
union access_masks_all matches = {};
size_t layer_level;
for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
union access_masks_all layer = {
.masks = domain->access_masks[layer_level],
};
matches.all |= layer.all;
}
return matches.masks;
}
static inline void
landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
const access_mask_t fs_access_mask,
@@ -355,13 +326,4 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
return ruleset->access_masks[layer_level].scope;
}
bool landlock_unmask_layers(const struct landlock_rule *const rule,
struct layer_masks *masks);
access_mask_t
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
const access_mask_t access_request,
struct layer_masks *masks,
const enum landlock_key_type key_type);
#endif /* _SECURITY_LANDLOCK_RULESET_H */