mirror of
https://github.com/torvalds/linux.git
synced 2026-09-27 11:02:03 +02:00
landlock: Add tracepoints for rule checking
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>
This commit is contained in:
parent
132d84b16b
commit
3f1f106e4c
|
|
@ -16,8 +16,10 @@
|
||||||
#include <linux/tracepoint.h>
|
#include <linux/tracepoint.h>
|
||||||
#include <linux/trace_seq.h>
|
#include <linux/trace_seq.h>
|
||||||
|
|
||||||
|
struct dentry;
|
||||||
struct landlock_domain;
|
struct landlock_domain;
|
||||||
struct landlock_hierarchy;
|
struct landlock_hierarchy;
|
||||||
|
struct landlock_rule;
|
||||||
struct landlock_ruleset;
|
struct landlock_ruleset;
|
||||||
struct path;
|
struct path;
|
||||||
|
|
||||||
|
|
@ -63,6 +65,77 @@ __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fills the dense per-domain-layer array layers (one access mask per layer,
|
||||||
|
* indexed by level - 1) from rule's sparse layer stack, keeping only the
|
||||||
|
* requested rights (access_request). Layers with no matching rule entry get
|
||||||
|
* a zero mask. Shared by the check_rule_fs and check_rule_net events.
|
||||||
|
*
|
||||||
|
* rule->layers is sorted by ascending level, with levels in the domain's
|
||||||
|
* [1, num_layers] range (see landlock_merge_ruleset()), so every entry maps
|
||||||
|
* to a slot. A leftover entry would be a malformed rule; the zero-filled
|
||||||
|
* slots keep the output and the array bounds safe regardless.
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
__trace_landlock_fill_layers(access_mask_t *const layers,
|
||||||
|
const size_t num_layers,
|
||||||
|
const struct landlock_rule *const rule,
|
||||||
|
const access_mask_t access_request)
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
for (size_t level = 1; level <= num_layers; level++) {
|
||||||
|
access_mask_t grants = 0;
|
||||||
|
|
||||||
|
if (i < rule->num_layers && level == rule->layers[i].level) {
|
||||||
|
grants = rule->layers[i].access & access_request;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
layers[level - 1] = grants;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A leftover entry means an out-of-range or unsorted rule level. */
|
||||||
|
WARN_ON_ONCE(i < rule->num_layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Renders the dense per-domain-layer access array as symbolic flag names for
|
||||||
|
* the grants field: layers wrapped in "{}", flags within a layer joined by
|
||||||
|
* "|", layers separated by ",", an empty layer rendered as nothing.
|
||||||
|
* Open-codes the flag walk because trace_print_flags_seq() NUL-terminates per
|
||||||
|
* call and so cannot be chained into a single field. The shared names table
|
||||||
|
* covers every access right, so masked bits are always named. Returns the
|
||||||
|
* trace_seq position like __print_flags().
|
||||||
|
*/
|
||||||
|
static inline const char *__trace_landlock_print_layers(
|
||||||
|
struct trace_seq *p, const access_mask_t *const layers,
|
||||||
|
const size_t num_layers, const struct trace_print_flags *const names,
|
||||||
|
const size_t names_size)
|
||||||
|
{
|
||||||
|
const char *const ret = trace_seq_buffer_ptr(p);
|
||||||
|
|
||||||
|
trace_seq_putc(p, '{');
|
||||||
|
for (size_t i = 0; i < num_layers; i++) {
|
||||||
|
access_mask_t mask = layers[i];
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
if (i)
|
||||||
|
trace_seq_putc(p, ',');
|
||||||
|
for (size_t j = 0; mask && j < names_size; j++) {
|
||||||
|
if ((mask & names[j].mask) != names[j].mask)
|
||||||
|
continue;
|
||||||
|
if (!first)
|
||||||
|
trace_seq_putc(p, '|');
|
||||||
|
trace_seq_puts(p, names[j].name);
|
||||||
|
mask &= ~names[j].mask;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace_seq_putc(p, '}');
|
||||||
|
trace_seq_putc(p, 0);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* CREATE_TRACE_POINTS */
|
#endif /* CREATE_TRACE_POINTS */
|
||||||
|
|
||||||
/* clang-format off */
|
/* clang-format off */
|
||||||
|
|
@ -123,8 +196,34 @@ __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len)
|
||||||
* (e.g. network ports are __u64 in host endianness, like
|
* (e.g. network ports are __u64 in host endianness, like
|
||||||
* landlock_net_port_attr.port). Per-event details, such as where a value
|
* landlock_net_port_attr.port). Per-event details, such as where a value
|
||||||
* is byte-swapped, live in the field's own kdoc.
|
* is byte-swapped, live in the field's own kdoc.
|
||||||
|
*
|
||||||
|
* Rule-check fields
|
||||||
|
* ~~~~~~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* The check_rule events fire during an access check, once per matching
|
||||||
|
* rule, before the final allow-or-deny verdict. They share domain (the
|
||||||
|
* enforcing domain being evaluated), access_request (the access mask being
|
||||||
|
* checked), and rule (the matching rule, with per-layer access masks).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prints a per-layer access mask array (the dynamic array @array) as symbolic
|
||||||
|
* flag names using the shared @flag_names list (a _LANDLOCK_*_NAMES macro).
|
||||||
|
* Stays outside CREATE_TRACE_POINTS: TP_printk is expanded in the print-output
|
||||||
|
* pass where that macro is undefined.
|
||||||
|
*/
|
||||||
|
#define __print_landlock_layers(array, flag_names...) \
|
||||||
|
({ \
|
||||||
|
static const struct trace_print_flags __layer_names[] = { \
|
||||||
|
flag_names \
|
||||||
|
}; \
|
||||||
|
__trace_landlock_print_layers( \
|
||||||
|
p, __get_dynamic_array(array), \
|
||||||
|
__get_dynamic_array_len(array) / \
|
||||||
|
sizeof(access_mask_t), \
|
||||||
|
__layer_names, ARRAY_SIZE(__layer_names)); \
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* landlock_create_ruleset - New ruleset created
|
* landlock_create_ruleset - New ruleset created
|
||||||
*
|
*
|
||||||
|
|
@ -432,6 +531,103 @@ TRACE_EVENT(landlock_free_domain,
|
||||||
__entry->domain_id, __entry->denials)
|
__entry->domain_id, __entry->denials)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* landlock_check_rule_fs - Filesystem rule evaluated during access check
|
||||||
|
*
|
||||||
|
* @domain: Enforcing domain (never NULL).
|
||||||
|
* @rule: Matching rule with per-layer access masks (never NULL).
|
||||||
|
* @access_request: Access mask evaluated against the rule (the domain's
|
||||||
|
* handled mask during rename/link double-checks).
|
||||||
|
* @dentry: Filesystem dentry being checked (never NULL).
|
||||||
|
*
|
||||||
|
* Emitted for each rule that matches during a filesystem access check.
|
||||||
|
* The grants array shows the requested rights the rule grants at each
|
||||||
|
* domain layer. See Documentation/trace/events-landlock.rst for how to
|
||||||
|
* interpret it.
|
||||||
|
*/
|
||||||
|
TRACE_EVENT(landlock_check_rule_fs,
|
||||||
|
|
||||||
|
TP_PROTO(const struct landlock_domain *domain,
|
||||||
|
const struct landlock_rule *rule,
|
||||||
|
access_mask_t access_request, const struct dentry *dentry),
|
||||||
|
|
||||||
|
TP_ARGS(domain, rule, access_request, dentry),
|
||||||
|
|
||||||
|
TP_STRUCT__entry(
|
||||||
|
__field( __u64, domain_id )
|
||||||
|
__field( access_mask_t, access_request )
|
||||||
|
__field( dev_t, dev )
|
||||||
|
__field( ino_t, ino )
|
||||||
|
__dynamic_array(access_mask_t, grants,
|
||||||
|
domain->num_layers)
|
||||||
|
),
|
||||||
|
|
||||||
|
TP_fast_assign(
|
||||||
|
__entry->domain_id = domain->hierarchy->id;
|
||||||
|
__entry->access_request = access_request;
|
||||||
|
__entry->dev = dentry->d_sb->s_dev;
|
||||||
|
__entry->ino = d_backing_inode(dentry)->i_ino;
|
||||||
|
|
||||||
|
__trace_landlock_fill_layers(__get_dynamic_array(grants),
|
||||||
|
__get_dynamic_array_len(grants) /
|
||||||
|
sizeof(access_mask_t),
|
||||||
|
rule, access_request);
|
||||||
|
),
|
||||||
|
|
||||||
|
TP_printk("domain=%llx access_request=%s dev=%u:%u ino=%lu grants=%s",
|
||||||
|
__entry->domain_id,
|
||||||
|
__print_flags(__entry->access_request, "|", _LANDLOCK_ACCESS_FS_NAMES),
|
||||||
|
MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino,
|
||||||
|
__print_landlock_layers(grants, _LANDLOCK_ACCESS_FS_NAMES))
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* landlock_check_rule_net - Network port rule evaluated during access check
|
||||||
|
*
|
||||||
|
* @domain: Enforcing domain (never NULL).
|
||||||
|
* @rule: Matching rule with per-layer access masks (never NULL).
|
||||||
|
* @access_request: Access mask being requested.
|
||||||
|
* @port: Network port being checked (host endianness).
|
||||||
|
*
|
||||||
|
* Emitted for each rule that matches during a network access check. The
|
||||||
|
* grants array shows the requested rights the rule grants at each domain
|
||||||
|
* layer. See Documentation/trace/events-landlock.rst for how to
|
||||||
|
* interpret it.
|
||||||
|
*/
|
||||||
|
TRACE_EVENT(landlock_check_rule_net,
|
||||||
|
|
||||||
|
TP_PROTO(const struct landlock_domain *domain,
|
||||||
|
const struct landlock_rule *rule,
|
||||||
|
access_mask_t access_request, __u64 port),
|
||||||
|
|
||||||
|
TP_ARGS(domain, rule, access_request, port),
|
||||||
|
|
||||||
|
TP_STRUCT__entry(
|
||||||
|
__field( __u64, domain_id )
|
||||||
|
__field( access_mask_t, access_request )
|
||||||
|
__field( __u64, port )
|
||||||
|
__dynamic_array(access_mask_t, grants,
|
||||||
|
domain->num_layers)
|
||||||
|
),
|
||||||
|
|
||||||
|
TP_fast_assign(
|
||||||
|
__entry->domain_id = domain->hierarchy->id;
|
||||||
|
__entry->access_request = access_request;
|
||||||
|
__entry->port = port;
|
||||||
|
|
||||||
|
__trace_landlock_fill_layers(__get_dynamic_array(grants),
|
||||||
|
__get_dynamic_array_len(grants) /
|
||||||
|
sizeof(access_mask_t),
|
||||||
|
rule, access_request);
|
||||||
|
),
|
||||||
|
|
||||||
|
TP_printk("domain=%llx access_request=%s port=%llu grants=%s",
|
||||||
|
__entry->domain_id,
|
||||||
|
__print_flags(__entry->access_request, "|", _LANDLOCK_ACCESS_NET_NAMES),
|
||||||
|
__entry->port,
|
||||||
|
__print_landlock_layers(grants, _LANDLOCK_ACCESS_NET_NAMES))
|
||||||
|
);
|
||||||
|
|
||||||
#undef _LANDLOCK_NAME_ENTRY
|
#undef _LANDLOCK_NAME_ENTRY
|
||||||
|
|
||||||
#endif /* _TRACE_LANDLOCK_H */
|
#endif /* _TRACE_LANDLOCK_H */
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,9 @@ void landlock_put_domain_deferred(struct landlock_domain *const domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The returned access has the same lifetime as the domain. */
|
/* The returned access has the same lifetime as the domain. */
|
||||||
const struct landlock_rule *
|
static const struct landlock_rule *
|
||||||
landlock_find_rule(const struct landlock_domain *const domain,
|
find_rule(const struct landlock_domain *const domain,
|
||||||
const struct landlock_id id)
|
const struct landlock_id id)
|
||||||
{
|
{
|
||||||
const struct rb_root *root;
|
const struct rb_root *root;
|
||||||
const struct rb_node *node;
|
const struct rb_node *node;
|
||||||
|
|
@ -127,26 +127,38 @@ landlock_find_rule(const struct landlock_domain *const domain,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* landlock_unmask_layers - Remove the access rights in @masks which are
|
* landlock_unmask_layers - Remove the access rights in @masks which are
|
||||||
* granted in @rule
|
* granted by a matching rule
|
||||||
*
|
*
|
||||||
* Updates the set of (per-layer) unfulfilled access rights @masks so that all
|
* Looks up the rule matching @id in @domain, then updates the set of
|
||||||
* the access rights granted in @rule are removed from it (because they are now
|
* (per-layer) unfulfilled access rights @masks so that all the access rights
|
||||||
* fulfilled).
|
* granted by that rule are removed (because they are now fulfilled).
|
||||||
*
|
*
|
||||||
* @rule: A rule that grants a set of access rights for each layer.
|
* @domain: The Landlock domain to search for a matching rule.
|
||||||
|
* @id: Identifier for the rule target (e.g. inode, port).
|
||||||
* @masks: A matrix of unfulfilled access rights for each layer.
|
* @masks: A matrix of unfulfilled access rights for each layer.
|
||||||
|
* @matched_rule: Optional output for the matched rule (for tracing); set to
|
||||||
|
* the matching rule when non-NULL, unchanged otherwise.
|
||||||
*
|
*
|
||||||
* Return: True if the request is allowed (i.e. the access rights granted all
|
* 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).
|
* remaining unfulfilled access rights and masks has no leftover set bits).
|
||||||
*/
|
*/
|
||||||
bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
bool landlock_unmask_layers(const struct landlock_domain *const domain,
|
||||||
struct layer_masks *masks)
|
const struct landlock_id id,
|
||||||
|
struct layer_masks *masks,
|
||||||
|
const struct landlock_rule **matched_rule)
|
||||||
{
|
{
|
||||||
|
const struct landlock_rule *rule;
|
||||||
|
|
||||||
if (!masks)
|
if (!masks)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
rule = find_rule(domain, id);
|
||||||
if (!rule)
|
if (!rule)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (matched_rule)
|
||||||
|
*matched_rule = rule;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* An access is granted if, for each policy layer, at least one rule
|
* An access is granted if, for each policy layer, at least one rule
|
||||||
* encountered on the pathwalk grants the requested access, regardless
|
* encountered on the pathwalk grants the requested access, regardless
|
||||||
|
|
|
||||||
|
|
@ -306,12 +306,10 @@ struct landlock_domain *
|
||||||
landlock_merge_ruleset(struct landlock_domain *const parent,
|
landlock_merge_ruleset(struct landlock_domain *const parent,
|
||||||
struct landlock_ruleset *const ruleset);
|
struct landlock_ruleset *const ruleset);
|
||||||
|
|
||||||
const struct landlock_rule *
|
bool landlock_unmask_layers(const struct landlock_domain *const domain,
|
||||||
landlock_find_rule(const struct landlock_domain *const domain,
|
const struct landlock_id id,
|
||||||
const struct landlock_id id);
|
struct layer_masks *masks,
|
||||||
|
const struct landlock_rule **matched_rule);
|
||||||
bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
|
||||||
struct layer_masks *masks);
|
|
||||||
|
|
||||||
access_mask_t
|
access_mask_t
|
||||||
landlock_init_layer_masks(const struct landlock_domain *const domain,
|
landlock_init_layer_masks(const struct landlock_domain *const domain,
|
||||||
|
|
|
||||||
|
|
@ -377,31 +377,55 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
|
||||||
|
|
||||||
/* Access-control management */
|
/* Access-control management */
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The lifetime of the returned rule is tied to @domain.
|
* get_inode_id - Look up the Landlock object for a dentry
|
||||||
|
* @dentry: The dentry to look up.
|
||||||
|
* @id: Filled with the inode's Landlock object pointer on success.
|
||||||
*
|
*
|
||||||
* Returns NULL if no rule is found or if @dentry is negative.
|
* Extracts the Landlock object pointer from @dentry's inode security blob and
|
||||||
|
* stores it in @id for use as a rule-tree lookup key.
|
||||||
|
*
|
||||||
|
* When this returns false (negative dentry or no Landlock object), no rule can
|
||||||
|
* match this inode, so landlock_unmask_layers() need not be called. Callers
|
||||||
|
* that gate landlock_unmask_layers() on this function must handle the NULL
|
||||||
|
* masks case independently, since the !masks-returns-true early-return in
|
||||||
|
* landlock_unmask_layers() will not be reached. See the allowed_parent2
|
||||||
|
* initialization in is_access_to_paths_allowed().
|
||||||
|
*
|
||||||
|
* Return: True if a Landlock object exists for @dentry, false otherwise.
|
||||||
*/
|
*/
|
||||||
static const struct landlock_rule *
|
static bool get_inode_id(const struct dentry *const dentry,
|
||||||
find_rule(const struct landlock_domain *const domain,
|
struct landlock_id *id)
|
||||||
const struct dentry *const dentry)
|
|
||||||
{
|
{
|
||||||
const struct landlock_rule *rule;
|
|
||||||
const struct inode *inode;
|
|
||||||
struct landlock_id id = {
|
|
||||||
.type = LANDLOCK_KEY_INODE,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Ignores nonexistent leafs. */
|
/* Ignores nonexistent leafs. */
|
||||||
if (d_is_negative(dentry))
|
if (d_is_negative(dentry))
|
||||||
return NULL;
|
return false;
|
||||||
|
|
||||||
inode = d_backing_inode(dentry);
|
/*
|
||||||
rcu_read_lock();
|
* rcu_access_pointer() is sufficient: the pointer is used only as a
|
||||||
id.key.object = rcu_dereference(landlock_inode(inode)->object);
|
* numeric comparison key for rule lookup, not dereferenced. The object
|
||||||
rule = landlock_find_rule(domain, id);
|
* cannot be freed while the domain exists because the domain's rule
|
||||||
rcu_read_unlock();
|
* tree holds its own reference to it.
|
||||||
return rule;
|
*/
|
||||||
|
id->key.object = rcu_access_pointer(
|
||||||
|
landlock_inode(d_backing_inode(dentry))->object);
|
||||||
|
return !!id->key.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool unmask_layers_fs(const struct landlock_domain *const domain,
|
||||||
|
const struct landlock_id id,
|
||||||
|
const access_mask_t access_request,
|
||||||
|
struct layer_masks *masks,
|
||||||
|
const struct dentry *const dentry)
|
||||||
|
{
|
||||||
|
const struct landlock_rule *rule = NULL;
|
||||||
|
bool ret;
|
||||||
|
|
||||||
|
ret = landlock_unmask_layers(domain, id, masks, &rule);
|
||||||
|
if (rule)
|
||||||
|
trace_landlock_check_rule_fs(domain, rule, access_request,
|
||||||
|
dentry);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -782,6 +806,9 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
|
||||||
bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
|
bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
|
||||||
child1_is_directory = true, child2_is_directory = true;
|
child1_is_directory = true, child2_is_directory = true;
|
||||||
struct path walker_path;
|
struct path walker_path;
|
||||||
|
struct landlock_id id = {
|
||||||
|
.type = LANDLOCK_KEY_INODE,
|
||||||
|
};
|
||||||
access_mask_t access_masked_parent1, access_masked_parent2;
|
access_mask_t access_masked_parent1, access_masked_parent2;
|
||||||
struct layer_masks _layer_masks_child1, _layer_masks_child2;
|
struct layer_masks _layer_masks_child1, _layer_masks_child2;
|
||||||
struct layer_masks *layer_masks_child1 = NULL,
|
struct layer_masks *layer_masks_child1 = NULL,
|
||||||
|
|
@ -821,28 +848,46 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
|
||||||
/* For a simple request, only check for requested accesses. */
|
/* For a simple request, only check for requested accesses. */
|
||||||
access_masked_parent1 = access_request_parent1;
|
access_masked_parent1 = access_request_parent1;
|
||||||
access_masked_parent2 = access_request_parent2;
|
access_masked_parent2 = access_request_parent2;
|
||||||
|
/*
|
||||||
|
* Simple requests have no parent2 to check, so parent2 is
|
||||||
|
* trivially allowed. This must be set explicitly because the
|
||||||
|
* get_inode_id() gate in the pathwalk loop may prevent
|
||||||
|
* landlock_unmask_layers() from being called (which would
|
||||||
|
* otherwise return true for NULL masks as a side effect).
|
||||||
|
*/
|
||||||
|
allowed_parent2 = true;
|
||||||
is_dom_check = false;
|
is_dom_check = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlikely(dentry_child1)) {
|
if (unlikely(dentry_child1)) {
|
||||||
/*
|
struct landlock_id id = {
|
||||||
* Get the layer masks for the child dentries for use by domain
|
.type = LANDLOCK_KEY_INODE,
|
||||||
* check later.
|
};
|
||||||
*/
|
access_mask_t handled;
|
||||||
if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
|
|
||||||
&_layer_masks_child1,
|
handled = landlock_init_layer_masks(domain,
|
||||||
LANDLOCK_KEY_INODE))
|
LANDLOCK_MASK_ACCESS_FS,
|
||||||
landlock_unmask_layers(find_rule(domain, dentry_child1),
|
&_layer_masks_child1,
|
||||||
&_layer_masks_child1);
|
LANDLOCK_KEY_INODE);
|
||||||
|
if (handled && get_inode_id(dentry_child1, &id))
|
||||||
|
unmask_layers_fs(domain, id, handled,
|
||||||
|
&_layer_masks_child1, dentry_child1);
|
||||||
layer_masks_child1 = &_layer_masks_child1;
|
layer_masks_child1 = &_layer_masks_child1;
|
||||||
child1_is_directory = d_is_dir(dentry_child1);
|
child1_is_directory = d_is_dir(dentry_child1);
|
||||||
}
|
}
|
||||||
if (unlikely(dentry_child2)) {
|
if (unlikely(dentry_child2)) {
|
||||||
if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
|
struct landlock_id id = {
|
||||||
&_layer_masks_child2,
|
.type = LANDLOCK_KEY_INODE,
|
||||||
LANDLOCK_KEY_INODE))
|
};
|
||||||
landlock_unmask_layers(find_rule(domain, dentry_child2),
|
access_mask_t handled;
|
||||||
&_layer_masks_child2);
|
|
||||||
|
handled = landlock_init_layer_masks(domain,
|
||||||
|
LANDLOCK_MASK_ACCESS_FS,
|
||||||
|
&_layer_masks_child2,
|
||||||
|
LANDLOCK_KEY_INODE);
|
||||||
|
if (handled && get_inode_id(dentry_child2, &id))
|
||||||
|
unmask_layers_fs(domain, id, handled,
|
||||||
|
&_layer_masks_child2, dentry_child2);
|
||||||
layer_masks_child2 = &_layer_masks_child2;
|
layer_masks_child2 = &_layer_masks_child2;
|
||||||
child2_is_directory = d_is_dir(dentry_child2);
|
child2_is_directory = d_is_dir(dentry_child2);
|
||||||
}
|
}
|
||||||
|
|
@ -854,8 +899,6 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
|
||||||
* restriction.
|
* restriction.
|
||||||
*/
|
*/
|
||||||
while (true) {
|
while (true) {
|
||||||
const struct landlock_rule *rule;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If at least all accesses allowed on the destination are
|
* If at least all accesses allowed on the destination are
|
||||||
* already allowed on the source, respectively if there is at
|
* already allowed on the source, respectively if there is at
|
||||||
|
|
@ -896,13 +939,20 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
rule = find_rule(domain, walker_path.dentry);
|
if (get_inode_id(walker_path.dentry, &id)) {
|
||||||
allowed_parent1 =
|
allowed_parent1 =
|
||||||
allowed_parent1 ||
|
allowed_parent1 ||
|
||||||
landlock_unmask_layers(rule, layer_masks_parent1);
|
unmask_layers_fs(domain, id,
|
||||||
allowed_parent2 =
|
access_masked_parent1,
|
||||||
allowed_parent2 ||
|
layer_masks_parent1,
|
||||||
landlock_unmask_layers(rule, layer_masks_parent2);
|
walker_path.dentry);
|
||||||
|
allowed_parent2 =
|
||||||
|
allowed_parent2 ||
|
||||||
|
unmask_layers_fs(domain, id,
|
||||||
|
access_masked_parent2,
|
||||||
|
layer_masks_parent2,
|
||||||
|
walker_path.dentry);
|
||||||
|
}
|
||||||
|
|
||||||
/* Stops when a rule from each layer grants access. */
|
/* Stops when a rule from each layer grants access. */
|
||||||
if (allowed_parent1 && allowed_parent2)
|
if (allowed_parent1 && allowed_parent2)
|
||||||
|
|
@ -1076,23 +1126,30 @@ static bool collect_domain_accesses(const struct landlock_domain *const domain,
|
||||||
struct layer_masks *layer_masks_dom)
|
struct layer_masks *layer_masks_dom)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
access_mask_t access_masked_dom;
|
||||||
|
|
||||||
if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom))
|
if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom))
|
||||||
return true;
|
return true;
|
||||||
if (is_nouser_or_private(dir))
|
if (is_nouser_or_private(dir))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
|
access_masked_dom =
|
||||||
layer_masks_dom, LANDLOCK_KEY_INODE))
|
landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
|
||||||
|
layer_masks_dom, LANDLOCK_KEY_INODE);
|
||||||
|
if (!access_masked_dom)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
dget(dir);
|
dget(dir);
|
||||||
while (true) {
|
while (true) {
|
||||||
struct dentry *parent_dentry;
|
struct dentry *parent_dentry;
|
||||||
|
struct landlock_id id = {
|
||||||
|
.type = LANDLOCK_KEY_INODE,
|
||||||
|
};
|
||||||
|
|
||||||
/* Gets all layers allowing all domain accesses. */
|
/* Gets all layers allowing all domain accesses. */
|
||||||
if (landlock_unmask_layers(find_rule(domain, dir),
|
if (get_inode_id(dir, &id) &&
|
||||||
layer_masks_dom)) {
|
unmask_layers_fs(domain, id, access_masked_dom,
|
||||||
|
layer_masks_dom, dir)) {
|
||||||
/*
|
/*
|
||||||
* Stops when all handled accesses are allowed by at
|
* Stops when all handled accesses are allowed by at
|
||||||
* least one rule in each layer.
|
* least one rule in each layer.
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,22 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool unmask_layers_net(const struct landlock_domain *const domain,
|
||||||
|
const struct landlock_id id,
|
||||||
|
struct layer_masks *masks,
|
||||||
|
access_mask_t access_request)
|
||||||
|
{
|
||||||
|
const struct landlock_rule *rule = NULL;
|
||||||
|
bool ret;
|
||||||
|
|
||||||
|
ret = landlock_unmask_layers(domain, id, masks, &rule);
|
||||||
|
if (rule)
|
||||||
|
trace_landlock_check_rule_net(
|
||||||
|
domain, rule, access_request,
|
||||||
|
ntohs((__force __be16)id.key.data));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int current_check_access_socket(struct socket *const sock,
|
static int current_check_access_socket(struct socket *const sock,
|
||||||
struct sockaddr *const address,
|
struct sockaddr *const address,
|
||||||
const int addrlen,
|
const int addrlen,
|
||||||
|
|
@ -62,7 +78,6 @@ static int current_check_access_socket(struct socket *const sock,
|
||||||
unsigned short sock_family;
|
unsigned short sock_family;
|
||||||
__be16 port;
|
__be16 port;
|
||||||
struct layer_masks layer_masks = {};
|
struct layer_masks layer_masks = {};
|
||||||
const struct landlock_rule *rule;
|
|
||||||
struct landlock_id id = {
|
struct landlock_id id = {
|
||||||
.type = LANDLOCK_KEY_NET_PORT,
|
.type = LANDLOCK_KEY_NET_PORT,
|
||||||
};
|
};
|
||||||
|
|
@ -248,14 +263,14 @@ static int current_check_access_socket(struct socket *const sock,
|
||||||
id.key.data = (__force uintptr_t)port;
|
id.key.data = (__force uintptr_t)port;
|
||||||
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
|
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
|
||||||
|
|
||||||
rule = landlock_find_rule(subject->domain, id);
|
|
||||||
access_request = landlock_init_layer_masks(subject->domain,
|
access_request = landlock_init_layer_masks(subject->domain,
|
||||||
access_request, &layer_masks,
|
access_request, &layer_masks,
|
||||||
LANDLOCK_KEY_NET_PORT);
|
LANDLOCK_KEY_NET_PORT);
|
||||||
if (!access_request)
|
if (!access_request)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (landlock_unmask_layers(rule, &layer_masks))
|
if (unmask_layers_net(subject->domain, id, &layer_masks,
|
||||||
|
access_request))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
audit_net.family = address->sa_family;
|
audit_net.family = address->sa_family;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user