mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
Merge landlock_find_rule() into landlock_unmask_layers() so rule pointers stay inside the domain implementation while unmask checking gets the matched rule it needs for the check_rule tracepoint. landlock_unmask_layers() now takes a landlock_id and the domain instead of a rule pointer. A rename or link evaluates the same dentry against both renamed parents, so this path now looks the rule up once per parent; collapsing that back to a single lookup is left to a follow-up. Emit, via the per-type wrappers unmask_layers_fs() and unmask_layers_net(), the rights each matching rule grants at every domain layer. The events carry this as a dynamic per-layer array (up to LANDLOCK_MAX_NUM_LAYERS entries) reserved from the trace ring buffer, not the caller's stack, and rendered symbolically per layer. A WARN_ON_ONCE() in __trace_landlock_fill_layers() flags a rule whose layer levels fall outside the domain range or are unsorted, a cannot-happen case; the zero-filled slots keep the rendered output and the array bounds safe regardless. Setting allowed_parent2 to true for non-dom-check requests when get_inode_id() returns false preserves the pre-refactoring behavior: a negative dentry (no backing inode) has no matching rule, so the access is allowed at this path component. Before the refactoring, landlock_unmask_layers() with a NULL rule produced this result as a side effect; now the caller must set it explicitly. Name the trace-only check_rule fields so each printk label equals its ring-buffer field name and works directly as an ftrace filter: the request field is labelled access_request= and the per-layer array is named grants. Values audit also logs keep audit's label (domain=, ruleset=) so a single filter works across trace and audit. Cc: Günther Noack <gnoack@google.com> Cc: Justin Suess <utilityemal77@gmail.com> Cc: Masami Hiramatsu <mhiramat@kernel.org> Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Tingmao Wang <m@maowtm.org> Link: https://patch.msgid.link/20260811094338.288094-12-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
327 lines
9.4 KiB
C
327 lines
9.4 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/*
|
|
* Landlock - Domain management
|
|
*
|
|
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
|
|
* Copyright © 2018-2020 ANSSI
|
|
* Copyright © 2024-2025 Microsoft Corporation
|
|
* Copyright © 2026 Cloudflare, Inc.
|
|
*/
|
|
|
|
#ifndef _SECURITY_LANDLOCK_DOMAIN_H
|
|
#define _SECURITY_LANDLOCK_DOMAIN_H
|
|
|
|
#include <linux/cleanup.h>
|
|
#include <linux/limits.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/path.h>
|
|
#include <linux/pid.h>
|
|
#include <linux/refcount.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#include "access.h"
|
|
#include "log.h"
|
|
#include "ruleset.h"
|
|
|
|
enum landlock_log_status {
|
|
/*
|
|
* Hierarchy whose creation event has not been emitted, so it is not yet
|
|
* observable from user space. A hierarchy is born in this state (the
|
|
* zero value, so a partially initialized hierarchy defaults to "not
|
|
* observable") and leaves it when landlock_restrict_self() emits its
|
|
* creation event, right after the merge and before the thread-sync
|
|
* wait. No trace free_domain event (and no audit deallocation record)
|
|
* fires while a hierarchy is in this state, so a hierarchy that never
|
|
* became observable (e.g. its initialization failed) is freed silently.
|
|
* A domain aborted by a thread-sync failure already emitted its
|
|
* creation event, so it is no longer UNCOMMITTED and does fire
|
|
* free_domain.
|
|
*/
|
|
LANDLOCK_LOG_UNCOMMITTED = 0,
|
|
LANDLOCK_LOG_PENDING,
|
|
LANDLOCK_LOG_RECORDED,
|
|
LANDLOCK_LOG_DISABLED,
|
|
};
|
|
|
|
/**
|
|
* struct landlock_details - Domain's creation information
|
|
*
|
|
* Rarely accessed, mainly when logging the first domain's denial.
|
|
*
|
|
* The contained pointers are initialized at the domain creation time and never
|
|
* changed again.
|
|
*/
|
|
struct landlock_details {
|
|
/**
|
|
* @pid: PID of the task that initially restricted itself. It still
|
|
* identifies the same task. Keeping a reference to this PID ensures that
|
|
* it will not be recycled.
|
|
*/
|
|
struct pid *pid;
|
|
/**
|
|
* @uid: UID of the task that initially restricted itself, at creation time.
|
|
*/
|
|
uid_t uid;
|
|
/**
|
|
* @comm: Command line of the task that initially restricted itself, at
|
|
* creation time. Always NULL terminated.
|
|
*/
|
|
char comm[TASK_COMM_LEN];
|
|
/**
|
|
* @exe_path: Executable path of the task that initially restricted
|
|
* itself, at creation time. Always NULL terminated, and never greater
|
|
* than LANDLOCK_PATH_MAX_SIZE.
|
|
*/
|
|
char exe_path[];
|
|
};
|
|
|
|
/* Adds 11 extra characters for the potential " (deleted)" suffix. */
|
|
#define LANDLOCK_PATH_MAX_SIZE (PATH_MAX + 11)
|
|
|
|
/* Makes sure the greatest landlock_details can be allocated. */
|
|
static_assert(struct_size_t(struct landlock_details, exe_path,
|
|
LANDLOCK_PATH_MAX_SIZE) <= KMALLOC_MAX_SIZE);
|
|
|
|
/**
|
|
* struct landlock_hierarchy - Node in a domain hierarchy
|
|
*/
|
|
struct landlock_hierarchy {
|
|
/**
|
|
* @parent: Pointer to the parent node, or NULL if it is a root
|
|
* Landlock domain.
|
|
*/
|
|
struct landlock_hierarchy *parent;
|
|
/**
|
|
* @usage: Number of potential children domains plus their parent
|
|
* domain.
|
|
*/
|
|
refcount_t usage;
|
|
|
|
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
|
/**
|
|
* @log_status: Whether this domain should be logged or not. Because
|
|
* concurrent log entries may be created at the same time, it is still
|
|
* possible to have several domain records of the same domain.
|
|
*/
|
|
enum landlock_log_status log_status;
|
|
/**
|
|
* @num_denials: Number of access requests denied by this domain.
|
|
* Masked (i.e. never logged) denials are still counted.
|
|
*/
|
|
atomic64_t num_denials;
|
|
/**
|
|
* @id: Landlock domain ID, set once at domain creation time.
|
|
*/
|
|
u64 id;
|
|
/**
|
|
* @details: Information about the related domain.
|
|
*/
|
|
const struct landlock_details *details;
|
|
/**
|
|
* @log_same_exec: Set if the domain is *not* configured with
|
|
* %LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF. Set to true by default.
|
|
*/
|
|
u32 log_same_exec : 1,
|
|
/**
|
|
* @log_new_exec: Set if the domain is configured with
|
|
* %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON. Set to false by default.
|
|
*/
|
|
log_new_exec : 1;
|
|
/**
|
|
* @quiet_masks: Bitmasks of access that should be quieted (i.e. not
|
|
* logged) if the related object is marked as quiet.
|
|
*/
|
|
struct access_masks quiet_masks;
|
|
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
|
};
|
|
|
|
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
|
|
|
deny_masks_t
|
|
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
|
const access_mask_t optional_access,
|
|
const struct layer_masks *const masks);
|
|
|
|
optional_access_t landlock_get_quiet_optional_accesses(
|
|
const access_mask_t all_existing_optional_access,
|
|
const deny_masks_t deny_masks, const struct layer_masks *const masks);
|
|
|
|
int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy);
|
|
|
|
static inline void
|
|
landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy)
|
|
{
|
|
if (!hierarchy || !hierarchy->details)
|
|
return;
|
|
|
|
put_pid(hierarchy->details->pid);
|
|
kfree(hierarchy->details);
|
|
}
|
|
|
|
#else /* CONFIG_SECURITY_LANDLOCK_LOG */
|
|
|
|
static inline int
|
|
landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void
|
|
landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy)
|
|
{
|
|
}
|
|
|
|
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
|
|
|
static inline void
|
|
landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy)
|
|
{
|
|
if (hierarchy)
|
|
refcount_inc(&hierarchy->usage);
|
|
}
|
|
|
|
static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
|
|
{
|
|
while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) {
|
|
const struct landlock_hierarchy *const freeme = hierarchy;
|
|
|
|
landlock_log_free_domain(hierarchy);
|
|
landlock_free_hierarchy_details(hierarchy);
|
|
hierarchy = hierarchy->parent;
|
|
kfree(freeme);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* struct landlock_domain - Immutable Landlock domain
|
|
*
|
|
* A domain is created from a ruleset by landlock_merge_ruleset() and enforced
|
|
* on a task. Once created, its rules and access masks are immutable. Unlike
|
|
* &struct landlock_ruleset, a domain has no lock field.
|
|
*/
|
|
struct landlock_domain {
|
|
/**
|
|
* @rules: Red-black tree storage for rules.
|
|
*/
|
|
struct landlock_rules rules;
|
|
/**
|
|
* @hierarchy: Enables hierarchy identification even when a parent
|
|
* domain vanishes. This is needed for the ptrace and scope
|
|
* restrictions.
|
|
*/
|
|
struct landlock_hierarchy *hierarchy;
|
|
union {
|
|
/**
|
|
* @work_free: Enables to free a domain within a lockless
|
|
* section. This is only used by landlock_put_domain_deferred()
|
|
* when @usage reaches zero. The fields @usage, @num_layers and
|
|
* @handled_masks are then unused.
|
|
*/
|
|
struct work_struct work_free;
|
|
struct {
|
|
/**
|
|
* @usage: Number of credentials referencing this
|
|
* domain.
|
|
*/
|
|
refcount_t usage;
|
|
/**
|
|
* @num_layers: Number of layers that are used in this
|
|
* domain. This enables to check that all the layers
|
|
* allow an access request.
|
|
*/
|
|
u32 num_layers;
|
|
/**
|
|
* @handled_masks: Contains the subset of filesystem and
|
|
* network actions that are restricted by a domain. A
|
|
* domain saves all layers of merged rulesets in a stack
|
|
* (FAM), starting from the first layer to the last one.
|
|
* These layers are used when merging rulesets, for user
|
|
* space backward compatibility (i.e. future-proof), and
|
|
* to properly handle merged rulesets without
|
|
* overlapping access rights. These layers are set once
|
|
* and never changed for the lifetime of the domain.
|
|
*/
|
|
struct access_masks handled_masks[];
|
|
};
|
|
};
|
|
};
|
|
|
|
static inline access_mask_t
|
|
landlock_get_fs_access_mask(const struct landlock_domain *const domain,
|
|
const u16 layer_level)
|
|
{
|
|
/* Handles all initially denied by default access rights. */
|
|
return domain->handled_masks[layer_level].fs |
|
|
_LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
|
|
}
|
|
|
|
static inline access_mask_t
|
|
landlock_get_net_access_mask(const struct landlock_domain *const domain,
|
|
const u16 layer_level)
|
|
{
|
|
return domain->handled_masks[layer_level].net;
|
|
}
|
|
|
|
static inline access_mask_t
|
|
landlock_get_scope_mask(const struct landlock_domain *const domain,
|
|
const u16 layer_level)
|
|
{
|
|
return domain->handled_masks[layer_level].scope;
|
|
}
|
|
|
|
/**
|
|
* landlock_union_access_masks - Return all access rights handled in the
|
|
* domain
|
|
*
|
|
* @domain: Landlock 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_domain *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->handled_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))
|
|
|
|
struct landlock_domain *
|
|
landlock_merge_ruleset(struct landlock_domain *const parent,
|
|
struct landlock_ruleset *const ruleset);
|
|
|
|
bool landlock_unmask_layers(const struct landlock_domain *const domain,
|
|
const struct landlock_id id,
|
|
struct layer_masks *masks,
|
|
const struct landlock_rule **matched_rule);
|
|
|
|
access_mask_t
|
|
landlock_init_layer_masks(const struct landlock_domain *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)
|
|
refcount_inc(&domain->usage);
|
|
}
|
|
|
|
#endif /* _SECURITY_LANDLOCK_DOMAIN_H */
|