From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp-8fa8.mail.infomaniak.ch (smtp-8fa8.mail.infomaniak.ch [83.166.143.168]) (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 86E79357D05 for ; Wed, 22 Jul 2026 17:12:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=83.166.143.168 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784740352; cv=none; b=ZGt3LGjp1FFygwXTvgIsq3MZPEdNTHQaArVuYVl+iehKfqeMQsGr8DhbrFquv8pJ0rKqggEFsv3uhSTkCjGX6uDVyKHd3RDHhCvIBW4PkhkJeUJ8dUW6UkAplr1eRnY2+5vwX7EdBZd9C2ES1HDHrGMmELzFa+vXz2wMcepR3is= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784740352; c=relaxed/simple; bh=V/UXZ21XuPsHxeG8IjEZ4eCv4wV9TpKP0N3UB08vHI0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=svuoIeJJpHulnWif7IyVWmEyozA6pv/apDd9yy9DPQCIY1sh18jde3oX6ck/LYbVfnZZzN05p4M9JJWrwSJc6fCN/4f8BM18ZKBtkM3ZriD2mLqWe3UIVrq8bA4OpFM+ro5eva2TDAsP2/f21v7Gz/oHDKecyj6QCwv6GTW8zaE= 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=1t0V+lj9; arc=none smtp.client-ip=83.166.143.168 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="1t0V+lj9" Received: from smtp-3-0000.mail.infomaniak.ch (smtp-3-0000.mail.infomaniak.ch [10.4.36.107]) by smtp-3-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4h515d2shFz114r; Wed, 22 Jul 2026 19:12:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=digikod.net; s=20191114; t=1784740341; bh=si9RCEY7YwAuaD50RCv61SBQjKAj2oGLNMu4ja/OP64=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=1t0V+lj9V6iajRpP6tNEhNo4dJ4BRrpr2/zeqCmHTXgbT6oEkKCTSyz76FYecg3fR lDNymEZtpwvyVP+7Y5KIu8Ish8/UutRkA7opUmt+bfNLaDKJUkHObG5Lc5AseovMZh gSh14VQG5njDx5PpCMMSI/C+NsoZPzn77DDv4GDw= Received: from unknown by smtp-3-0000.mail.infomaniak.ch (Postfix) with ESMTPA id 4h515c1QMrz21n; Wed, 22 Jul 2026 19:12:20 +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 06/20] landlock: Consolidate access-right and scope names in a shared header Date: Wed, 22 Jul 2026 19:11:38 +0200 Message-ID: <20260722171159.2776765-7-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-security-module@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 Audit formats denial records with per-right name strings. A following commit adds trace events that print the same access and scope masks with __print_flags() and need the same names, but a trace event header cannot include Landlock-internal headers, so the names cannot be shared from the logging unit. Define the filesystem, network, and scope names once, as the _LANDLOCK_ACCESS_FS_NAMES, _LANDLOCK_ACCESS_NET_NAMES, and _LANDLOCK_SCOPE_NAMES lists in the public Landlock header. Each entry is a _LANDLOCK_NAME_ENTRY() the consumer expands: audit maps it to a "[bit] = name" array slot for an O(1) lookup, the trace events map it to a __print_flags() { mask, name } pair. The bit value comes only from the LANDLOCK_* UAPI constant each entry references, so every bit-to-name mapping has a single source and does not depend on entry order. The shared names are unprefixed; blocker_prefix() prepends the fs./net./scope. category for audit records, so the scope names move from inline literals to the shared table too. Audit records are unchanged. No functional change. Cc: Günther Noack Cc: Tingmao Wang Signed-off-by: Mickaël Salaün --- Changes since v2: - New patch. --- MAINTAINERS | 1 + include/linux/landlock.h | 55 +++++++++++++++++++++++++ security/landlock/audit.c | 84 ++++++++++++++++++++++++--------------- 3 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 include/linux/landlock.h diff --git a/MAINTAINERS b/MAINTAINERS index a674e36529f7..91de7e3c2836 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14598,6 +14598,7 @@ F: Documentation/admin-guide/LSM/landlock.rst F: Documentation/security/landlock.rst F: Documentation/userspace-api/landlock.rst F: fs/ioctl.c +F: include/linux/landlock.h F: include/uapi/linux/landlock.h F: samples/landlock/ F: security/landlock/ diff --git a/include/linux/landlock.h b/include/linux/landlock.h new file mode 100644 index 000000000000..cabe6c784833 --- /dev/null +++ b/include/linux/landlock.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Public types and definitions + * + * Copyright © 2016-2026 Mickaël Salaün + * Copyright © 2026 Cloudflare, Inc. + */ + +#ifndef _LINUX_LANDLOCK_H +#define _LINUX_LANDLOCK_H + +#include + +/* + * Access-right and scope names, shared between the audit records (get_blocker() + * in security/landlock/audit.c) and the trace events + * (include/trace/events/landlock.h). A consumer defines + * _LANDLOCK_NAME_ENTRY(mask, name) before expanding a list and undefines it + * afterwards: audit maps each entry to a "[bit] = name" slot for O(1) lookup, + * the trace events map it to a __print_flags() { mask, name } pair. The bit + * value lives only in the LANDLOCK_* UAPI constant each entry references. + * Names are unprefixed; audit prepends the "fs."/"net."/"scope." category. + */ +#define _LANDLOCK_ACCESS_FS_NAMES \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_EXECUTE, "execute"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_WRITE_FILE, "write_file"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_READ_FILE, "read_file"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_READ_DIR, "read_dir"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_REMOVE_DIR, "remove_dir"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_REMOVE_FILE, "remove_file"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_CHAR, "make_char"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_DIR, "make_dir"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_REG, "make_reg"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_SOCK, "make_sock"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_FIFO, "make_fifo"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_BLOCK, "make_block"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_SYM, "make_sym"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_REFER, "refer"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_TRUNCATE, "truncate"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_IOCTL_DEV, "ioctl_dev"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_RESOLVE_UNIX, "resolve_unix") + +#define _LANDLOCK_ACCESS_NET_NAMES \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_BIND_TCP, "bind_tcp"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_CONNECT_TCP, "connect_tcp"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_BIND_UDP, "bind_udp"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP, \ + "connect_send_udp") + +#define _LANDLOCK_SCOPE_NAMES \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, \ + "abstract_unix_socket"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_SIGNAL, "signal") + +#endif /* _LINUX_LANDLOCK_H */ diff --git a/security/landlock/audit.c b/security/landlock/audit.c index 32260e7cbfe9..e02963834e48 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -19,38 +20,28 @@ #include "limits.h" #include "log.h" -static const char *const fs_access_strings[] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute", - [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = "fs.write_file", - [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = "fs.read_file", - [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = "fs.read_dir", - [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = "fs.remove_dir", - [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_FILE)] = "fs.remove_file", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_CHAR)] = "fs.make_char", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_DIR)] = "fs.make_dir", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = "fs.make_reg", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SOCK)] = "fs.make_sock", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_FIFO)] = "fs.make_fifo", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_BLOCK)] = "fs.make_block", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SYM)] = "fs.make_sym", - [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer", - [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate", - [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev", - [BIT_INDEX(LANDLOCK_ACCESS_FS_RESOLVE_UNIX)] = "fs.resolve_unix", -}; +/* + * Access-right and scope names are built from the lists shared with the trace + * events (see ). 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[] = { - [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp", - [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp", - [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_UDP)] = "net.bind_udp", - [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)] = - "net.connect_send_udp", -}; +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) @@ -62,7 +53,7 @@ get_blocker(const enum landlock_request_type type, case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: WARN_ON_ONCE(access_bit != -1); - return "fs.change_topology"; + return "change_topology"; case LANDLOCK_REQUEST_FS_ACCESS: if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings))) @@ -76,32 +67,63 @@ get_blocker(const enum landlock_request_type type, case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: WARN_ON_ONCE(access_bit != -1); - return "scope.abstract_unix_socket"; + return scope_strings[BIT_INDEX( + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET)]; case LANDLOCK_REQUEST_SCOPE_SIGNAL: WARN_ON_ONCE(access_bit != -1); - return "scope.signal"; + 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", is_first ? "" : ",", + 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", get_blocker(type, -1)); + audit_log_format(ab, "%s%s", prefix, get_blocker(type, -1)); } static void log_domain(struct landlock_hierarchy *const hierarchy) -- 2.54.0