diff options
| author | Kees Cook <kees+treewide@kernel.org> | 2026-09-02 15:31:14 -0700 |
|---|---|---|
| committer | Kees Cook <kees@kernel.org> | 2026-09-04 21:37:00 -0700 |
| commit | 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d (patch) | |
| tree | c65086f9bdcd48c6360fb7cb4598bca084da1f32 /security/landlock/audit.c | |
| download | linux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.tar.gz linux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.zip | |
treewide: refresh kmalloc_obj() conversionsgrafted
This is another run of the Coccinelle script for converting kmalloc()
family of allocations to kmalloc_obj() via the existing rules in
scripts/coccinelle/api/kmalloc_objs.cocci
This catches both the set of kmalloc() uses added since the first
kmalloc_obj() conversions in v7.0 and adds a large group missed in the
first pass due to Coccinelle not interacting well with the cleanup.h
scoped_...() family of macros[1]. I worked around this with spatch's
"--macro-file" argument to a file with all the scoped_...() macros mapped
to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control
flow indicator I could find.
Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc,
riscv, and s390 with no new warnings.
Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1]
Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2]
Signed-off-by: Kees Cook <kees+treewide@kernel.org>
Diffstat (limited to 'security/landlock/audit.c')
| -rw-r--r-- | security/landlock/audit.c | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/security/landlock/audit.c b/security/landlock/audit.c new file mode 100644 index 000000000..e02963834 --- /dev/null +++ b/security/landlock/audit.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Audit helpers + * + * Copyright © 2023-2025 Microsoft Corporation + */ + +#include <linux/audit.h> +#include <linux/bitops.h> +#include <linux/landlock.h> +#include <linux/lsm_audit.h> +#include <linux/pid.h> +#include <uapi/linux/landlock.h> + +#include "access.h" +#include "audit.h" +#include "common.h" +#include "cred.h" +#include "domain.h" +#include "limits.h" +#include "log.h" + +/* + * Access-right and scope names are built from the lists shared with the trace + * events (see <linux/landlock.h>). The designated initializer places each name + * at its bit index, so the lookup stays O(1) and does not depend on the entry + * order. log_blockers() adds the "fs."/"net."/"scope." category prefix. + */ +#define _LANDLOCK_NAME_ENTRY(mask, name) [BIT_INDEX(mask)] = name + +static const char *const fs_access_strings[] = { _LANDLOCK_ACCESS_FS_NAMES }; + +static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS); + +static const char *const net_access_strings[] = { _LANDLOCK_ACCESS_NET_NAMES }; + +static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET); + +static const char *const scope_strings[] = { _LANDLOCK_SCOPE_NAMES }; + +static_assert(ARRAY_SIZE(scope_strings) == LANDLOCK_NUM_SCOPE); + +#undef _LANDLOCK_NAME_ENTRY + +static __attribute_const__ const char * +get_blocker(const enum landlock_request_type type, + const unsigned long access_bit) +{ + switch (type) { + case LANDLOCK_REQUEST_PTRACE: + WARN_ON_ONCE(access_bit != -1); + return "ptrace"; + + case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: + WARN_ON_ONCE(access_bit != -1); + return "change_topology"; + + case LANDLOCK_REQUEST_FS_ACCESS: + if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings))) + return "unknown"; + return fs_access_strings[access_bit]; + + case LANDLOCK_REQUEST_NET_ACCESS: + if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(net_access_strings))) + return "unknown"; + return net_access_strings[access_bit]; + + case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: + WARN_ON_ONCE(access_bit != -1); + return scope_strings[BIT_INDEX( + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET)]; + + case LANDLOCK_REQUEST_SCOPE_SIGNAL: + WARN_ON_ONCE(access_bit != -1); + return scope_strings[BIT_INDEX(LANDLOCK_SCOPE_SIGNAL)]; + } + + WARN_ON_ONCE(1); + return "unknown"; +} + +/* + * Returns the audit category prefix prepended to the unprefixed blocker name + * returned by get_blocker() (filesystem and network access rights, + * change_topology, and scopes). The ptrace blocker is standalone and carries + * its full name in get_blocker(), so it uses no prefix. + */ +static __attribute_const__ const char * +blocker_prefix(const enum landlock_request_type type) +{ + switch (type) { + case LANDLOCK_REQUEST_PTRACE: + return ""; + + case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: + case LANDLOCK_REQUEST_FS_ACCESS: + return "fs."; + + case LANDLOCK_REQUEST_NET_ACCESS: + return "net."; + + case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: + case LANDLOCK_REQUEST_SCOPE_SIGNAL: + return "scope."; + } + + WARN_ON_ONCE(1); + return ""; +} + +static void log_blockers(struct audit_buffer *const ab, + const enum landlock_request_type type, + const access_mask_t access) +{ + const unsigned long access_mask = access; + const char *const prefix = blocker_prefix(type); + unsigned long access_bit; + bool is_first = true; + + for_each_set_bit(access_bit, &access_mask, BITS_PER_TYPE(access)) { + audit_log_format(ab, "%s%s%s", is_first ? "" : ",", prefix, + get_blocker(type, access_bit)); + is_first = false; + } + if (is_first) + audit_log_format(ab, "%s%s", prefix, get_blocker(type, -1)); +} + +static void log_domain(struct landlock_hierarchy *const hierarchy) +{ + struct audit_buffer *ab; + + /* Ignores already logged domains. */ + if (READ_ONCE(hierarchy->log_status) == LANDLOCK_LOG_RECORDED) + return; + + /* Uses consistent allocation flags wrt common_lsm_audit(). */ + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, + AUDIT_LANDLOCK_DOMAIN); + if (!ab) + return; + + WARN_ON_ONCE(hierarchy->id == 0); + audit_log_format( + ab, + "domain=%llx status=allocated mode=enforcing pid=%d uid=%u exe=", + hierarchy->id, pid_nr(hierarchy->details->pid), + hierarchy->details->uid); + audit_log_untrustedstring(ab, hierarchy->details->exe_path); + audit_log_format(ab, " comm="); + audit_log_untrustedstring(ab, hierarchy->details->comm); + audit_log_end(ab); + + /* + * There may be race condition leading to logging of the same domain + * several times but that is OK. + */ + WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED); +} + +/** + * landlock_audit_denial - Create an audit record for a denied access request + * + * @request: Detail of the user space request. + * @youngest_denied: The youngest hierarchy node that denied the access. + * @missing: The set of denied access rights. + * @logged: Whether the denial is selected for logging, as computed by + * landlock_log_denial() (domain policy and quiet rules). + * + * Emits the record when audit is enabled and the denial is selected for + * logging. + */ +void landlock_audit_denial(const struct landlock_request *const request, + struct landlock_hierarchy *const youngest_denied, + const access_mask_t missing, const bool logged) +{ + struct audit_buffer *ab; + + if (!audit_enabled) + return; + + /* + * Skips denials the domain's policy or a quiet rule excludes from + * logging (folded into @logged by landlock_log_denial()). + */ + if (!logged) + return; + + /* Uses consistent allocation flags wrt common_lsm_audit(). */ + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, + AUDIT_LANDLOCK_ACCESS); + if (!ab) + return; + + audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id); + log_blockers(ab, request->type, missing); + audit_log_lsm_data(ab, &request->audit); + audit_log_end(ab); + + /* Logs this domain the first time it shows in log. */ + log_domain(youngest_denied); +} + +/** + * landlock_audit_free_domain - Create an audit record on domain deallocation + * + * @hierarchy: The domain's hierarchy being deallocated. + * + * Only domains which previously appeared in the audit logs are logged again. + * This is useful to know when a domain will never show again in the audit log. + * + * Called from landlock_log_free_domain(). + */ +void landlock_audit_free_domain(const struct landlock_hierarchy *const hierarchy) +{ + struct audit_buffer *ab; + + if (!audit_enabled) + return; + + /* Ignores domains that were not logged. */ + if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_RECORDED) + return; + + /* + * If logging of domain allocation succeeded, warns about failure to log + * domain deallocation to highlight unbalanced domain lifetime logs. + */ + ab = audit_log_start(audit_context(), GFP_KERNEL, + AUDIT_LANDLOCK_DOMAIN); + if (!ab) + return; + + audit_log_format(ab, "domain=%llx status=deallocated denials=%llu", + hierarchy->id, atomic64_read(&hierarchy->num_denials)); + audit_log_end(ab); +} |
