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_DOMAIN and log domain status

Asynchronously log domain information when it first denies an access.
This minimize the amount of generated logs, which makes it possible to
always log denials for the current execution since they should not
happen. These records are identified with the new AUDIT_LANDLOCK_DOMAIN
type.

The AUDIT_LANDLOCK_DOMAIN message contains:
- the "domain" ID which is described;
- the "status" which can either be "allocated" or "deallocated";
- the "mode" which is for now only "enforcing";
- for the "allocated" status, a minimal set of properties to easily
identify the task that loaded the domain's policy with
landlock_restrict_self(2): "pid", "uid", executable path ("exe"), and
command line ("comm");
- for the "deallocated" state, the number of "denials" accounted to this
domain, which is at least 1.

This requires each domain to save these task properties at creation
time in the new struct landlock_details. A reference to the PID is kept
for the lifetime of the domain to avoid race conditions when
investigating the related task. The executable path is resolved and
stored to not keep a reference to the filesystem and block related
actions. All these metadata are stored for the lifetime of the related
domain and should then be minimal. The required memory is not accounted
to the task calling landlock_restrict_self(2) contrary to most other
Landlock allocations (see related comment).

The AUDIT_LANDLOCK_DOMAIN record follows the first AUDIT_LANDLOCK_ACCESS
record for the same domain, which is always followed by AUDIT_SYSCALL
and AUDIT_PROCTITLE. This is in line with the audit logic to first
record the cause of an event, and then add context with other types of
record.

Audit event sample for a first denial:

type=LANDLOCK_ACCESS msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
type=LANDLOCK_DOMAIN msg=audit(1732186800.349:44): domain=195ba459b status=allocated mode=enforcing pid=300 uid=0 exe="/root/sandboxer" comm="sandboxer"
type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0

Audit event sample for a following denial:

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

Log domain deletion with the "deallocated" state when a domain was
previously logged. This makes it possible for log parsers to free
potential resources when a domain ID will never show again.

The number of denied access requests is useful to easily check how many
access requests a domain blocked and potentially if some of them are
missing in logs because of audit rate limiting, audit rules, or Landlock
log configuration flags (see following commit).

Audit event sample for a deletion of a domain that denied something:

type=LANDLOCK_DOMAIN msg=audit(1732186800.393:46): domain=195ba459b status=deallocated denials=2

Cc: Günther Noack <gnoack@google.com>
Acked-by: Paul Moore <paul@paul-moore.com>
Link: https://lore.kernel.org/r/20250320190717.2287696-11-mic@digikod.net
[mic: Update comment and GFP flag for landlock_log_drop_domain()]
Signed-off-by: Mickaël Salaün <mic@digikod.net>

