From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp-1909.mail.infomaniak.ch (smtp-1909.mail.infomaniak.ch [185.125.25.9]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B064743CE5C for ; Wed, 22 Jul 2026 17:12:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.125.25.9 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784740363; cv=none; b=j7T1tnmK6+NBgDdo9YrL8HB8F7St5l4n3ZlOw/kDAZjc7GD98YykDKwzhM2mnrYjO4x+KxbH2XOySbWxf2xcBDXQQI2lrgwx8b10qyWpppXqgzFXGmLsIj+2Wvy4Cr2ZlcSvSfrHWrFeJ7fwYCElgBeSwpQ2Nxq4C77F4U91oEQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784740363; c=relaxed/simple; bh=sSEGhFCxJ8WI8o2niyCyMvnr9rzRsXN/1iiOJqaLr8A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=SUrOQPFyq/ByMYYv2MDu03+/shQuOHqsoviSn4UAD177somtCR4kFqNnOAjiOuOgjCh/Aq3ruHhPsnovvLYrCsd0Za9TQKdF2Kx6j3TZeQyq275U30h6YIE7w5Kmcw6rXZ/GIiMobtxvb332gTsN+3G3GhmwFVJF4qXyLXbAlyo= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net; spf=pass smtp.mailfrom=digikod.net; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b=u6qKBGfo; arc=none smtp.client-ip=185.125.25.9 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=digikod.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b="u6qKBGfo" Received: from smtp-4-0001.mail.infomaniak.ch (smtp-4-0001.mail.infomaniak.ch [10.7.10.108]) by smtp-4-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4h515p2VQ0zrvx; Wed, 22 Jul 2026 19:12:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=digikod.net; s=20191114; t=1784740350; bh=7e/UmKM7r+fftvOgIlpesWHHji8svBxKv/3FTeedbhg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=u6qKBGfoYxf3tRAz+T4+XEytwLk9bCpg3Zg0Ul+RIhk82lxrhBR1vJZewRl4JFb75 ux+yV/Halw5tZQX1Fyovjh0UhOgbR8odXgPL+8yMO1kyYaRgtSKVS7YQouYU9lPHnw bVtbybNI8R93uoIPNw1oeI+YbLBfB9zkISlmW+yc= Received: from unknown by smtp-4-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 4h515n3mrGzxMr; Wed, 22 Jul 2026 19:12:29 +0200 (CEST) From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= To: =?UTF-8?q?G=C3=BCnther=20Noack?= , Steven Rostedt Cc: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= , Christian Brauner , Jann Horn , Jeff Xu , Justin Suess , Kees Cook , Masami Hiramatsu , Mathieu Desnoyers , Matthieu Buffet , Mikhail Ivanov , Tingmao Wang , kernel-team@cloudflare.com, linux-security-module@vger.kernel.org, linux-trace-kernel@vger.kernel.org Subject: [PATCH v3 12/20] landlock: Add tracepoints for rule checking Date: Wed, 22 Jul 2026 19:11:44 +0200 Message-ID: <20260722171159.2776765-13-mic@digikod.net> In-Reply-To: <20260722171159.2776765-1-mic@digikod.net> References: <20260722171159.2776765-1-mic@digikod.net> Precedence: bulk X-Mailing-List: linux-trace-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Infomaniak-Routing: alpha 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 Cc: Justin Suess Cc: Masami Hiramatsu Cc: Mathieu Desnoyers Cc: Steven Rostedt Cc: Tingmao Wang Signed-off-by: Mickaël Salaün --- Changes since v2: https://patch.msgid.link/20260406143717.1815792-10-mic@digikod.net - Order the check_rule tracepoint arguments with the common part (domain, rule) before the event-specific access_request and object. - Reformatted TP_STRUCT__entry and TP_fast_assign to kernel tracing convention (requested by Steven Rostedt). - Render the request access right as symbolic names with __print_flags(), shared with the audit blocker names. - Rename the per-layer field label allowed= to grants= and render it symbolically via __print_landlock_layers(), intersected with the request to show the granted requested rights per layer (was a raw hex value). - Rename struct layer_access_masks to the base's struct layer_masks. - Rename the check_rule printk label request= to access_request= and the per-layer ring-buffer field layers to grants, so each trace-only field and its printk label share one name (usable directly as an ftrace filter); audit-shared labels (domain=, ruleset=) are unchanged. Changes since v1: https://patch.msgid.link/20250523165741.693976-6-mic@digikod.net - Merged find-rule consolidation (v1 2/5) into this patch. - Added check_rule_net tracepoint for network rules. - Added get_inode_id() helper with rcu_access_pointer(). - Added allowed_parent2 behavioral fix. --- include/trace/events/landlock.h | 203 ++++++++++++++++++++++++++++++++ security/landlock/domain.c | 32 +++-- security/landlock/domain.h | 10 +- security/landlock/fs.c | 149 +++++++++++++++-------- security/landlock/net.c | 21 +++- 5 files changed, 350 insertions(+), 65 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 7f221d8fff38..c693248afe23 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -11,10 +11,13 @@ #define _TRACE_LANDLOCK_H #include +#include #include +struct dentry; struct landlock_domain; struct landlock_hierarchy; +struct landlock_rule; struct landlock_ruleset; struct path; @@ -74,7 +77,110 @@ struct path; * (e.g. network ports are __u64 in host endianness, like * landlock_net_port_attr.port). Per-event details, such as where a value * 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). + */ + +#ifdef CREATE_TRACE_POINTS + +/* + * 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 */ + +/* + * 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 @@ -374,6 +480,103 @@ TRACE_EVENT(landlock_free_domain, __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 #endif /* _TRACE_LANDLOCK_H */ diff --git a/security/landlock/domain.c b/security/landlock/domain.c index 082c4da68536..c20020024de4 100644 --- a/security/landlock/domain.c +++ b/security/landlock/domain.c @@ -97,9 +97,9 @@ 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_domain *const domain, - const struct landlock_id id) +static const struct landlock_rule * +find_rule(const struct landlock_domain *const domain, + const struct landlock_id id) { const struct rb_root *root; const struct rb_node *node; @@ -126,26 +126,38 @@ landlock_find_rule(const struct landlock_domain *const domain, /** * 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 - * the access rights granted in @rule are removed from it (because they are now - * fulfilled). + * Looks up the rule matching @id in @domain, then updates the set of + * (per-layer) unfulfilled access rights @masks so that all the access rights + * 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. + * @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 * 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) +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) { + const struct landlock_rule *rule; + if (!masks) return true; + + rule = find_rule(domain, id); if (!rule) return false; + if (matched_rule) + *matched_rule = rule; + /* * An access is granted if, for each policy layer, at least one rule * encountered on the pathwalk grants the requested access, regardless diff --git a/security/landlock/domain.h b/security/landlock/domain.h index 8351e22016fe..5baa4a73b446 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -305,12 +305,10 @@ struct landlock_domain * landlock_merge_ruleset(struct landlock_domain *const parent, struct landlock_ruleset *const ruleset); -const struct landlock_rule * -landlock_find_rule(const struct landlock_domain *const domain, - const struct landlock_id id); - -bool landlock_unmask_layers(const struct landlock_rule *const rule, - struct layer_masks *masks); +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, diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 48744a21d0a3..fe028aac26ae 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -376,31 +376,55 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, /* 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. + * + * Extracts the Landlock object pointer from @dentry's inode security blob and + * stores it in @id for use as a rule-tree lookup key. * - * Returns NULL if no rule is found or if @dentry is negative. + * 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 * -find_rule(const struct landlock_domain *const domain, - const struct dentry *const dentry) +static bool get_inode_id(const struct dentry *const dentry, + struct landlock_id *id) { - const struct landlock_rule *rule; - const struct inode *inode; - struct landlock_id id = { - .type = LANDLOCK_KEY_INODE, - }; - /* Ignores nonexistent leafs. */ if (d_is_negative(dentry)) - return NULL; + return false; - inode = d_backing_inode(dentry); - rcu_read_lock(); - id.key.object = rcu_dereference(landlock_inode(inode)->object); - rule = landlock_find_rule(domain, id); - rcu_read_unlock(); - return rule; + /* + * rcu_access_pointer() is sufficient: the pointer is used only as a + * numeric comparison key for rule lookup, not dereferenced. The object + * cannot be freed while the domain exists because the domain's rule + * tree holds its own reference to it. + */ + 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; } /* @@ -781,6 +805,9 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain, bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check, child1_is_directory = true, child2_is_directory = true; struct path walker_path; + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; access_mask_t access_masked_parent1, access_masked_parent2; struct layer_masks _layer_masks_child1, _layer_masks_child2; struct layer_masks *layer_masks_child1 = NULL, @@ -820,28 +847,46 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain, /* For a simple request, only check for requested accesses. */ access_masked_parent1 = access_request_parent1; 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; } if (unlikely(dentry_child1)) { - /* - * Get the layer masks for the child dentries for use by domain - * check later. - */ - if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child1, - LANDLOCK_KEY_INODE)) - landlock_unmask_layers(find_rule(domain, dentry_child1), - &_layer_masks_child1); + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; + access_mask_t handled; + + handled = landlock_init_layer_masks(domain, + LANDLOCK_MASK_ACCESS_FS, + &_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; child1_is_directory = d_is_dir(dentry_child1); } if (unlikely(dentry_child2)) { - if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child2, - LANDLOCK_KEY_INODE)) - landlock_unmask_layers(find_rule(domain, dentry_child2), - &_layer_masks_child2); + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; + access_mask_t handled; + + 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; child2_is_directory = d_is_dir(dentry_child2); } @@ -853,8 +898,6 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain, * restriction. */ while (true) { - const struct landlock_rule *rule; - /* * If at least all accesses allowed on the destination are * already allowed on the source, respectively if there is at @@ -895,13 +938,20 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain, break; } - rule = find_rule(domain, walker_path.dentry); - allowed_parent1 = - allowed_parent1 || - landlock_unmask_layers(rule, layer_masks_parent1); - allowed_parent2 = - allowed_parent2 || - landlock_unmask_layers(rule, layer_masks_parent2); + if (get_inode_id(walker_path.dentry, &id)) { + allowed_parent1 = + allowed_parent1 || + unmask_layers_fs(domain, id, + access_masked_parent1, + layer_masks_parent1, + 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. */ if (allowed_parent1 && allowed_parent2) @@ -1064,23 +1114,30 @@ static bool collect_domain_accesses(const struct landlock_domain *const domain, struct layer_masks *layer_masks_dom) { bool ret = false; + access_mask_t access_masked_dom; if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom)) return true; if (is_nouser_or_private(dir)) return true; - if (!landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - layer_masks_dom, LANDLOCK_KEY_INODE)) + access_masked_dom = + landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, + layer_masks_dom, LANDLOCK_KEY_INODE); + if (!access_masked_dom) return true; dget(dir); while (true) { struct dentry *parent_dentry; + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; /* Gets all layers allowing all domain accesses. */ - if (landlock_unmask_layers(find_rule(domain, dir), - layer_masks_dom)) { + if (get_inode_id(dir, &id) && + unmask_layers_fs(domain, id, access_masked_dom, + layer_masks_dom, dir)) { /* * Stops when all handled accesses are allowed by at * least one rule in each layer. diff --git a/security/landlock/net.c b/security/landlock/net.c index ead97fcfdcff..8f2aaac54b33 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -53,6 +53,22 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, 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, struct sockaddr *const address, const int addrlen, @@ -62,7 +78,6 @@ static int current_check_access_socket(struct socket *const sock, unsigned short sock_family; __be16 port; struct layer_masks layer_masks = {}; - const struct landlock_rule *rule; struct landlock_id id = { .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; 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, &layer_masks, LANDLOCK_KEY_NET_PORT); if (!access_request) return 0; - if (landlock_unmask_layers(rule, &layer_masks)) + if (unmask_layers_net(subject->domain, id, &layer_masks, + access_request)) return 0; audit_net.family = address->sa_family; -- 2.54.0