Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

landlock: Add AUDIT_LANDLOCK_ACCESS and log ptrace denials

Add a new AUDIT_LANDLOCK_ACCESS record type dedicated to an access
request denied by a Landlock domain. AUDIT_LANDLOCK_ACCESS indicates
that something unexpected happened.

For now, only denied access are logged, which means that any
AUDIT_LANDLOCK_ACCESS record is always followed by a SYSCALL record with
"success=no". However, log parsers should check this syscall property
because this is the only sign that a request was denied. Indeed, we
could have "success=yes" if Landlock would support a "permissive" mode.
We could also add a new field to AUDIT_LANDLOCK_DOMAIN for this mode
(see following commit).

By default, the only logged access requests are those coming from the
same executed program that enforced the Landlock restriction on itself.
In other words, no audit record are created for a task after it called
execve(2). This is required to avoid log spam because programs may only
be aware of their own restrictions, but not the inherited ones.

Following commits will allow to conditionally generate
AUDIT_LANDLOCK_ACCESS records according to dedicated
landlock_restrict_self(2)'s flags.

The AUDIT_LANDLOCK_ACCESS message contains:
- the "domain" ID restricting the action on an object,
- the "blockers" that are missing to allow the requested access,
- a set of fields identifying the related object (e.g. task identified
with "opid" and "ocomm").

The blockers are implicit restrictions (e.g. ptrace), or explicit access
rights (e.g. filesystem), or explicit scopes (e.g. signal). This field
contains a list of at least one element, each separated with a comma.

The initial blocker is "ptrace", which describe all implicit Landlock
restrictions related to ptrace (e.g. deny tracing of tasks outside a
sandbox).

Add audit support to ptrace_access_check and ptrace_traceme hooks. For
the ptrace_access_check case, we log the current/parent domain and the
child task. For the ptrace_traceme case, we log the parent domain and
the current/child task. Indeed, the requester and the target are the
current task, but the action would be performed by the parent task.

Audit event sample:

type=LANDLOCK_ACCESS msg=audit(1729738800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
type=SYSCALL msg=audit(1729738800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0

A following commit adds user documentation.

Add KUnit tests to check reading of domain ID relative to layer level.

The quick return for non-landlocked tasks is moved from task_ptrace() to
each LSM hooks.

It is not useful to inline the audit_enabled check because other
computation are performed by landlock_log_denial().

Use scoped guards for RCU read-side critical sections.

Cc: Günther Noack <gnoack@google.com>
Acked-by: Paul Moore <paul@paul-moore.com>
Link: https://lore.kernel.org/r/20250320190717.2287696-10-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>

+338 -25
+2 -1
include/uapi/linux/audit.h
··· 33 33 * 1100 - 1199 user space trusted application messages 34 34 * 1200 - 1299 messages internal to the audit daemon 35 35 * 1300 - 1399 audit event messages 36 - * 1400 - 1499 SE Linux use 36 + * 1400 - 1499 access control messages 37 37 * 1500 - 1599 kernel LSPP events 38 38 * 1600 - 1699 kernel crypto events 39 39 * 1700 - 1799 kernel anomaly records ··· 146 146 #define AUDIT_IPE_ACCESS 1420 /* IPE denial or grant */ 147 147 #define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */ 148 148 #define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */ 149 + #define AUDIT_LANDLOCK_ACCESS 1423 /* Landlock denial */ 149 150 150 151 #define AUDIT_FIRST_KERN_ANOM_MSG 1700 151 152 #define AUDIT_LAST_KERN_ANOM_MSG 1799
+4 -1
security/landlock/Makefile
··· 5 5 6 6 landlock-$(CONFIG_INET) += net.o 7 7 8 - landlock-$(CONFIG_AUDIT) += id.o 8 + landlock-$(CONFIG_AUDIT) += \ 9 + id.o \ 10 + audit.o \ 11 + domain.o
+151
security/landlock/audit.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Landlock - Audit helpers 4 + * 5 + * Copyright © 2023-2025 Microsoft Corporation 6 + */ 7 + 8 + #include <kunit/test.h> 9 + #include <linux/audit.h> 10 + #include <linux/lsm_audit.h> 11 + 12 + #include "audit.h" 13 + #include "cred.h" 14 + #include "domain.h" 15 + #include "limits.h" 16 + #include "ruleset.h" 17 + 18 + static const char *get_blocker(const enum landlock_request_type type) 19 + { 20 + switch (type) { 21 + case LANDLOCK_REQUEST_PTRACE: 22 + return "ptrace"; 23 + } 24 + 25 + WARN_ON_ONCE(1); 26 + return "unknown"; 27 + } 28 + 29 + static void log_blockers(struct audit_buffer *const ab, 30 + const enum landlock_request_type type) 31 + { 32 + audit_log_format(ab, "%s", get_blocker(type)); 33 + } 34 + 35 + static struct landlock_hierarchy * 36 + get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer) 37 + { 38 + struct landlock_hierarchy *hierarchy = domain->hierarchy; 39 + ssize_t i; 40 + 41 + if (WARN_ON_ONCE(layer >= domain->num_layers)) 42 + return hierarchy; 43 + 44 + for (i = domain->num_layers - 1; i > layer; i--) { 45 + if (WARN_ON_ONCE(!hierarchy->parent)) 46 + break; 47 + 48 + hierarchy = hierarchy->parent; 49 + } 50 + 51 + return hierarchy; 52 + } 53 + 54 + #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST 55 + 56 + static void test_get_hierarchy(struct kunit *const test) 57 + { 58 + struct landlock_hierarchy dom0_hierarchy = { 59 + .id = 10, 60 + }; 61 + struct landlock_hierarchy dom1_hierarchy = { 62 + .parent = &dom0_hierarchy, 63 + .id = 20, 64 + }; 65 + struct landlock_hierarchy dom2_hierarchy = { 66 + .parent = &dom1_hierarchy, 67 + .id = 30, 68 + }; 69 + struct landlock_ruleset dom2 = { 70 + .hierarchy = &dom2_hierarchy, 71 + .num_layers = 3, 72 + }; 73 + 74 + KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id); 75 + KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id); 76 + KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id); 77 + KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); 78 + } 79 + 80 + #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ 81 + 82 + static bool is_valid_request(const struct landlock_request *const request) 83 + { 84 + if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS)) 85 + return false; 86 + 87 + if (WARN_ON_ONCE(!request->layer_plus_one)) 88 + return false; 89 + 90 + return true; 91 + } 92 + 93 + /** 94 + * landlock_log_denial - Create audit records related to a denial 95 + * 96 + * @subject: The Landlock subject's credential denying an action. 97 + * @request: Detail of the user space request. 98 + */ 99 + void landlock_log_denial(const struct landlock_cred_security *const subject, 100 + const struct landlock_request *const request) 101 + { 102 + struct audit_buffer *ab; 103 + struct landlock_hierarchy *youngest_denied; 104 + size_t youngest_layer; 105 + 106 + if (WARN_ON_ONCE(!subject || !subject->domain || 107 + !subject->domain->hierarchy || !request)) 108 + return; 109 + 110 + if (!is_valid_request(request)) 111 + return; 112 + 113 + if (!audit_enabled) 114 + return; 115 + 116 + youngest_layer = request->layer_plus_one - 1; 117 + youngest_denied = get_hierarchy(subject->domain, youngest_layer); 118 + 119 + /* Ignores denials after an execution. */ 120 + if (!(subject->domain_exec & (1 << youngest_layer))) 121 + return; 122 + 123 + /* Uses consistent allocation flags wrt common_lsm_audit(). */ 124 + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, 125 + AUDIT_LANDLOCK_ACCESS); 126 + if (!ab) 127 + return; 128 + 129 + audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id); 130 + log_blockers(ab, request->type); 131 + audit_log_lsm_data(ab, &request->audit); 132 + audit_log_end(ab); 133 + } 134 + 135 + #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST 136 + 137 + static struct kunit_case test_cases[] = { 138 + /* clang-format off */ 139 + KUNIT_CASE(test_get_hierarchy), 140 + {} 141 + /* clang-format on */ 142 + }; 143 + 144 + static struct kunit_suite test_suite = { 145 + .name = "landlock_audit", 146 + .test_cases = test_cases, 147 + }; 148 + 149 + kunit_test_suite(test_suite); 150 + 151 + #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+52
security/landlock/audit.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-only */ 2 + /* 3 + * Landlock - Audit helpers 4 + * 5 + * Copyright © 2023-2025 Microsoft Corporation 6 + */ 7 + 8 + #ifndef _SECURITY_LANDLOCK_AUDIT_H 9 + #define _SECURITY_LANDLOCK_AUDIT_H 10 + 11 + #include <linux/audit.h> 12 + #include <linux/lsm_audit.h> 13 + 14 + #include "cred.h" 15 + 16 + enum landlock_request_type { 17 + LANDLOCK_REQUEST_PTRACE = 1, 18 + }; 19 + 20 + /* 21 + * We should be careful to only use a variable of this type for 22 + * landlock_log_denial(). This way, the compiler can remove it entirely if 23 + * CONFIG_AUDIT is not set. 24 + */ 25 + struct landlock_request { 26 + /* Mandatory fields. */ 27 + enum landlock_request_type type; 28 + struct common_audit_data audit; 29 + 30 + /** 31 + * layer_plus_one: First layer level that denies the request + 1. The 32 + * extra one is useful to detect uninitialized field. 33 + */ 34 + size_t layer_plus_one; 35 + }; 36 + 37 + #ifdef CONFIG_AUDIT 38 + 39 + void landlock_log_denial(const struct landlock_cred_security *const subject, 40 + const struct landlock_request *const request); 41 + 42 + #else /* CONFIG_AUDIT */ 43 + 44 + static inline void 45 + landlock_log_denial(const struct landlock_cred_security *const subject, 46 + const struct landlock_request *const request) 47 + { 48 + } 49 + 50 + #endif /* CONFIG_AUDIT */ 51 + 52 + #endif /* _SECURITY_LANDLOCK_AUDIT_H */
+28
security/landlock/domain.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Landlock - Domain management 4 + * 5 + * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> 6 + * Copyright © 2018-2020 ANSSI 7 + * Copyright © 2024-2025 Microsoft Corporation 8 + */ 9 + 10 + #include "domain.h" 11 + #include "id.h" 12 + 13 + #ifdef CONFIG_AUDIT 14 + 15 + /** 16 + * landlock_init_hierarchy_log - Partially initialize landlock_hierarchy 17 + * 18 + * @hierarchy: The hierarchy to initialize. 19 + * 20 + * @hierarchy->parent and @hierarchy->usage should already be set. 21 + */ 22 + int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy) 23 + { 24 + hierarchy->id = landlock_get_id_range(1); 25 + return 0; 26 + } 27 + 28 + #endif /* CONFIG_AUDIT */
+22
security/landlock/domain.h
··· 4 4 * 5 5 * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> 6 6 * Copyright © 2018-2020 ANSSI 7 + * Copyright © 2024-2025 Microsoft Corporation 7 8 */ 8 9 9 10 #ifndef _SECURITY_LANDLOCK_DOMAIN_H ··· 27 26 * domain. 28 27 */ 29 28 refcount_t usage; 29 + 30 + #ifdef CONFIG_AUDIT 31 + /** 32 + * @id: Landlock domain ID, sets once at domain creation time. 33 + */ 34 + u64 id; 35 + #endif /* CONFIG_AUDIT */ 30 36 }; 37 + 38 + #ifdef CONFIG_AUDIT 39 + 40 + int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy); 41 + 42 + #else /* CONFIG_AUDIT */ 43 + 44 + static inline int 45 + landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy) 46 + { 47 + return 0; 48 + } 49 + 50 + #endif /* CONFIG_AUDIT */ 31 51 32 52 static inline void 33 53 landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy)
+6
security/landlock/ruleset.c
··· 23 23 #include <linux/workqueue.h> 24 24 25 25 #include "access.h" 26 + #include "audit.h" 26 27 #include "domain.h" 27 28 #include "limits.h" 28 29 #include "object.h" ··· 506 505 free_ruleset(ruleset); 507 506 } 508 507 508 + /* Only called by hook_cred_free(). */ 509 509 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset) 510 510 { 511 511 if (ruleset && refcount_dec_and_test(&ruleset->usage)) { ··· 563 561 564 562 /* ...and including @ruleset. */ 565 563 err = merge_ruleset(new_dom, ruleset); 564 + if (err) 565 + return ERR_PTR(err); 566 + 567 + err = landlock_init_hierarchy_log(new_dom->hierarchy); 566 568 if (err) 567 569 return ERR_PTR(err); 568 570
+73 -23
security/landlock/task.c
··· 12 12 #include <linux/cred.h> 13 13 #include <linux/errno.h> 14 14 #include <linux/kernel.h> 15 + #include <linux/lsm_audit.h> 15 16 #include <linux/lsm_hooks.h> 16 17 #include <linux/rcupdate.h> 17 18 #include <linux/sched.h> ··· 20 19 #include <net/af_unix.h> 21 20 #include <net/sock.h> 22 21 22 + #include "audit.h" 23 23 #include "common.h" 24 24 #include "cred.h" 25 25 #include "domain.h" ··· 43 41 { 44 42 const struct landlock_hierarchy *walker; 45 43 44 + /* Quick return for non-landlocked tasks. */ 46 45 if (!parent) 47 46 return true; 47 + 48 48 if (!child) 49 49 return false; 50 + 50 51 for (walker = child->hierarchy; walker; walker = walker->parent) { 51 52 if (walker == parent->hierarchy) 52 53 /* @parent is in the scoped hierarchy of @child. */ 53 54 return true; 54 55 } 56 + 55 57 /* There is no relationship between @parent and @child. */ 56 58 return false; 57 59 } 58 60 59 - static bool task_is_scoped(const struct task_struct *const parent, 60 - const struct task_struct *const child) 61 + static int domain_ptrace(const struct landlock_ruleset *const parent, 62 + const struct landlock_ruleset *const child) 61 63 { 62 - bool is_scoped; 63 - const struct landlock_ruleset *dom_parent, *dom_child; 64 - 65 - rcu_read_lock(); 66 - dom_parent = landlock_get_task_domain(parent); 67 - dom_child = landlock_get_task_domain(child); 68 - is_scoped = domain_scope_le(dom_parent, dom_child); 69 - rcu_read_unlock(); 70 - return is_scoped; 71 - } 72 - 73 - static int task_ptrace(const struct task_struct *const parent, 74 - const struct task_struct *const child) 75 - { 76 - /* Quick return for non-landlocked tasks. */ 77 - if (!landlocked(parent)) 64 + if (domain_scope_le(parent, child)) 78 65 return 0; 79 - if (task_is_scoped(parent, child)) 80 - return 0; 66 + 81 67 return -EPERM; 82 68 } 83 69 ··· 85 95 static int hook_ptrace_access_check(struct task_struct *const child, 86 96 const unsigned int mode) 87 97 { 88 - return task_ptrace(current, child); 98 + const struct landlock_cred_security *parent_subject; 99 + const struct landlock_ruleset *child_dom; 100 + int err; 101 + 102 + /* Quick return for non-landlocked tasks. */ 103 + parent_subject = landlock_cred(current_cred()); 104 + if (!parent_subject) 105 + return 0; 106 + 107 + scoped_guard(rcu) 108 + { 109 + child_dom = landlock_get_task_domain(child); 110 + err = domain_ptrace(parent_subject->domain, child_dom); 111 + } 112 + 113 + if (!err) 114 + return 0; 115 + 116 + /* 117 + * For the ptrace_access_check case, we log the current/parent domain 118 + * and the child task. 119 + */ 120 + if (!(mode & PTRACE_MODE_NOAUDIT)) 121 + landlock_log_denial(parent_subject, &(struct landlock_request) { 122 + .type = LANDLOCK_REQUEST_PTRACE, 123 + .audit = { 124 + .type = LSM_AUDIT_DATA_TASK, 125 + .u.tsk = child, 126 + }, 127 + .layer_plus_one = parent_subject->domain->num_layers, 128 + }); 129 + 130 + return err; 89 131 } 90 132 91 133 /** ··· 134 112 */ 135 113 static int hook_ptrace_traceme(struct task_struct *const parent) 136 114 { 137 - return task_ptrace(parent, current); 115 + const struct landlock_cred_security *parent_subject; 116 + const struct landlock_ruleset *child_dom; 117 + int err; 118 + 119 + child_dom = landlock_get_current_domain(); 120 + 121 + guard(rcu)(); 122 + parent_subject = landlock_cred(__task_cred(parent)); 123 + err = domain_ptrace(parent_subject->domain, child_dom); 124 + 125 + if (!err) 126 + return 0; 127 + 128 + /* 129 + * For the ptrace_traceme case, we log the domain which is the cause of 130 + * the denial, which means the parent domain instead of the current 131 + * domain. This may look unusual because the ptrace_traceme action is a 132 + * request to be traced, but the semantic is consistent with 133 + * hook_ptrace_access_check(). 134 + */ 135 + landlock_log_denial(parent_subject, &(struct landlock_request) { 136 + .type = LANDLOCK_REQUEST_PTRACE, 137 + .audit = { 138 + .type = LSM_AUDIT_DATA_TASK, 139 + .u.tsk = current, 140 + }, 141 + .layer_plus_one = parent_subject->domain->num_layers, 142 + }); 143 + return err; 138 144 } 139 145 140 146 /** ··· 181 131 access_mask_t scope) 182 132 { 183 133 int client_layer, server_layer; 184 - struct landlock_hierarchy *client_walker, *server_walker; 134 + const struct landlock_hierarchy *client_walker, *server_walker; 185 135 186 136 /* Quick return if client has no domain */ 187 137 if (WARN_ON_ONCE(!client))