+286 -4
+1
include/uapi/linux/audit.h
··· 147 147 #define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */ 148 148 #define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */ 149 149 #define AUDIT_LANDLOCK_ACCESS 1423 /* Landlock denial */ 150 + #define AUDIT_LANDLOCK_DOMAIN 1424 /* Landlock domain status */ 150 151 151 152 #define AUDIT_FIRST_KERN_ANOM_MSG 1700 152 153 #define AUDIT_LAST_KERN_ANOM_MSG 1799
+86 -3
security/landlock/audit.c
··· 8 8 #include <kunit/test.h> 9 9 #include <linux/audit.h> 10 10 #include <linux/lsm_audit.h> 11 + #include <linux/pid.h> 11 12 12 13 #include "audit.h" 13 14 #include "cred.h" ··· 31 30 const enum landlock_request_type type) 32 31 { 33 32 audit_log_format(ab, "%s", get_blocker(type)); 33 + } 34 + 35 + static void log_domain(struct landlock_hierarchy *const hierarchy) 36 + { 37 + struct audit_buffer *ab; 38 + 39 + /* Ignores already logged domains. */ 40 + if (READ_ONCE(hierarchy->log_status) == LANDLOCK_LOG_RECORDED) 41 + return; 42 + 43 + /* Uses consistent allocation flags wrt common_lsm_audit(). */ 44 + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, 45 + AUDIT_LANDLOCK_DOMAIN); 46 + if (!ab) 47 + return; 48 + 49 + WARN_ON_ONCE(hierarchy->id == 0); 50 + audit_log_format( 51 + ab, 52 + "domain=%llx status=allocated mode=enforcing pid=%d uid=%u exe=", 53 + hierarchy->id, pid_nr(hierarchy->details->pid), 54 + hierarchy->details->uid); 55 + audit_log_untrustedstring(ab, hierarchy->details->exe_path); 56 + audit_log_format(ab, " comm="); 57 + audit_log_untrustedstring(ab, hierarchy->details->comm); 58 + audit_log_end(ab); 59 + 60 + /* 61 + * There may be race condition leading to logging of the same domain 62 + * several times but that is OK. 63 + */ 64 + WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED); 34 65 } 35 66 36 67 static struct landlock_hierarchy * ··· 143 110 if (!is_valid_request(request)) 144 111 return; 145 112 146 - if (!audit_enabled) 147 - return; 148 - 149 113 youngest_layer = request->layer_plus_one - 1; 150 114 youngest_denied = get_hierarchy(subject->domain, youngest_layer); 115 + 116 + /* 117 + * Consistently keeps track of the number of denied access requests 118 + * even if audit is currently disabled, or if audit rules currently 119 + * exclude this record type, or if landlock_restrict_self(2)'s flags 120 + * quiet logs. 121 + */ 122 + atomic64_inc(&youngest_denied->num_denials); 123 + 124 + if (!audit_enabled) 125 + return; 151 126 152 127 /* Ignores denials after an execution. */ 153 128 if (!(subject->domain_exec & (1 << youngest_layer))) ··· 170 129 audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id); 171 130 log_blockers(ab, request->type); 172 131 audit_log_lsm_data(ab, &request->audit); 132 + audit_log_end(ab); 133 + 134 + /* Logs this domain the first time it shows in log. */ 135 + log_domain(youngest_denied); 136 + } 137 + 138 + /** 139 + * landlock_log_drop_domain - Create an audit record on domain deallocation 140 + * 141 + * @hierarchy: The domain's hierarchy being deallocated. 142 + * 143 + * Only domains which previously appeared in the audit logs are logged again. 144 + * This is useful to know when a domain will never show again in the audit log. 145 + * 146 + * Called in a work queue scheduled by landlock_put_ruleset_deferred() called 147 + * by hook_cred_free(). 148 + */ 149 + void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy) 150 + { 151 + struct audit_buffer *ab; 152 + 153 + if (WARN_ON_ONCE(!hierarchy)) 154 + return; 155 + 156 + if (!audit_enabled) 157 + return; 158 + 159 + /* Ignores domains that were not logged. */ 160 + if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_RECORDED) 161 + return; 162 + 163 + /* 164 + * If logging of domain allocation succeeded, warns about failure to log 165 + * domain deallocation to highlight unbalanced domain lifetime logs. 166 + */ 167 + ab = audit_log_start(audit_context(), GFP_KERNEL, 168 + AUDIT_LANDLOCK_DOMAIN); 169 + if (!ab) 170 + return; 171 + 172 + audit_log_format(ab, "domain=%llx status=deallocated denials=%llu", 173 + hierarchy->id, atomic64_read(&hierarchy->num_denials)); 173 174 audit_log_end(ab); 174 175 } 175 176
+7
security/landlock/audit.h
··· 36 36 37 37 #ifdef CONFIG_AUDIT 38 38 39 + void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy); 40 + 39 41 void landlock_log_denial(const struct landlock_cred_security *const subject, 40 42 const struct landlock_request *const request); 41 43 42 44 #else /* CONFIG_AUDIT */ 45 + 46 + static inline void 47 + landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy) 48 + { 49 + } 43 50 44 51 static inline void 45 52 landlock_log_denial(const struct landlock_cred_security *const subject,
+101
security/landlock/domain.c
··· 7 7 * Copyright © 2024-2025 Microsoft Corporation 8 8 */ 9 9 10 + #include <linux/cred.h> 11 + #include <linux/file.h> 12 + #include <linux/mm.h> 13 + #include <linux/path.h> 14 + #include <linux/pid.h> 15 + #include <linux/sched.h> 16 + #include <linux/uidgid.h> 17 + 10 18 #include "domain.h" 11 19 #include "id.h" 12 20 13 21 #ifdef CONFIG_AUDIT 14 22 15 23 /** 24 + * get_current_exe - Get the current's executable path, if any 25 + * 26 + * @exe_str: Returned pointer to a path string with a lifetime tied to the 27 + * returned buffer, if any. 28 + * @exe_size: Returned size of @exe_str (including the trailing null 29 + * character), if any. 30 + * 31 + * Returns: A pointer to an allocated buffer where @exe_str point to, %NULL if 32 + * there is no executable path, or an error otherwise. 33 + */ 34 + static const void *get_current_exe(const char **const exe_str, 35 + size_t *const exe_size) 36 + { 37 + const size_t buffer_size = LANDLOCK_PATH_MAX_SIZE; 38 + struct mm_struct *mm = current->mm; 39 + struct file *file __free(fput) = NULL; 40 + char *buffer __free(kfree) = NULL; 41 + const char *exe; 42 + ssize_t size; 43 + 44 + if (!mm) 45 + return NULL; 46 + 47 + file = get_mm_exe_file(mm); 48 + if (!file) 49 + return NULL; 50 + 51 + buffer = kmalloc(buffer_size, GFP_KERNEL); 52 + if (!buffer) 53 + return ERR_PTR(-ENOMEM); 54 + 55 + exe = d_path(&file->f_path, buffer, buffer_size); 56 + if (WARN_ON_ONCE(IS_ERR(exe))) 57 + /* Should never happen according to LANDLOCK_PATH_MAX_SIZE. */ 58 + return ERR_CAST(exe); 59 + 60 + size = buffer + buffer_size - exe; 61 + if (WARN_ON_ONCE(size <= 0)) 62 + return ERR_PTR(-ENAMETOOLONG); 63 + 64 + *exe_size = size; 65 + *exe_str = exe; 66 + return no_free_ptr(buffer); 67 + } 68 + 69 + /* 70 + * Returns: A newly allocated object describing a domain, or an error 71 + * otherwise. 72 + */ 73 + static struct landlock_details *get_current_details(void) 74 + { 75 + /* Cf. audit_log_d_path_exe() */ 76 + static const char null_path[] = "(null)"; 77 + const char *path_str = null_path; 78 + size_t path_size = sizeof(null_path); 79 + const void *buffer __free(kfree) = NULL; 80 + struct landlock_details *details; 81 + 82 + buffer = get_current_exe(&path_str, &path_size); 83 + if (IS_ERR(buffer)) 84 + return ERR_CAST(buffer); 85 + 86 + /* 87 + * Create the new details according to the path's length. Do not 88 + * allocate with GFP_KERNEL_ACCOUNT because it is independent from the 89 + * caller. 90 + */ 91 + details = 92 + kzalloc(struct_size(details, exe_path, path_size), GFP_KERNEL); 93 + if (!details) 94 + return ERR_PTR(-ENOMEM); 95 + 96 + memcpy(details->exe_path, path_str, path_size); 97 + WARN_ON_ONCE(current_cred() != current_real_cred()); 98 + details->pid = get_pid(task_pid(current)); 99 + details->uid = from_kuid(&init_user_ns, current_uid()); 100 + get_task_comm(details->comm, current); 101 + return details; 102 + } 103 + 104 + /** 16 105 * landlock_init_hierarchy_log - Partially initialize landlock_hierarchy 17 106 * 18 107 * @hierarchy: The hierarchy to initialize. 108 + * 109 + * The current task is referenced as the domain that is enforcing the 110 + * restriction. The subjective credentials must not be in an overridden state. 19 111 * 20 112 * @hierarchy->parent and @hierarchy->usage should already be set. 21 113 */ 22 114 int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy) 23 115 { 116 + struct landlock_details *details; 117 + 118 + details = get_current_details(); 119 + if (IS_ERR(details)) 120 + return PTR_ERR(details); 121 + 122 + hierarchy->details = details; 24 123 hierarchy->id = landlock_get_id_range(1); 124 + hierarchy->log_status = LANDLOCK_LOG_PENDING; 125 + atomic64_set(&hierarchy->num_denials, 0); 25 126 return 0; 26 127 } 27 128
+86
security/landlock/domain.h
··· 10 10 #ifndef _SECURITY_LANDLOCK_DOMAIN_H 11 11 #define _SECURITY_LANDLOCK_DOMAIN_H 12 12 13 + #include <linux/limits.h> 13 14 #include <linux/mm.h> 15 + #include <linux/path.h> 16 + #include <linux/pid.h> 14 17 #include <linux/refcount.h> 18 + #include <linux/sched.h> 19 + #include <linux/slab.h> 20 + 21 + #include "audit.h" 22 + 23 + enum landlock_log_status { 24 + LANDLOCK_LOG_PENDING = 0, 25 + LANDLOCK_LOG_RECORDED, 26 + }; 27 + 28 + /** 29 + * struct landlock_details - Domain's creation information 30 + * 31 + * Rarely accessed, mainly when logging the first domain's denial. 32 + * 33 + * The contained pointers are initialized at the domain creation time and never 34 + * changed again. Contrary to most other Landlock object types, this one is 35 + * not allocated with GFP_KERNEL_ACCOUNT because its size may not be under the 36 + * caller's control (e.g. unknown exe_path) and the data is not explicitly 37 + * requested nor used by tasks. 38 + */ 39 + struct landlock_details { 40 + /** 41 + * @pid: PID of the task that initially restricted itself. It still 42 + * identifies the same task. Keeping a reference to this PID ensures that 43 + * it will not be recycled. 44 + */ 45 + struct pid *pid; 46 + /** 47 + * @uid: UID of the task that initially restricted itself, at creation time. 48 + */ 49 + uid_t uid; 50 + /** 51 + * @comm: Command line of the task that initially restricted itself, at 52 + * creation time. Always NULL terminated. 53 + */ 54 + char comm[TASK_COMM_LEN]; 55 + /** 56 + * @exe_path: Executable path of the task that initially restricted 57 + * itself, at creation time. Always NULL terminated, and never greater 58 + * than LANDLOCK_PATH_MAX_SIZE. 59 + */ 60 + char exe_path[]; 61 + }; 62 + 63 + /* Adds 11 extra characters for the potential " (deleted)" suffix. */ 64 + #define LANDLOCK_PATH_MAX_SIZE (PATH_MAX + 11) 65 + 66 + /* Makes sure the greatest landlock_details can be allocated. */ 67 + static_assert(struct_size_t(struct landlock_details, exe_path, 68 + LANDLOCK_PATH_MAX_SIZE) <= KMALLOC_MAX_SIZE); 15 69 16 70 /** 17 71 * struct landlock_hierarchy - Node in a domain hierarchy ··· 84 30 85 31 #ifdef CONFIG_AUDIT 86 32 /** 33 + * @log_status: Whether this domain should be logged or not. Because 34 + * concurrent log entries may be created at the same time, it is still 35 + * possible to have several domain records of the same domain. 36 + */ 37 + enum landlock_log_status log_status; 38 + /** 39 + * @num_denials: Number of access requests denied by this domain. 40 + * Masked (i.e. never logged) denials are still counted. 41 + */ 42 + atomic64_t num_denials; 43 + /** 87 44 * @id: Landlock domain ID, sets once at domain creation time. 88 45 */ 89 46 u64 id; 47 + /** 48 + * @details: Information about the related domain. 49 + */ 50 + const struct landlock_details *details; 90 51 #endif /* CONFIG_AUDIT */ 91 52 }; 92 53 ··· 109 40 110 41 int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy); 111 42 43 + static inline void 44 + landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy) 45 + { 46 + if (WARN_ON_ONCE(!hierarchy || !hierarchy->details)) 47 + return; 48 + 49 + put_pid(hierarchy->details->pid); 50 + kfree(hierarchy->details); 51 + } 52 + 112 53 #else /* CONFIG_AUDIT */ 113 54 114 55 static inline int 115 56 landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy) 116 57 { 117 58 return 0; 59 + } 60 + 61 + static inline void 62 + landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy) 63 + { 118 64 } 119 65 120 66 #endif /* CONFIG_AUDIT */ ··· 146 62 while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) { 147 63 const struct landlock_hierarchy *const freeme = hierarchy; 148 64 65 + landlock_log_drop_domain(hierarchy); 66 + landlock_free_hierarchy_details(hierarchy); 149 67 hierarchy = hierarchy->parent; 150 68 kfree(freeme); 151 69 }
+3
security/landlock/ruleset.c
··· 521 521 * @parent: Parent domain. 522 522 * @ruleset: New ruleset to be merged. 523 523 * 524 + * The current task is requesting to be restricted. The subjective credentials 525 + * must not be in an overridden state. cf. landlock_init_hierarchy_log(). 526 + * 524 527 * Returns the intersection of @parent and @ruleset, or returns @parent if 525 528 * @ruleset is empty, or returns a duplicate of @ruleset if @parent is empty. 526 529 */
+2 -1
security/landlock/ruleset.h
··· 17 17 #include <linux/workqueue.h> 18 18 19 19 #include "access.h" 20 - #include "domain.h" 21 20 #include "limits.h" 22 21 #include "object.h" 22 + 23 + struct landlock_hierarchy; 23 24 24 25 /** 25 26 * struct landlock_layer - Access rights for a given layer