Linux Security Modules development
 help / color / mirror / Atom feed
* [PATCH v2] landlock: Set audit_net.sk for socket access checks
From: Mickaël Salaün @ 2026-06-12 17:27 UTC (permalink / raw)
  To: Günther Noack
  Cc: Mickaël Salaün, linux-security-module, Tingmao Wang,
	Matthieu Buffet, stable

Set audit_net.sk in current_check_access_socket() to provide the socket
object to audit_log_lsm_data().  This makes Landlock consistent with
AppArmor, which always sets .sk for socket operations, and with
SELinux's generic socket permission checks.

The socket's local and foreign address information (laddr, lport, faddr,
fport) is logged by the shared lsm_audit.c infrastructure when the
socket has bound or connected state.  Fields with zero values are
suppressed by print_ipv4_addr()/print_ipv6_addr(), so the audit output
is unchanged for the common case of bind denials on unbound sockets.
For connect denials after a prior bind, the bound local address (laddr,
lport) appears before the existing sockaddr fields (daddr, dest).

No existing fields are removed or reordered, and the new field names
(laddr, lport, faddr, fport) are standard audit fields already emitted
by other LSMs through the same lsm_audit.c code path.

Add a connect_tcp_bound audit test that binds to an allowed port and
then connects to a denied one, verifying that the denial record reports
laddr/lport from the bound socket in addition to the connect
destination.

Cc: Günther Noack <gnoack@google.com>
Cc: Tingmao Wang <m@maowtm.org>
Cc: stable@vger.kernel.org
Fixes: 9f74411a40ce ("landlock: Log TCP bind and connect denials")
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---

Changes since v1:
https://lore.kernel.org/r/20260406143717.1815792-11-mic@digikod.net
- Move the new socket-audit coverage into the network test fixture,
  which sets up an isolated network namespace with a configured
  loopback interface; the previous location ran without a network
  namespace (reported by Tingmao Wang).  Cover the enriched laddr/lport
  via a connect-after-bind denial.
---
 security/landlock/net.c                     |  1 +
 tools/testing/selftests/landlock/net_test.c | 62 +++++++++++++++++++++
 2 files changed, 63 insertions(+)

diff --git a/security/landlock/net.c b/security/landlock/net.c
index c368649985c5..a38bdfcffc22 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -198,6 +198,7 @@ static int current_check_access_socket(struct socket *const sock,
 		return 0;
 
 	audit_net.family = address->sa_family;
+	audit_net.sk = sock->sk;
 	landlock_log_denial(subject,
 			    &(struct landlock_request){
 				    .type = LANDLOCK_REQUEST_NET_ACCESS,
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 4c528154ea92..0c256e7c8675 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -2026,4 +2026,66 @@ TEST_F(audit, connect)
 	EXPECT_EQ(0, close(sock_fd));
 }
 
+static int matches_log_tcp_bound(int audit_fd, const char *const addr,
+				 __u16 lport, __u16 dport)
+{
+	static const char log_template[] = REGEX_LANDLOCK_PREFIX
+		" blockers=net\\.connect_tcp laddr=%s lport=%u daddr=%s dest=%u$";
+	/* Slack for two addresses and two port numbers. */
+	char log_match[sizeof(log_template) + 40];
+	int log_match_len;
+
+	log_match_len = snprintf(log_match, sizeof(log_match), log_template,
+				 addr, lport, addr, dport);
+	if (log_match_len > sizeof(log_match))
+		return -E2BIG;
+
+	return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
+				  NULL);
+}
+
+/*
+ * After a bind() to an allowed port, a denied connect must report laddr/lport
+ * from the bound socket (made available through audit_net.sk) in addition to
+ * the connect sockaddr's daddr/dest.
+ */
+TEST_F(audit, connect_tcp_bound)
+{
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	const struct landlock_net_port_attr rule_bind = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+		.port = self->srv0.port,
+	};
+	struct service_fixture srv_remote;
+	struct audit_records records;
+	int ruleset_fd, sock_fd;
+
+	/* Uses a second port as the denied connect target. */
+	ASSERT_EQ(0, set_service(&srv_remote, variant->prot, 1));
+
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+				       &rule_bind, 0));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	sock_fd = socket_variant(&self->srv0);
+	ASSERT_LE(0, sock_fd);
+	EXPECT_EQ(0, bind_variant(sock_fd, &self->srv0));
+	EXPECT_EQ(-EACCES, connect_variant(sock_fd, &srv_remote));
+	EXPECT_EQ(0, matches_log_tcp_bound(self->audit_fd, variant->addr,
+					   self->srv0.port, srv_remote.port));
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(1, records.domain);
+
+	EXPECT_EQ(0, close(sock_fd));
+}
+
 TEST_HARNESS_MAIN

base-commit: d8dfb4c7faa87c3e41a8678f38f136c2c7c036fa
-- 
2.54.0


^ permalink raw reply related

* [PATCH 03/10] docs/zh_CN: add LSM/LoadPin Chinese translation
From: Yan Zhu @ 2026-06-12 16:03 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/LoadPin.rst into Chinese.

Update the translation through commit 0860b72d535f
("security/loadpin: Update the changing interface in the source code.")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/LoadPin.rst         | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/LoadPin.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/LoadPin.rst b/Documentation/translations/zh_CN/admin-guide/LSM/LoadPin.rst
new file mode 100644
index 000000000000..d75a06f515f6
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/LoadPin.rst
@@ -0,0 +1,33 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/LoadPin.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+
+=======
+LoadPin
+=======
+
+LoadPin 是一种 Linux 安全模块(LSM),保证所有内核加载的文件(模块、固件等)
+均来自同一文件系统,并且该文件系统应当以只读设备(如 dm-verity 或 CDROM)为后
+端存储。这使得在拥有经过验证和/或不可更改的文件系统的系统上,能够在不对每个文
+件单独签名的前提下,强制模块和固件的加载限制。
+
+该 LSM 在编译时通过 ``CONFIG_SECURITY_LOADPIN`` 进行选择,并且可以在启动时通
+过内核命令行参数 ``loadpin.enforce`` 进行控制。默认情况下该功能是启用的,亦可
+在启动时通过 ``loadpin.enforce=0`` 将其关闭。
+
+LoadPin 会在检测到首个文件被加载时开始生效。如果承载该文件系统的块设备不是只
+读的,系统会创建一个 sysctl 条目 ``/proc/sys/kernel/loadpin/enabled`` 用于切
+换锁定功能。可写的文件系统意味着锁定功能亦可被修改,但通过 sysctl 可以在可
+写文件系统上便捷地进行测试。
+
+也可以使用内核命令行参数 ``loadpin.exclude`` 将特定文件类型从 LoadPin 中排除。
+默认情况下所有文件均被包含,但可以通过
+``loadpin.exclude=kernel-module,kexec-image`` 等方式排除相应类型。这允许在使
+用 ``CONFIG_MODULE_SIG``、``CONFIG_KEXEC_VERIFY_SIG`` 等机制对内核模块和内核
+镜像进行校验的同时,仍然利用 LoadPin 保护其他内核加载文件的完整性。有效文件类
+型的完整列表可在 ``include/linux/kernel_read_file.h`` 中的
+``kernel_read_file_str`` 找到。
-- 
2.43.0


^ permalink raw reply related

* [PATCH 08/10] docs/zh_CN: add LSM/SafeSetID Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/SafeSetID.rst into Chinese.

Update the translation through commit c34921670736
("Documentation: Fix admin-guide typos")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/SafeSetID.rst       | 82 +++++++++++++++++++
 1 file changed, 82 insertions(+)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/SafeSetID.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/SafeSetID.rst b/Documentation/translations/zh_CN/admin-guide/LSM/SafeSetID.rst
new file mode 100644
index 000000000000..3f96df3ed776
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/SafeSetID.rst
@@ -0,0 +1,82 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/SafeSetID.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+
+=========
+SafeSetID
+=========
+
+SafeSetID 是一个 LSM 模块,用于对 setid 系列系统调用进行门控,限制 UID/GID
+的转变只能在系统范围的白名单中批准的 UID/GID 之间进行。这些限制还禁止给定的
+UID/GID 获得与 ``CAP_SET{U/G}ID`` 关联的辅助特权,例如允许用户设置用户命名空
+间的 UID/GID 映射。
+
+背景
+====
+在缺少文件能力的情况下,需要切换到其他用户的进程必须具备 ``CAP_SETUID`` 权限。
+``CAP_SETUID`` 只授予以 root 身份运行的程序或显式获得 ``CAP_SETUID`` 运行时
+能力的非 root 程序。相较于文件能力,通常更推荐使用 Linux 运行时能力,因为使
+用文件能力以提升的权限运行程序会带来潜在的安全风险——任何拥有该文件访问权限的
+用户都可以通过 ``exec()`` 运行该程序来获得提升的特权。
+
+虽然可以通过为完整的 ``CAP_SET{U/G}ID`` 能力给进程树授予权限来实现,但这与在
+非 root 用户下运行进程树的目标相冲突。尤其 ``CAP_SETUID`` 允许切换到系统上任
+何用户,包括 root,这在很多场景中过于强大。实际中多数程序仅调用 ``setuid()``
+降低特权,而非提升特权。Linux 并未提供通用机制限制用户通过 ``setuid()`` 能切
+换到的 UID 范围,除非允许其切换到系统上任意用户。SafeSetID LSM 正是为了解决
+这一问题。
+
+主要使用场景是允许非 root 程序在不拥有完整 ``CAP_SETUID`` 能力的情况下,安全
+地切换到其他非受信任的 UID。该非 root 程序仍需 ``CAP_SETUID`` 才能执行任何转
+变,但SafeSetID 施加的额外限制,使其成为 ``CAP_SETUID`` 的“安全版”,防止其进
+行未授权操作(如切换到 UID 0 或创建/进入新的用户命名空间)。这为系统服务提供
+基于UID 的沙箱化提供了可能,而无需在大量非 root 程序上分配完整的
+``CAP_SETUID``。
+
+其他已考虑的方案
+================
+
+在用户空间解决此问题
+--------------------
+可以通过在用户空间完全移除 setid 能力并使用特权帮助程序来完成进程的 UID/GID
+转换。然而,这会影响大量与进程生成相关的语义,如 ``fork()`` 后不立即
+``exec()`` 的行为、父进程自定义环境变量或命令行参数、以及文件句柄跨
+``fork()/exec()`` 的继承等。因此,此类方案对依赖特定进程生成语义的现有项目
+支持度较低。
+
+使用用户命名空间
+----------------
+另一种思路是在独立的用户命名空间中运行进程树,并在该命名空间内授予 setid 能力。
+这样,进程可以在自己的命名空间内自由切换 UID/GID,但只能映射到系统范围白名单
+中的 UID/GID。遗憾的是,用户命名空间往往需要与其他命名空间配合使用,例如网络
+或 PID 命名空间,否则会导致失去 ``CAP_NET_ADMIN`` 等关键能力,限制了实际可用
+性。
+
+使用已有 LSM
+------------
+当前树中没有任何其他 LSM 能够对 setid 转换进行门控,也没有实现
+``security_task_fix_setuid`` 钩子。SELinux 对此钩子声明:
+"由于setuid仅影响当前进程,并且由于SELinux的权限控制不基于Linux标识属性。
+因此,SELinux不需要控制此操作。"
+
+使用方法
+========
+SafeSetID 在 ``securityfs`` 中通过写入 ``safesetid/uid_allowlist_policy``
+与 ``safesetid/gid_allowlist_policy`` 文件来配置策略。策略的格式为
+``<UID>:<UID>`` 或 ``<GID>:<GID>``(使用十进制数字),并以换行符结束,例如
+``123:456\n``。写入空字符串 ``""`` 可清空策略。为特定 UID/GID 配置策略后,将
+阻止该 UID/GID 获得 ``CAP_SET{U/G}ID`` 相关的辅助特权,例如设置用户命名空间
+UID/GID 映射。
+
+GID 策略与 ``setgroups()``
+==========================
+在 v5.9 中已加入对 ``CAP_SETGID`` 限制的支持,与之前对 ``CAP_SETUID`` 的处理
+相同。然而,为了兼容用户空间常见的沙箱化代码规范,目前允许具有 ``CAP_SETGID``
+限制的进程调用任意 ``setgroups()``。这意味着在这些 ``setgroups()`` 限制策略
+检查代码就位之前,**当前的 GID 策略并未提供任何有意义的安全保障**。
+``setgroups()`` 的限制将在未来版本中加入策略检查代码后真正生效,该代码将依赖
+于 v5.9 中加入的 GID 策略配置代码。
-- 
2.43.0


^ permalink raw reply related

* [PATCH 03/10] docs/zh_CN: add LSM/LoadPin Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/LoadPin.rst into Chinese.

Update the translation through commit 0860b72d535f
("security/loadpin: Update the changing interface in the source code.")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/LoadPin.rst         | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/LoadPin.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/LoadPin.rst b/Documentation/translations/zh_CN/admin-guide/LSM/LoadPin.rst
new file mode 100644
index 000000000000..d75a06f515f6
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/LoadPin.rst
@@ -0,0 +1,33 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/LoadPin.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+
+=======
+LoadPin
+=======
+
+LoadPin 是一种 Linux 安全模块(LSM),保证所有内核加载的文件(模块、固件等)
+均来自同一文件系统,并且该文件系统应当以只读设备(如 dm-verity 或 CDROM)为后
+端存储。这使得在拥有经过验证和/或不可更改的文件系统的系统上,能够在不对每个文
+件单独签名的前提下,强制模块和固件的加载限制。
+
+该 LSM 在编译时通过 ``CONFIG_SECURITY_LOADPIN`` 进行选择,并且可以在启动时通
+过内核命令行参数 ``loadpin.enforce`` 进行控制。默认情况下该功能是启用的,亦可
+在启动时通过 ``loadpin.enforce=0`` 将其关闭。
+
+LoadPin 会在检测到首个文件被加载时开始生效。如果承载该文件系统的块设备不是只
+读的,系统会创建一个 sysctl 条目 ``/proc/sys/kernel/loadpin/enabled`` 用于切
+换锁定功能。可写的文件系统意味着锁定功能亦可被修改,但通过 sysctl 可以在可
+写文件系统上便捷地进行测试。
+
+也可以使用内核命令行参数 ``loadpin.exclude`` 将特定文件类型从 LoadPin 中排除。
+默认情况下所有文件均被包含,但可以通过
+``loadpin.exclude=kernel-module,kexec-image`` 等方式排除相应类型。这允许在使
+用 ``CONFIG_MODULE_SIG``、``CONFIG_KEXEC_VERIFY_SIG`` 等机制对内核模块和内核
+镜像进行校验的同时,仍然利用 LoadPin 保护其他内核加载文件的完整性。有效文件类
+型的完整列表可在 ``include/linux/kernel_read_file.h`` 中的
+``kernel_read_file_str`` 找到。
-- 
2.43.0


^ permalink raw reply related

* [PATCH 10/10] docs/zh_CN: add LSM/landlock Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/landlock.rst into Chinese.

Update the translation through commit de4b09abf088
("landlock: Document audit blocker field format")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/landlock.rst        | 169 ++++++++++++++++++
 1 file changed, 169 insertions(+)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/landlock.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/landlock.rst b/Documentation/translations/zh_CN/admin-guide/LSM/landlock.rst
new file mode 100644
index 000000000000..7c465ce1c774
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/landlock.rst
@@ -0,0 +1,169 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. Copyright © 2025 Microsoft Corporation
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/landlock.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+
+========================
+Landlock:系统范围的管理
+========================
+
+:Author: Mickaël Salaün
+:Date: 2026年1月
+
+Landlock 可以利用审计框架记录事件。
+
+用户空间文档位于:Documentation/userspace-api/landlock.rst。
+
+审计
+====
+
+默认情况下,若启用了 ``audit``,沙箱程序的被拒访问请求会被记录。此默认行为可
+通过 ``sys_landlock_restrict_self()`` 标志更改(参见
+Documentation/userspace-api/landlock.rst)。审计规则也可以屏蔽 Landlock 日志。
+Landlock 能生成两种审计记录类型。
+
+记录类型
+--------
+
+AUDIT_LANDLOCK_ACCESS
+    此记录标识对内核资源的拒绝访问请求。``domain`` 字段指示阻止该请求的
+    域 ID。``blockers`` 字段列出导致拒绝的原因(逗号分隔),随后字段标识
+    内核对象(类似 SELinux)。一次审计事件可能出现多个此类记录。
+
+    示例:同一事件中两条文件链接请求记录::
+
+        domain=195ba459b blockers=fs.refer path="/usr/bin" dev="vda2" ino=351
+        domain=195ba459b blockers=fs.make_reg,fs.refer path="/usr/local" dev="vda2" ino=365
+
+    ``blockers`` 字段使用点分前缀标识限制类型:
+
+    **fs.*** – 文件系统访问权(ABI 1+):
+        - fs.execute, fs.write_file, fs.read_file, fs.read_dir
+        - fs.remove_dir, fs.remove_file
+        - fs.make_char, fs.make_dir, fs.make_reg, fs.make_sock
+        - fs.make_fifo, fs.make_block, fs.make_sym
+        - fs.refer (ABI 2+)
+        - fs.truncate (ABI 3+)
+        - fs.ioctl_dev (ABI 5+)
+
+    **net.*** – 网络访问权(ABI 4+):
+        - net.bind_tcp – TCP 端口绑定被拒绝
+        - net.connect_tcp – TCP 连接被拒绝
+
+    **scope.*** – IPC 范围限制(ABI 6+):
+        - scope.abstract_unix_socket – 抽象 UNIX 套接字连接被拒绝
+        - scope.signal – 信号发送被拒绝
+
+    当多个访问权缺失时,可出现多个 ``blockers``(逗号分隔),例如缺少
+    ``make_reg`` 与 ``refer`` 权限时会显示 ``blockers=fs.make_reg,fs.refer``。
+
+    对象标识字段(路径、设备、inode 等)根据被阻止的访问类型提供上下文信息。
+
+AUDIT_LANDLOCK_DOMAIN
+    此记录描述 Landlock 域的状态。``status`` 字段为 ``allocated`` 或
+    ``deallocated``。
+
+    ``allocated`` 状态随同首条 ``AUDIT_LANDLOCK_ACCESS`` 记录出现,
+    提供以下信息:
+
+    - 域 ID(``domain``)
+    - 强制模式(``mode``)
+    - 创建域的 PID(``pid``)
+    - 创建域的 UID(``uid``)
+    - 创建域的可执行路径(``exe``)
+    - 创建域的命令行(``comm``)
+
+    示例::
+
+        domain=195ba459b status=allocated mode=enforcing pid=300 uid=0 ...
+
+    ``deallocated`` 为单独事件,表示域释放。此后该域 ID 不会在系统生命周期中
+    再次使用。``deallocated`` 记录包含域 ID 与 ``denials`` 字段,后者统计该
+    域被拒绝的请求数。
+
+    示例::
+
+        domain=195ba459b status=deallocated denials=3
+
+事件示例
+--------
+
+以下为两组审计日志示例(含序列号)。
+
+示例 1:沙箱程序 ``kill`` 试图向 init 进程发送信号,
+被 ``scope.signal`` 限制拒绝::
+
+  $ LL_FS_RO=/ LL_FS_RW=/ LL_SCOPED=s LL_FORCE_LOG=1 ./sandboxer kill 1
+
+此命令生成两个事件,每个事件都由一个时间戳后跟一个唯一的序列号标识
+(``msg=audit(1729738800.268:30)``)。第一个事件(序列号为“30”)包含 4 条记
+录。第一条记录(``type=LANDLOCK_ACCESS``)显示域 `1a6fdc66f` 拒绝了访问。拒
+绝的原因是信号范围限制(``blockers=scope.signal``)。本应接收此信号的进程是
+init 进程(``opid=1 ocomm="systemd"``)。
+
+第二个记录(``type=LANDLOCK_DOMAIN``) 描述了(``status=allocated``)域
+`1a6fdc66f`。此域由进程 ``286`` 创建,该进程执行了由 root 用户启动的
+`/root/sandboxer` 程序。
+
+第三个记录(``type=SYSCALL``)描述了系统调用、其提供的参数、其结果
+``success=no exit=-1`` 以及调用它的进程。
+
+第四个记录(``type=PROCTITLE``)以十六进制值的形式显示命令名称。这可以通过执
+行 ``python -c 'print(bytes.fromhex("6B696C6C0031"))'`` 进行转换。
+
+最后一条记录(``type=LANDLOCK_DOMAIN``)也是第二个事件(序列号“31”)中的唯一
+一条记录。它并非与直接的用户空间操作相关,而是异步操作以释放与 Landlock 域相
+关的资源(``status=deallocated``)。这有助于了解后续的日志将不再涉及域
+`1a6fdc66f`。此记录还总结了该域拒绝的请求数量(``denials=1``),以及这些请求
+是否被记录。
+
+
+.. code-block::
+
+  type=LANDLOCK_ACCESS msg=audit(1729738800.268:30): domain=1a6fdc66f blockers=scope.signal opid=1 ocomm="systemd"
+  type=LANDLOCK_DOMAIN msg=audit(1729738800.268:30): domain=1a6fdc66f status=allocated mode=enforcing pid=286 uid=0 exe="/root/sandboxer" comm="sandboxer"
+  type=SYSCALL msg=audit(1729738800.268:30): arch=c000003e syscall=62 success=no exit=-1 [..] ppid=272 pid=286 auid=0 uid=0 gid=0 [...] comm="kill" [...]
+  type=PROCTITLE msg=audit(1729738800.268:30): proctitle=6B696C6C0031
+  type=LANDLOCK_DOMAIN msg=audit(1729738800.324:31): domain=1a6fdc66f status=deallocated denials=1
+
+
+示例 2:文件系统访问控制示例::
+
+    $ LL_FS_RO=/ LL_FS_RW=/tmp LL_FORCE_LOG=1 ./sandboxer sh -c "echo > /etc/passwd"
+
+相关的审计日志包含由同一个域 `1a6fdc679` 创建的 3 个不同事件(序列号 33、34
+和 35)的 8 条记录::
+
+  type=LANDLOCK_ACCESS msg=audit(1729738800.221:33): domain=1a6fdc679 blockers=fs.write_file path="/dev/tty" dev="devtmpfs" ino=9
+  type=LANDLOCK_DOMAIN msg=audit(1729738800.221:33): domain=1a6fdc679 status=allocated mode=enforcing pid=289 uid=0 exe="/root/sandboxer" comm="sandboxer"
+  type=SYSCALL msg=audit(1729738800.221:33): arch=c000003e syscall=257 success=no exit=-13 [...] ppid=272 pid=289 auid=0 uid=0 gid=0 [...] comm="sh" [...]
+  type=PROCTITLE msg=audit(1729738800.221:33): proctitle=7368002D63006563686F203E202F6574632F706173737764
+  type=LANDLOCK_ACCESS msg=audit(1729738800.221:34): domain=1a6fdc679 blockers=fs.write_file path="/etc/passwd" dev="vda2" ino=143821
+  type=SYSCALL msg=audit(1729738800.221:34): arch=c000003e syscall=257 success=no exit=-13 [...] ppid=272 pid=289 auid=0 uid=0 gid=0 [...] comm="sh" [...]
+  type=PROCTITLE msg=audit(1729738800.221:34): proctitle=7368002D63006563686F203E202F6574632F706173737764
+  type=LANDLOCK_DOMAIN msg=audit(1729738800.261:35): domain=1a6fdc679 status=deallocated denials=2
+
+
+过滤审计日志
+------------
+
+如果审计日志数量过多,可通过以下两种方式过滤噪声:
+
+- 使用 ``sys_landlock_restrict_self()`` 的标志对沙箱程序进行配置;
+- 或使用审计规则(参见 :manpage:`auditctl(8)`)过滤。
+
+补充文档
+--------
+
+* `Linux Audit Documentation`_
+* Documentation/userspace-api/landlock.rst
+* Documentation/security/landlock.rst
+* https://landlock.io
+
+.. 链接
+.. _Linux Audit Documentation:
+   https://github.com/linux-audit/audit-documentation/wiki
-- 
2.43.0


^ permalink raw reply related

* [PATCH 09/10] docs/zh_CN: add LSM/ipe Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/ipe.rst into Chinese.

Update the translation through commit d7ba853c0e47
("ipe: Update documentation for script enforcement")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/ipe.rst             | 723 ++++++++++++++++++
 1 file changed, 723 insertions(+)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/ipe.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/ipe.rst b/Documentation/translations/zh_CN/admin-guide/LSM/ipe.rst
new file mode 100644
index 000000000000..08af2d03d400
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/ipe.rst
@@ -0,0 +1,723 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/ipe.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+==================================
+Integrity Policy Enforcement (IPE)
+==================================
+
+.. NOTE::
+
+   本文档面向管理员、系统构建者或尝试使用 IPE 的个人。如需面向开发者的 IPE 文
+   档,请参阅 :doc:`设计文档 </security/ipe>`。
+
+概览
+----
+
+Integrity Policy Enforcement (IPE) 是一种 Linux 安全模块,采用与传统访问控制
+互补的方式。不同于依赖标签和路径进行决策的传统访问控制机制,IPE 关注系统组件
+固有的不可变安全属性。这些属性是系统组件的基本特征或特性,无法被更改,从而为
+安全决策提供一致且可靠的基础。
+
+具体而言,在 IPE 的语境中,系统组件主要指文件或这些文件所在的设备。但这只是个
+起点。系统组件的概念是灵活的,可以随着系统演进而扩展以涵盖新的元素。不可变属
+性包括文件的来源,它随时间保持不变且不可更改。例如,IPE 策略可以被设计为信任
+来自 initramfs 的文件。由于 initramfs 通常由引导加载程序验证,其中的文件被认
+为是可信的;因而 "文件来自initramfs" 成为 IPE 考量的不可变属性。
+
+不可变属性的概念还延伸到文件来源上启用的安全特性,例如 dm-verity 或
+fs-verity,它们提供了完整性与信任的附加层。例如,IPE 允许定义信任来自
+dm-verity 保护设备的文件的策略。dm-verity 通过提供可验证且不可变的内容状态来
+确保整个设备的完整性。类似地,fs-verity 提供文件系统级别的完整性检查,允许
+IPE 强制执行信任受 fs-verity 保护的文件的策略。这两项特性一旦建立便无法关闭,
+因此被视为不可变属性。这些示例展示了 IPE 如何利用不可变属性(如文件来源及其完
+整性保护机制)来进行访问控制决策。
+
+具体而言,IPE 策略通过依据策略中定义的参考值评估安全属性,来实施严格的访问控
+制。评估既可以基于安全属性的存在与否(如验证文件是否源自 initramfs),也可以
+评估不可变安全属性的内部状态。后者包括检查 dm-verity 保护设备的 roothash、判
+断 dm-verity 是否拥有有效签名、评估 fs-verity 保护文件的摘要、或判断
+fs-verity 是否拥有有效的内建签名。这种精细的策略执行方法实现了高度安全且可定
+制的系统防御机制,能够适应特定的安全需求和信任模型。
+
+要启用 IPE,请确保在 :menuselection:`Security --> Integrity Policy
+Enforcement (IPE)` 下的 ``CONFIG_SECURITY_IPE`` 配置选项已打开。
+
+使用场景
+--------
+
+IPE 最适用于固定功能设备:即用途明确定义且不应更改的设备(如数据中心的网络防
+火墙设备、IoT 设备等),其中所有软件和配置均由系统所有者构建和部署。
+
+IPE 在通用计算领域还有很长的路要走:Linux 社区整体倾向于采用去中心化的信任模
+型(即 web of trust),而 IPE 目前尚不支持此模型。相反,IPE 支持 PKI(公钥基
+础设施),通常指定一组受信任的实体来提供一定程度的绝对信任。
+
+此外,虽然当今大多数软件包已经过签名,但包内的文件(例如可执行文件)通常未签
+名。这使得在需要包管理器正常工作的系统中难以利用 IPE,除非对包管理器及其背后
+的生态系统进行重大更改。
+
+digest_cache LSM [#digest_cache_lsm]_ 是一个系统,当与 IPE 结合使用时,可
+用于启用和支持通用计算场景。
+
+已知限制
+--------
+
+IPE 无法验证匿名可执行内存的完整性,例如 gcc 闭包和 libffi (<3.4.2) 创建的跳
+转表,或 JIT 编译的代码。不幸的是,由于这些是动态生成的代码,IPE 无法确保其完
+整性从而形成信任基础。
+
+IPE 无法验证解释型语言的程序完整性,当这些脚本通过以解释器传递程序文件的方式
+被调用时。这是因为解释器执行这些文件的方式所致;脚本本身并不通过 IPE 的任一钩
+子被评估为可执行代码,它们只是作为文本文件被读取(与已编译的可执行文件相对)。
+然而,随着 ``AT_EXECVE_CHECK`` 标志的引入
+(:doc:`AT_EXECVE_CHECK </userspace-api/check_exec>`),解释器可以使用它来通知
+内核一个脚本文件即将被执行,并请求内核对其执行 LSM 安全检查。
+
+IPE 的 EXECUTE 操作执行策略在已编译可执行文件和解释型脚本之间有所不同:对于已
+编译可执行文件,执行检查在内核执行 ``execve()``、``execveat()``、``mmap()``
+和 ``mprotect()`` 系统调用加载可执行内容时自动触发。对于解释型脚本,执行检查
+需要解释器显式集成,使用带 ``AT_EXECVE_CHECK`` 标志的 ``execveat()``。与 IPE
+在执行过程中拦截的 exec 系统调用不同,这一机制需要解释器主动配合,除非加入信
+号调用,否则现有解释器不会自动获得支持。
+
+威胁模型
+--------
+
+IPE 专门针对内核初始引导后用户空间可执行代码被篡改的风险,包括通过
+``modprobe`` 或 ``insmod`` 从用户空间加载的内核模块。
+
+举例说明,设想一个场景:一个不受信任的二进制文件(可能是恶意的)连同各种必要
+的依赖项(包括加载器和 libc)一起被下载。IPE 在此场景中的主要功能是阻止此类二
+进制文件及其依赖项的执行。
+
+IPE 通过在执行前验证所有可执行代码的完整性和真实性来实现这一目标。它进行彻底
+的检查,确保代码的完整性完好无损,并且代码与既定策略中授权的参考值(摘要、签
+名等)匹配。如果二进制文件未通过此验证过程——无论是因其完整性受到破坏还是不符
+合授权条件——IPE 将拒绝其执行。此外,IPE 还会生成审计日志,可用于检测和分析因
+策略违规而导致的失败。
+
+篡改威胁场景包括各类行为者对可执行代码的修改或替换,包括:
+
+-  对硬件有物理访问权限的行为者
+-  对系统有本地网络访问权限的行为者
+-  对部署系统有访问权限的行为者
+-  处于外部控制下的受感染内部系统
+-  系统的恶意终端用户
+-  系统受感染的终端用户
+-  系统的远程(外部)入侵
+
+IPE 不缓解来自恶意但已获授权的开发者(持有签名证书),或其使用的受感染开发工
+具(即面向返回编程攻击)所带来的威胁。此外,IPE 在用户空间与内核空间之间划定
+了硬安全边界。因此,内核级别的漏洞利用被视为IPE 范围之外,应由其他机制来缓解。
+
+策略
+----
+
+IPE 策略是一种纯文本 [#devdoc]_ 格式,由多条分布在多行上的语句组成。策略顶部
+有一行必需的内容,用于指明策略名称和策略版本,例如::
+
+   policy_name=Ex_Policy policy_version=0.0.0
+
+策略名称唯一键值,以人类可读的名称标识该策略。它用于在 securityfs 下创建节点,
+以及唯一标识策略以部署新策略或更新现有策略。
+
+策略版本表示策略的当前版本(**非** 策略语法版本)。用于防止将策略回滚到可能不
+安全的旧版本。
+
+IPE 策略的下一部分是规则。规则由 ``key=value`` 对组成,称为属性。IPE 规则需
+要两个属性:``action`` 决定了 IPE 在匹配到该规则时的行为,以及 ``op`` 决定了
+该规则何时应该被评估。顺序很重要,规则必须以 ``op`` 开头,以 ``action`` 结尾。
+因此,最小规则是::
+
+   op=EXECUTE action=ALLOW
+
+此示例将允许任何执行。其他属性用于评估被评估文件的不可变安全属性。这些属性旨
+在描述内核中能够提供完整性验证度量的系统,使得 IPE 能够根据属性的取值来判断资
+源的可信度。
+
+规则从上到下进行评估。因此,任何撤销规则或拒绝规则应放在文件的较早位置,以确
+保这些规则在带有 ``action=ALLOW`` 的规则之前被评估。
+
+IPE 策略支持注释。字符 '#' 起到注释的作用,忽略 '#' 右侧直到换行符之前的所有
+字符。
+
+IPE 评估的默认行为也可以通过 ``DEFAULT`` 语句在策略中表达。可以在全局级别或
+按操作级别设置::
+
+   # 全局
+   DEFAULT action=ALLOW
+
+   # 按操作级别
+   DEFAULT op=EXECUTE action=ALLOW
+
+必须为 IPE 中所有已知操作设置默认值。如果您希望保留旧策略与新内核的兼容性(新
+内核可能引入新操作),可以设置 ``ALLOW`` 的全局默认值,然后在每个操作的基础上
+覆盖默认值(如上所示)。
+
+对于基于可配置策略的 LSM,在启动时实施可配置策略存在若干问题,围绕策略的读取
+和解析:
+
+1. 内核 **不应** 从用户空间读取文件,因此直接读取策略文件是被禁止的。
+2. 内核命令行有字符长度限制,一个内核模块不应为自身配置占用整个字符限制。
+3. 内核生态系统中存在各种引导加载程序,因此通过传递内存块的方式成本高昂,难以
+   维护。
+
+为此,IPE 通过 "boot policy" 的概念来解决这个问题。启动策略是一份编译进内核
+的最小策略。该策略旨在将系统引导至用户空间已设置好并可以接收命令的状态,届时
+可以通过 securityfs 部署更复杂的策略。启动策略可以通过
+``SECURITY_IPE_BOOT_POLICY`` 配置选项指定,该选项接受要应用的 IPE 策略纯文本
+版本的路径。此策略将被编译进内核。如果未指定,IPE 将保持禁用状态,直到通过
+securityfs 部署并激活策略。
+
+部署策略
+~~~~~~~~
+
+策略可以通过 securityfs 从用户空间部署。这些策略通过 PKCS#7 消息格式进行签名,
+以实施某种程度的策略授权(防止攻击者获得无限制的 root 权限并部署 "allow all"
+策略)。这些策略必须由可链至 ``SYSTEM_TRUSTED_KEYRING`` 的证书签名,或者如果
+分别启用了 ``CONFIG_IPE_POLICY_SIG_SECONDARY_KEYRING`` 和/或
+``CONFIG_IPE_POLICY_SIG_PLATFORM_KEYRING``,则可链至次级和/或平台密钥环。使
+用 openssl,可以通过以下命令对策略进行签名::
+
+   openssl smime -sign \
+      -in "$MY_POLICY" \
+      -signer "$MY_CERTIFICATE" \
+      -inkey "$MY_PRIVATE_KEY" \
+      -noattr \
+      -nodetach \
+      -nosmimecap \
+      -outform der \
+      -out "$MY_POLICY.p7b"
+
+部署策略通过 securityfs 的 ``new_policy`` 节点完成。要部署策略,只需将文件内
+容 cat 到 securityfs 节点::
+
+   cat "$MY_POLICY.p7b" > /sys/kernel/security/ipe/new_policy
+
+成功后,这将在 ``/sys/kernel/security/ipe/policies/`` 下创建一个子目录。该子
+目录将是所部署策略的 ``policy_name`` 字段,因此对于上述示例,目录将是
+``/sys/kernel/security/ipe/policies/Ex_Policy``。在该目录中,将包含七个文件:
+``pkcs7``、``policy``、``name``、``version``、``active``、``update`` 和
+``delete``。
+
+``pkcs7`` 文件为只读。读取它将返回提供给内核的原始 PKCS#7 数据,即策略的表示。
+如果正在读取的是启动策略,将返回 ``ENOENT``,因为它未经签名。
+
+``policy`` 文件为只读。读取它将返回 PKCS#7 的内部内容,即纯文本策略。
+
+``active`` 文件用于将某个策略设置为当前活动策略。此文件可读写,接受值 ``"1"``
+以将策略设置为活动状态。由于同一时间只能有一个策略处于活动状态,所有其他策略
+将被标记为非活动。被标记为活动的策略必须具有大于或等于当前运行版本的策略版本。
+
+``update`` 文件用于更新内核中已存在的策略。此文件为只写,接受 PKCS#7 签名的
+策略。对此策略将始终执行两项检查:第一,``policy_names`` 必须在更新版本和现有
+版本之间匹配。第二,更新策略的版本必须大于当前运行版本的策略版本。这是为了防
+止回滚攻击。
+
+``delete`` 文件用于删除不再需要的策略。此文件为只写,接受值 ``1`` 以删除策略。
+删除时,代表该策略的 securityfs 节点将被移除。但是,不允许删除当前活动策略,
+将返回操作不允许的错误。
+
+类似地,写入 ``update`` 和 ``new_policy`` 可能导致错误消息(策略语法错误)或
+文件存在错误。后一种错误发生在尝试部署具有某 ``policy_name`` 的策略时,而内核
+中已部署了具有相同 ``policy_name`` 的策略。
+
+部署策略 **不会** 导致 IPE 开始强制执行该策略。IPE 只会强制执行标记为活动的
+策略。请注意,同一时间只能有一个策略处于活动状态。
+
+部署成功后,可以通过写入文件
+``/sys/kernel/security/ipe/policies/$policy_name/active`` 来激活策略。例如,
+可以通过以下命令激活 ``Ex_Policy``::
+
+   echo 1 > "/sys/kernel/security/ipe/policies/Ex_Policy/active"
+
+从此刻起,``Ex_Policy`` 成为系统上强制执行的策略。
+
+IPE 还提供了删除策略的方法。这可以通过 ``delete`` securityfs 节点
+``/sys/kernel/security/ipe/policies/$policy_name/delete`` 来完成。
+向该文件写入 ``1`` 以删除策略::
+
+   echo 1 > "/sys/kernel/security/ipe/policies/$policy_name/delete"
+
+删除策略只有一个要求:被删除的策略必须处于非活动状态。
+
+.. NOTE::
+
+   如果启用了传统 MAC 系统(SELinux、apparmor、smack),所有对 IPE
+   securityfs 节点的写入都需要 ``CAP_MAC_ADMIN``。
+
+模式
+~~~~
+
+IPE 支持两种操作模式:宽松模式(permissive,类似 SELinux 的 permissive 模式)
+和强制模式(enforced)。在宽松模式下,所有事件都会被检查,策略违规会被记录,
+但策略不会真正被强制执行。这允许用户在强制执行之前先测试策略。
+
+默认模式为强制模式(enforce),可以通过内核命令行参数 ``ipe.enforce=(0|1)``
+或 securityfs 节点 ``/sys/kernel/security/ipe/enforce`` 进行更改。
+
+.. NOTE::
+
+   如果启用了传统 MAC 系统(SELinux、apparmor、smack 等),所有对
+   IPE securityfs 节点的写入都需要 ``CAP_MAC_ADMIN``。
+
+审计事件
+~~~~~~~~
+
+1420 AUDIT_IPE_ACCESS
+^^^^^^^^^^^^^^^^^^^^^
+事件示例::
+
+   type=1420 audit(1653364370.067:61): ipe_op=EXECUTE ipe_hook=MMAP enforcing=1 pid=2241 comm="ld-linux.so" path="/deny/lib/libc.so.6" dev="sda2" ino=14549020 rule="DEFAULT action=DENY"
+   type=1300 audit(1653364370.067:61): SYSCALL arch=c000003e syscall=9 success=no exit=-13 a0=7f1105a28000 a1=195000 a2=5 a3=812 items=0 ppid=2219 pid=2241 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=2 comm="ld-linux.so" exe="/tmp/ipe-test/lib/ld-linux.so" subj=unconfined key=(null)
+   type=1327 audit(1653364370.067:61): 707974686F6E3300746573742F6D61696E2E7079002D6E00
+
+   type=1420 audit(1653364735.161:64): ipe_op=EXECUTE ipe_hook=MMAP enforcing=1 pid=2472 comm="mmap_test" path=? dev=? ino=? rule="DEFAULT action=DENY"
+   type=1300 audit(1653364735.161:64): SYSCALL arch=c000003e syscall=9 success=no exit=-13 a0=0 a1=1000 a2=4 a3=21 items=0 ppid=2219 pid=2472 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=2 comm="mmap_test" exe="/root/overlake_test/upstream_test/vol_fsverity/bin/mmap_test" subj=unconfined key=(null)
+   type=1327 audit(1653364735.161:64): 707974686F6E3300746573742F6D61696E2E7079002D6E00
+
+此事件表明 IPE 做出了访问控制决策;IPE 特有记录 (1420) 始终与
+``AUDITSYSCALL`` 记录一起发出。
+
+可以通过 ``AUDITSYSCALL`` 记录的 ``success`` 属性和退出码来判断 IPE 处于宽松
+模式还是强制模式。
+
+字段说明:
+
++-----------+----------+--------+--------------------------------------------+
+| 字段      | 值类型   | 可选?  | 值说明                                     |
++===========+==========+========+============================================+
+| ipe_op    | string   | 否     | 与日志关联的 IPE 操作名称                  |
++-----------+----------+--------+--------------------------------------------+
+| ipe_hook  | string   | 否     | 触发 IPE 事件的 LSM 钩子名称               |
++-----------+----------+--------+--------------------------------------------+
+| enforcing | integer  | 否     | 当前 IPE 强制执行状态,1:强制模式,0:宽松模式|
++-----------+----------+--------+--------------------------------------------+
+| pid       | integer  | 否     | 触发 IPE 事件的进程 PID                    |
++-----------+----------+--------+--------------------------------------------+
+| comm      | string   | 否     | 触发 IPE 事件的进程命令行程序名            |
++-----------+----------+--------+--------------------------------------------+
+| path      | string   | 是     | 被评估文件的绝对路径                       |
++-----------+----------+--------+--------------------------------------------+
+| ino       | integer  | 是     | 被评估文件的 inode 号                      |
++-----------+----------+--------+--------------------------------------------+
+| dev       | string   | 是     | 被评估文件的设备名,如 vda                  |
++-----------+----------+--------+--------------------------------------------+
+| rule      | string   | 否     | 匹配的策略规则                             |
++-----------+----------+--------+--------------------------------------------+
+
+
+1421 AUDIT_IPE_CONFIG_CHANGE
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+事件示例::
+
+   type=1421 audit(1653425583.136:54): old_active_pol_name="Allow_All" old_active_pol_version=0.0.0 old_policy_digest=sha256:E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 new_active_pol_name="boot_verified" new_active_pol_version=0.0.0 new_policy_digest=sha256:820EEA5B40CA42B51F68962354BA083122A20BB846F26765076DD8EED7B8F4DB auid=4294967295 ses=4294967295 lsm=ipe res=1
+   type=1300 audit(1653425583.136:54): SYSCALL arch=c000003e syscall=1 success=yes exit=2 a0=3 a1=5596fcae1fb0 a2=2 a3=2 items=0 ppid=184 pid=229 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=4294967295 comm="python3" exe="/usr/bin/python3.10" key=(null)
+   type=1327 audit(1653425583.136:54): PROCTITLE proctitle=707974686F6E3300746573742F6D61696E2E7079002D66002E2
+
+此事件表明 IPE 将活动策略从一个切换到了另一个,同时记录了这两个策略的版本和哈
+希摘要。请注意,IPE 同一时间只能有一个活动策略,所有访问决策评估均基于当前活
+动策略。部署新策略的常规流程是先将策略加载到内核中,然后再将活动策略切换至它。
+
+此记录始终与 ``write`` 系统调用的 ``AUDITSYSCALL`` 记录一起发出。
+
+字段说明:
+
++------------------------+-----------+--------+----------------------------+
+| 字段                   | 值类型    | 可选? | 值说明                     |
++========================+===========+========+============================+
+| old_active_pol_name    | string    | 是     | 前一个活动策略的名         |
++------------------------+-----------+--------+----------------------------+
+| old_active_pol_version | string    | 是     | 前一个活动策略的版本       |
++------------------------+-----------+--------+----------------------------+
+| old_policy_digest      | string    | 是     | 前一个活动策略的哈希值     |
++------------------------+-----------+--------+----------------------------+
+| new_active_pol_name    | string    | 否     | 当前活动策略的名称         |
++------------------------+-----------+--------+----------------------------+
+| new_active_pol_version | string    | 否     | 当前活动策略的版本         |
++------------------------+-----------+--------+----------------------------+
+| new_policy_digest      | string    | 否     | 当前活动策略的哈希值       |
++------------------------+-----------+--------+----------------------------+
+| auid                   | integer   | 否     | 登录用户 ID                |
++------------------------+-----------+--------+----------------------------+
+| ses                    | integer   | 否     | 登录会话 ID                |
++------------------------+-----------+--------+----------------------------+
+| lsm                    | string    | 否     | 与该事件关联的 LSM 名称    |
++------------------------+-----------+--------+----------------------------+
+| res                    | integer   | 否     | 审计操作的结果(成功/失败)|
++------------------------+-----------+--------+----------------------------+
+
+1422 AUDIT_IPE_POLICY_LOAD
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+事件示例::
+
+   type=1422 audit(1653425529.927:53): policy_name="boot_verified" policy_version=0.0.0 policy_digest=sha256:820EEA5B40CA42B51F68962354BA083122A20BB846F26765076DD8EED7B8F4DB auid=4294967295 ses=4294967295 lsm=ipe res=1 errno=0
+   type=1300 audit(1653425529.927:53): arch=c000003e syscall=1 success=yes exit=2567 a0=3 a1=5596fcae1fb0 a2=a07 a3=2 items=0 ppid=184 pid=229 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=4294967295 comm="python3" exe="/usr/bin/python3.10" key=(null)
+   type=1327 audit(1653425529.927:53): PROCTITLE proctitle=707974686F6E3300746573742F6D61696E2E7079002D66002E2E
+
+此记录表明新策略已加载到内核中,包含策略名称、策略版本和策略哈希。
+
+此记录始终与 ``write`` 系统调用的 ``AUDITSYSCALL`` 记录一起发出。
+
+字段说明:
+
++----------------+-----------+--------+-----------------------------------+
+| 字段           | 值类型    | 可选? | 值说明                            |
++================+===========+========+===================================+
+| policy_name    | string    | 是     | 策略名称                          |
++----------------+-----------+--------+-----------------------------------+
+| policy_version | string    | 是     | 策略版本                          |
++----------------+-----------+--------+-----------------------------------+
+| policy_digest  | string    | 是     | 策略哈希                          |
++----------------+-----------+--------+-----------------------------------+
+| auid           | integer   | 否     | 登录用户 ID                       |
++----------------+-----------+--------+-----------------------------------+
+| ses            | integer   | 否     | 登录会话 ID                       |
++----------------+-----------+--------+-----------------------------------+
+| lsm            | string    | 否     | 与该事件关联的 LSM 名称           |
++----------------+-----------+--------+-----------------------------------+
+| res            | integer   | 否     | 审计操作的结果(成功/失败)       |
++----------------+-----------+--------+-----------------------------------+
+| errno          | integer   | 否     | 策略加载操作的错误码(见下表)    |
++----------------+-----------+--------+-----------------------------------+
+
+策略错误码 (errno):
+
+下表列出了在加载或更新策略时可能出现在 errno 字段中的错误码:
+
++----------------+------------------------------------------------------+
+| 错误码         | 说明                                                 |
++================+======================================================+
+| 0              | 成功                                                 |
++----------------+------------------------------------------------------+
+| -EPERM         | 权限不足                                             |
++----------------+------------------------------------------------------+
+| -EEXIST        | 同名策略已部署                                       |
++----------------+------------------------------------------------------+
+| -EBADMSG       | 策略无效                                             |
++----------------+------------------------------------------------------+
+| -ENOMEM        | 内存不足 (OOM)                                       |
++----------------+------------------------------------------------------+
+| -ERANGE        | 策略版本号溢出                                       |
++----------------+------------------------------------------------------+
+| -EINVAL        | 策略版本解析错误                                     |
++----------------+------------------------------------------------------+
+| -ENOKEY        | 签名 IPE 策略的密钥未在密钥环中找到                  |
++----------------+------------------------------------------------------+
+| -EKEYREJECTED  | 策略签名验证失败                                     |
++----------------+------------------------------------------------------+
+| -ESTALE        | 尝试使用旧版本更新 IPE 策略                          |
++----------------+------------------------------------------------------+
+| -ENOENT        | 策略在更新期间被删除                                 |
++----------------+------------------------------------------------------+
+
+1404 AUDIT_MAC_STATUS
+^^^^^^^^^^^^^^^^^^^^^
+
+事件示例::
+
+   type=1404 audit(1653425689.008:55): enforcing=0 old_enforcing=1 auid=4294967295 ses=4294967295 enabled=1 old-enabled=1 lsm=ipe res=1
+   type=1300 audit(1653425689.008:55): arch=c000003e syscall=1 success=yes exit=2 a0=1 a1=55c1065e5c60 a2=2 a3=0 items=0 ppid=405 pid=441 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=)
+   type=1327 audit(1653425689.008:55): proctitle="-bash"
+
+   type=1404 audit(1653425689.008:55): enforcing=1 old_enforcing=0 auid=4294967295 ses=4294967295 enabled=1 old-enabled=1 lsm=ipe res=1
+   type=1300 audit(1653425689.008:55): arch=c000003e syscall=1 success=yes exit=2 a0=1 a1=55c1065e5c60 a2=2 a3=0 items=0 ppid=405 pid=441 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=)
+   type=1327 audit(1653425689.008:55): proctitle="-bash"
+
+此记录始终与 ``write`` 系统调用的 ``AUDITSYSCALL`` 记录一起发出。
+
+字段说明:
+
++--------------+-----------+--------+-------------------------------------------------------+
+| 字段         | 值类型    | 可选? | 值说明                                                |
++==============+===========+========+=======================================================+
+| enforcing    | integer   | 否     | IPE 切换到的强制执行状态,1:强制模式,0:宽松模式        |
++--------------+-----------+--------+-------------------------------------------------------+
+| old_enforcing| integer   | 否     | IPE 切换前的强制执行状态,1:强制模式,0:宽松模式        |
++--------------+-----------+--------+-------------------------------------------------------+
+| auid         | integer   | 否     | 登录用户 ID                                           |
++--------------+-----------+--------+-------------------------------------------------------+
+| ses          | integer   | 否     | 登录会话 ID                                           |
++--------------+-----------+--------+-------------------------------------------------------+
+| enabled      | integer   | 否     | 新的 TTY 审计启用设置                                 |
++--------------+-----------+--------+-------------------------------------------------------+
+| old-enabled  | integer   | 否     | 旧的 TTY 审计启用设置                                 |
++--------------+-----------+--------+-------------------------------------------------------+
+| lsm          | string    | 否     | 与该事件关联的 LSM 名称                               |
++--------------+-----------+--------+-------------------------------------------------------+
+| res          | integer   | 否     | 审计操作的结果(成功/失败)                           |
++--------------+-----------+--------+-------------------------------------------------------+
+
+
+成功审计
+^^^^^^^^
+
+IPE 支持成功审计。启用后,所有通过 IPE 策略且未被阻止的事件都将生成审计事件。
+此功能默认关闭,可以通过内核命令行参数 ``ipe.success_audit=(0|1)`` 或
+securityfs 文件 ``/sys/kernel/security/ipe/success_audit`` 启用。
+
+这会产生 **大量** 日志,因为 IPE 会检查系统上的每一个用户空间二进制文件,但
+在调试策略时非常有用。
+
+.. NOTE::
+
+   如果启用了传统 MAC 系统(SELinux、apparmor、smack 等),所有对 IPE
+   securityfs 节点的写入都需要 ``CAP_MAC_ADMIN``。
+
+属性
+----
+
+如上所述,IPE 属性是 IPE 策略中以 ``key=value`` 对表示的表达式。策略解析器内
+置了两个属性:'op' 和 'action'。其他属性用于限定被评估文件的不可变安全属性。
+目前这些属性包括:'``boot_verified``'、'``dmverity_signature``'、
+'``dmverity_roothash``'、'``fsverity_signature``'、'``fsverity_digest``'。
+以下列出了 IPE 支持的所有属性的说明:
+
+op
+~~
+
+表示规则适用于的操作。必须作为第一条标记出现在每条规则中。IPE 支持
+以下操作:
+
+   ``EXECUTE``
+
+      适用于任何试图被执行或作为可执行文件加载的文件。
+
+   ``FIRMWARE``:
+
+      适用于通过 firmware_class 接口加载的固件。涵盖预分配缓冲区和固件文件本
+      身。
+
+   ``KMODULE``:
+
+      适用于通过 ``modprobe`` 或 ``insmod`` 加载的内核模块。
+
+   ``KEXEC_IMAGE``:
+
+      适用于通过 ``kexec`` 加载的内核镜像。
+
+   ``KEXEC_INITRAMFS``
+
+      适用于通过 ``kexec --initrd`` 加载的 initrd 镜像。
+
+   ``POLICY``:
+
+      通过内核空间发起的读取操作控制策略加载。
+
+      例如,将策略文件路径写入 ``$securityfs/ima/policy`` 来加载 IMA 策略。
+
+   ``X509_CERT``:
+
+      通过 Kconfig 选项 ``CONFIG_IMA_X509_PATH`` 和
+      ``CONFIG_EVM_X509_PATH`` 控制 IMA 证书的加载。
+
+action
+~~~~~~
+
+   决定 IPE 在规则匹配时的行为。必须作为最后一条子句出现在每条规则中。可以取
+   以下值:
+
+   ``ALLOW``:
+
+      如果规则匹配,显式允许访问资源并继续执行,不再评估后续规则。
+
+   ``DENY``:
+
+      如果规则匹配,显式禁止访问资源并停止执行,不再评估后续规则。
+
+boot_verified
+~~~~~~~~~~~~~
+
+   此属性可用于对来自 initramfs 的文件进行授权。
+   此属性的格式是::
+
+         boot_verified=(TRUE|FALSE)
+
+   .. WARNING::
+
+      此属性将信任来自 initramfs(rootfs) 的文件。它应仅在早期引导阶段使用。
+      在实际根文件系统挂载到 initramfs 之上之前,initramfs 脚本将递归删除
+      initramfs 上的所有文件和目录。这通常通过使用
+      switch_root(8) [#switch_root]_ 来实现。因此在实际根文件系统接管后,
+      initramfs 将为空且不可访问。建议在此之后切换到不依赖该属性的其他策略。
+      这样可以确保信任策略在整个系统运行期间保持相关和有效。
+
+dmverity_roothash
+~~~~~~~~~~~~~~~~~
+
+   此属性可用于授权或撤销特定的 dm-verity 卷,通过其根哈希进行标识。它依赖于
+   DM_VERITY 模块。此属性由 ``IPE_PROP_DM_VERITY`` 配置选项控制,当
+   ``SECURITY_IPE`` 和 ``DM_VERITY`` 都启用时它将自动被选中。
+   此属性的格式是::
+
+      dmverity_roothash=DigestName:HexadecimalString
+
+   dmverity_roothash 支持的 DigestName 有 [#dmveritydigests]_
+
+      + blake2b-512
+      + blake2s-256
+      + sha256
+      + sha384
+      + sha512
+      + sha3-224
+      + sha3-256
+      + sha3-384
+      + sha3-512
+      + sm3
+      + rmd160
+
+dmverity_signature
+~~~~~~~~~~~~~~~~~~
+
+   此属性可用于授权所有具有已签名 roothash 的 dm-verity 卷,该签名需由
+   dm-verity 配置中指定的密钥环(系统受信任密钥环或次级密钥环)验证有效。它依
+   赖于 ``DM_VERITY_VERIFY_ROOTHASH_SIG`` 配置选项,并由
+   ``IPE_PROP_DM_VERITY_SIGNATURE`` 配置选项控制,当 ``SECURITY_IPE``、
+   ``DM_VERITY`` 和 ``DM_VERITY_VERIFY_ROOTHASH_SIG`` 都启用时它将自动被选中。
+   此属性的格式是::
+
+      dmverity_signature=(TRUE|FALSE)
+
+fsverity_digest
+~~~~~~~~~~~~~~~
+
+   此属性可用于授权特定的启用了 fsverity 的文件,通过其 fsverity 摘要进行标
+   识。它依赖于 ``FS_VERITY`` 配置选项,并由 ``IPE_PROP_FS_VERITY`` 配置选项
+   控制,当 ``SECURITY_IPE`` 和 ``FS_VERITY`` 都启用时它将自动被选中。
+   此属性的格式是::
+
+      fsverity_digest=DigestName:HexadecimalString
+
+   fsverity_digest 支持的 DigestName 有 [#fsveritydigest]_
+
+      + sha256
+      + sha512
+
+fsverity_signature
+~~~~~~~~~~~~~~~~~~
+
+   此属性用于授权所有已通过 fs-verity 内建签名机制验证的启用了 fs-verity 的
+   文件。签名验证依赖于存储在 ".fs-verity" 密钥环中的密钥。它依赖于
+   ``FS_VERITY_BUILTIN_SIGNATURES`` 配置选项,并由 ``IPE_PROP_FS_VERITY``
+   配置选项控制,当 ``SECURITY_IPE``、``FS_VERITY`` 和
+   ``FS_VERITY_BUILTIN_SIGNATURES`` 都启用时它将自动被选中。
+   此属性的格式是::
+
+      fsverity_signature=(TRUE|FALSE)
+
+策略示例
+--------
+
+全部允许
+~~~~~~~~
+
+::
+
+   policy_name=Allow_All policy_version=0.0.0
+   DEFAULT action=ALLOW
+
+仅允许 initramfs
+~~~~~~~~~~~~~~~~
+
+::
+
+   policy_name=Allow_Initramfs policy_version=0.0.0
+   DEFAULT action=DENY
+
+   op=EXECUTE boot_verified=TRUE action=ALLOW
+
+允许任意已签名并验证的 dm-verity 卷和 initramfs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+   policy_name=Allow_Signed_DMV_And_Initramfs policy_version=0.0.0
+   DEFAULT action=DENY
+
+   op=EXECUTE boot_verified=TRUE action=ALLOW
+   op=EXECUTE dmverity_signature=TRUE action=ALLOW
+
+禁止从特定 dm-verity 卷执行
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+   policy_name=Deny_DMV_By_Roothash policy_version=0.0.0
+   DEFAULT action=DENY
+
+   op=EXECUTE dmverity_roothash=sha256:cd2c5bae7c6c579edaae4353049d58eb5f2e8be0244bf05345bc8e5ed257baff action=DENY
+
+   op=EXECUTE boot_verified=TRUE action=ALLOW
+   op=EXECUTE dmverity_signature=TRUE action=ALLOW
+
+仅允许特定 dm-verity 卷
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+   policy_name=Allow_DMV_By_Roothash policy_version=0.0.0
+   DEFAULT action=DENY
+
+   op=EXECUTE dmverity_roothash=sha256:401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=ALLOW
+
+允许任意带有有效内建签名的 fs-verity 文件
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+   policy_name=Allow_Signed_And_Validated_FSVerity policy_version=0.0.0
+   DEFAULT action=DENY
+
+   op=EXECUTE fsverity_signature=TRUE action=ALLOW
+
+允许执行特定 fs-verity 文件
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+   policy_name=ALLOW_FSV_By_Digest policy_version=0.0.0
+   DEFAULT action=DENY
+
+   op=EXECUTE fsverity_digest=sha256:fd88f2b8824e197f850bf4c5109bea5cf0ee38104f710843bb72da796ba5af9e action=ALLOW
+
+附加信息
+--------
+
+- `Github 仓库 <https://github.com/microsoft/ipe>`_
+- :doc:`IPE 开发者和设计文档 </security/ipe>`
+
+FAQ
+---
+
+Q:
+   IPE 与其他提供基于信任的访问控制的 LSM 有什么区别?
+
+A:
+
+   一般而言,另外有两种 LSM 可以提供类似的功能:IMA 和 Loadpin。
+
+   IMA 和 IPE 在功能上非常相似。两者之间的显著区别在于策略。 [#devdoc]_
+
+   Loadpin 与 IPE 差异相当大,因为 Loadpin 只覆盖了 IPE 的内核读取操作,而
+   IPE 除了内核读取外还能控制执行。信任模型也不同;Loadpin 的信任根植于初始
+   超级块,而 IPE 的信任来源于内核自身(通过 ``SYSTEM_TRUSTED_KEYS``)。
+
+------------
+
+.. [#digest_cache_lsm] https://lore.kernel.org/lkml/20240415142436.2545003-1-roberto.sassu@huaweicloud.com/
+
+.. [#devdoc] 关于此主题的更多信息,请参阅 :doc:`设计文档 </security/ipe>`。
+
+.. [#switch_root] https://man7.org/linux/man-pages/man8/switch_root.8.html
+
+.. [#dmveritydigests] 这些哈希算法基于 Linux 加密 API 接受的值;IPE 不限制摘
+                      要算法本身;因此,此列表可能不是最新的。
+
+.. [#fsveritydigest] 这些哈希算法基于内核 fsverity 支持接受的值;IPE 不限制
+                     摘要算法本身;因此,此列表可能不是最新的。
-- 
2.43.0


^ permalink raw reply related

* [PATCH 05/10] docs/zh_CN: add LSM/Smack Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/Smack.rst into Chinese.

Update the translation through commit 674e2b24791c
("smack: fix bug: setting task label silently ignores input garbage")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/Smack.rst           | 722 ++++++++++++++++++
 1 file changed, 722 insertions(+)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/Smack.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/Smack.rst b/Documentation/translations/zh_CN/admin-guide/LSM/Smack.rst
new file mode 100644
index 000000000000..cdc5221624a5
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/Smack.rst
@@ -0,0 +1,722 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/Smack.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+
+=====
+Smack
+=====
+
+"Good for you, you've decided to clean the elevator!"
+- The Elevator, from Dark Star
+
+Smack 是简化的强制访问控制 (MAC) 内核实现。
+它是一种基于内核的强制访问控制方案,设计目标强调
+简洁性。
+
+Smack 不是 Linux 唯一可用的 MAC 机制。首次接触 MAC
+的用户应将 Smack 与其他机制进行比较,以决定哪种最适合
+当前需求。
+
+Smack 由三大部分组成:
+
+- 内核
+- 基本工具(有帮助但非必需)
+- 配置数据
+
+内核部分实现为 Linux 安全模块 (LSM)。它依赖 netlabel,
+并在支持扩展属性的文件系统上表现最佳,但扩展属性支
+持并非严格要求。在标准发行版("vanilla")上运行 Smack
+内核是安全的。
+
+Smack 内核使用 CIPSO IP 选项。某些网络配置不接受 IP
+选项,可能会阻碍使用 Smack 的系统。
+
+Smack 已在 Tizen 操作系统中使用。了解 Smack 在 Tizen 中
+的使用方式,请访问 http://wiki.tizen.org 。
+
+当前 Smack 用户空间的 git 仓库为:
+
+    git://github.com/smack-team/smack.git
+
+大多数现代发行版都可以直接编译并安装。
+smackutil 中包含五个命令:
+
+chsmack:
+    显示或设置 Smack 扩展属性值
+
+smackctl:
+    加载 Smack 访问规则
+
+smackaccess:
+    检查具有某标签的进程是否能访问另一个标签的对象
+
+这两个命令已被以下接口取代:
+    smackfs/load2 和 smackfs/cipso2
+
+smackload:
+    为写入 smackfs/load 格式化数据
+
+smackcipso:
+    为写入 smackfs/cipso 格式化数据
+
+在 Smack 的理念下,配置数据极少且非强制要求。最关键
+的步骤是挂载 smackfs 伪文件系统。如果已安装 smackutil,
+启动脚本会自动完成;也可以手动完成。
+
+在 ``/etc/fstab`` 中加入如下行可实现自动挂载::
+
+    smackfs /sys/fs/smackfs smackfs defaults 0 0
+
+``/sys/fs/smackfs`` 目录由内核创建。
+
+Smack 使用扩展属性 (xattr) 在文件系统对象上存储标签,
+这些属性位于 security 命名空间。改变标签需要
+CAP_MAC_ADMIN 权限。
+
+Smack 使用的扩展属性如下:
+
+SMACK64
+    用于访问控制决策。新创建文件对象默认获得创建进程的标签。
+
+SMACK64EXEC
+    当带该属性的程序被 exec 时,进程将使用该属性值作为标签。
+
+SMACK64MMAP
+    若进程的 Smack 标签不允许某个该属性所含标签的进程被授予的全部访问
+    权限,则禁止对该文件进行 mmap。这是一个针对共享库的特定使用场景。
+
+SMACK64TRANSMUTE
+    仅能设置为 "TRUE"。若目录具有该属性,并且在目录中创建对象时,
+    允许写入该目录的 Smack 规则包含 transmute("t")模式,则对象将
+    继承目录标签而非创建进程标签。若创建的对象是目录,则
+    SMACK64TRANSMUTE 属性也会被一并设置。
+
+SMACK64IPIN
+    仅对套接字的文件描述符可用。使用该属性中的 Smack 标签对递送到
+    该套接字的数据包进行访问控制决策。
+
+SMACK64IPOUT
+    仅对套接字的文件描述符可用。使用该属性中的 Smack 标签对从该
+    套接字发出的数据包进行访问控制决策。
+
+设置 Smack 标签的方式示例::
+
+    # attr -S -s SMACK64 -V "value" path
+    # chsmack -a value path
+
+进程可通过读取 ``/proc/self/attr/current`` 查看自身标签,
+具备 ``CAP_MAC_ADMIN`` 的进程可写入该文件修改标签。
+
+多数 Smack 配置通过写入 smackfs 文件系统完成,该伪文件系统挂载在
+``/sys/fs/smackfs`` 上。
+
+access
+    为向后兼容保留;推荐使用 ``access2`` 接口。
+    此接口报告具有指定 Smack 标签的主体是否对具有指定 Smack 标签
+    的客体拥有特定访问权限。向该文件写入一条固定格式的访问规则,
+    下一次读取将指示访问是否被允许。内容为 "1" 表示允许访问,
+    "0" 表示拒绝。
+
+access2
+    此接口报告具有指定 Smack 标签的主体是否对具有指定 Smack 标签
+    的客体拥有特定访问权限。向该文件写入一条长格式的访问规则,
+    下一次读取将指示访问是否被允许。内容为 "1" 表示允许访问,
+    "0" 表示拒绝。
+
+ambient
+    包含对未标记网络数据包应用的 Smack 标签。
+
+change-rule
+    此接口允许修改已有的访问控制规则。写入格式为::
+
+        "%s %s %s %s"
+
+    其中第一个字符串为主体标签,第二个为客体标签,第三个为允许的
+    访问权限,第四个为拒绝的访问权限。访问字符串只能包含 "rwxat-"
+    字符。如果给定主体和客体的规则已存在,将通过启用第三个字符串中
+    的权限并禁用第四个字符串中的权限来修改该规则。如果不存在这样
+    的规则,将使用第三和第四个字符串中指定的访问权限创建新规则。
+
+cipso
+    为向后兼容保留;推荐使用 ``cipso2`` 接口。
+    此接口允许将特定的 CIPSO 标头分配给 Smack 标签。
+    写入格式为::
+
+        "%24s%4d%4d"["%4d"]...
+
+    第一个字符串是固定长度的 Smack 标签。第一个数字是要使用的级别。
+    第二个数字是类别数量。后续数字是类别::
+
+        "level-3-cats-5-19          3   2   5  19"
+
+cipso2
+    此接口允许将特定的 CIPSO 标头分配给 Smack 标签。
+    写入格式为::
+
+        "%s%4d%4d"["%4d"]...
+
+    第一个字符串是长 Smack 标签。第一个数字是要使用的级别。
+    第二个数字是类别数量。后续数字是类别::
+
+        "level-3-cats-5-19   3   2   5  19"
+
+direct
+    包含用于网络数据包中 Smack 直接标签表示的 CIPSO 级别。
+
+doi
+    包含网络数据包中使用的 CIPSO 解释域。
+
+ipv6host
+    此接口允许将特定的 IPv6 互联网地址视为单标签主机。数据包仅从
+    对主机标签具有 Smack 写访问权限的进程发送到单标签主机。所有从
+    单标签主机接收的数据包将被赋予指定标签。写入格式为::
+
+        "%h:%h:%h:%h:%h:%h:%h:%h label" 或
+        "%h:%h:%h:%h:%h:%h:%h:%h/%d label"
+
+    不支持 "::" 地址缩写。如果标签为 "-DELETE",将删除匹配的条目。
+
+load
+    为向后兼容保留;推荐使用 ``load2`` 接口。
+    此接口允许指定系统定义规则之外的访问控制规则。
+    写入格式为::
+
+        "%24s%24s%5s"
+
+    其中第一个字符串为主体标签,第二个为客体标签,第三个为请求的
+    访问权限。访问字符串只能包含 "rwxat-" 字符,用于指定允许的访问
+    类型。"-" 是不允许的权限的占位符。字符串 "r-x--" 表示读和执行
+    访问。标签长度限制为 23 个字符。
+
+load2
+    此接口允许指定系统定义规则之外的访问控制规则。
+    写入格式为::
+
+        "%s %s %s"
+
+    其中第一个字符串为主体标签,第二个为客体标签,第三个为请求的
+    访问权限。访问字符串只能包含 "rwxat-" 字符,用于指定允许的访问
+    类型。"-" 是不允许的权限的占位符。字符串 "r-x--" 表示读和执行
+    访问。
+
+load-self
+    为向后兼容保留;推荐使用 ``load-self2`` 接口。
+    此接口允许定义进程特定的访问规则。这些规则仅在访问本来会被允许
+    时才被查询,旨在对进程施加额外的限制。格式与 ``load`` 接口相同。
+
+load-self2
+    此接口允许定义进程特定的访问规则。这些规则仅在访问本来会被允许
+    时才被查询,旨在对进程施加额外的限制。格式与 ``load2`` 接口相同。
+
+logging
+    包含 Smack 日志状态。
+
+mapped
+    包含用于网络数据包中 Smack 映射标签表示的 CIPSO 级别。
+
+netlabel
+    此接口允许将特定的互联网地址视为单标签主机。数据包不带 CIPSO
+    标头发送到单标签主机,但仅从对主机标签具有 Smack 写访问权限的
+    进程发送。所有从单标签主机接收的数据包将被赋予指定标签。
+    写入格式为::
+
+        "%d.%d.%d.%d label" 或 "%d.%d.%d.%d/%d label"
+
+    如果指定的标签是 "-CIPSO",则该地址被视为支持 CIPSO 标头的主机。
+
+onlycap
+    包含进程必须具有的标签,才能使 ``CAP_MAC_ADMIN`` 和
+    ``CAP_MAC_OVERRIDE`` 生效。如果此文件为空,这些能力对具有任何
+    标签的进程都有效。通过向文件写入所需标签(用空格分隔)来设置值,
+    或通过写入 "-" 来清除。
+
+ptrace
+    用于定义当前的 ptrace 策略::
+
+        0 - default(默认):
+            依赖 Smack 访问规则的策略。对于 ``PTRACE_READ``,主体需要
+            对客体具有读访问权限。对于 ``PTRACE_ATTACH``,需要读写访问
+            权限。
+
+        1 - exact(精确):
+            限制 ``PTRACE_ATTACH`` 的策略。仅当主体和客体标签相同时才
+            允许 attach。``PTRACE_READ`` 不受影响。可通过
+            ``CAP_SYS_PTRACE`` 覆盖。
+
+        2 - draconian(严格):
+            此策略行为与上述 'exact' 相同,但不能通过
+            ``CAP_SYS_PTRACE`` 覆盖。
+
+revoke-subject
+    向此文件写入一个 Smack 标签,会将所有以该标签为主体的访问规则中
+    的访问权限设置为 '-'。
+
+unconfined
+    如果内核配置了 ``CONFIG_SECURITY_SMACK_BRINGUP``,具有
+    ``CAP_MAC_ADMIN`` 的进程可以向此接口写入一个标签。此后,涉及该
+    标签的访问将被记录,并且即使本来不会被允许的访问也会被放行。
+    请注意,这是危险的,可能会破坏系统的正确标签设置。切勿在生产环境
+    中使用。
+
+relabel-self
+    此接口包含进程可以通过写入 ``/proc/self/attr/current`` 转换到的
+    标签列表。通常进程可以将自己的标签更改为任何合法值,但前提是具有
+    ``CAP_MAC_ADMIN``。此接口允许没有 ``CAP_MAC_ADMIN`` 的进程将自身
+    重新标记为预定义列表中的某个标签。没有 ``CAP_MAC_ADMIN`` 的进程
+    只能更改自己的标签一次。更改后,此列表将被清空。
+    通过向文件写入所需标签(用空格分隔)来设置值,或通过写入 "-"
+    来清除。
+
+如果使用 smackload 工具,可以在 ``/etc/smack/accesses`` 中添加访问规则。
+格式为::
+
+    subjectlabel objectlabel access
+
+其中 access 是字母 rwxatb 的组合,指定具有 subjectlabel 的主体对具有
+objectlabel 的客体所允许的访问类型。如果不存在规则,则不允许任何访问。
+
+更多程序请访问 http://schaufler-ca.com 。
+
+简化强制访问控制内核(白皮书)
+====================================
+
+Casey Schaufler
+casey@schaufler-ca.com
+
+强制访问控制
+------------
+
+计算机系统采用多种方案来约束信息在使用机器的用户和服务之间的共享方式。其
+中一些方案允许程序或用户自行决定哪些其他程序或用户可以访问数据片段。这类
+方案被称为自主访问控制(DAC)机制,因为访问控制由用户自主指定。另一些方案
+则不允许用户或程序自行决定能访问什么。这类方案被称为强制访问控制(MAC)机
+制,因为在哪些用户或程序能够访问数据片段的问题上,你没有选择余地。
+
+Bell & LaPadula
+---------------
+
+从 20 世纪 80 年代中期到世纪之交,强制访问控制(MAC)一直与美国国防部标记
+纸质文件的 Bell & LaPadula 安全模型紧密关联。这种形式的 MAC 在 Capital
+Beltway 地区以及斯堪的纳维亚超级计算机中心拥有追随者,但也常被指未能满足
+通用需求。
+
+域类型强制(DTE)
+-----------------
+
+世纪之交前后,域类型强制(DTE)开始流行。此方案将用户、程序和数据组织进相
+互隔离的域中。该方案已作为流行 Linux 发行版的组成部分被广泛部署。然而,维
+护此方案所需的管理开销,以及提供安全域映射所需的对整个系统的深入理解,导致
+该方案在大多数情况下要么被禁用,要么仅以受限方式使用。
+
+Smack
+-----
+
+Smack 是一种强制访问控制机制,旨在提供有用的 MAC 功能,同时避免前人的缺陷。
+针对 Bell & LaPadula 的限制,Smack 通过提供一种方案使访问控制可以依据系统
+需求及目的灵活调整,而非受制于晦涩的政府政策。针对域类型强制的复杂性,Smack
+通过用已有的访问模式定义访问控制来加以避免。
+
+Smack 术语
+----------
+
+以下用于讨论 Smack 的术语,对接触过其他 MAC 系统的人来说会很熟悉,对初学者
+也不应太难掌握。其中四个术语有特定用法,尤为重要:
+
+  主体(Subject):
+	主体是计算机系统上的活动实体。在 Smack 中,主体即任务(task),
+	任务又是执行的基本单元。
+
+  客体(Object):
+	客体是计算机系统上的被动实体。在 Smack 中,各类文件、IPC 和任务
+	都可以是客体。
+
+  访问(Access):
+	主体试图向客体写入信息或从客体读取信息的任何尝试都是一次访问。
+
+  标签(Label):
+	标识主体或客体强制访问控制特征的数据。
+
+这些定义与安全社区中的传统用法一致。此外,还有一些来自 Linux 且可能频繁出
+现的术语:
+
+  能力(Capability):
+	拥有某项能力的任务有权违反系统安全策略的某个特定方面,该方面由
+	该项具体能力所标识。拥有一项或多项能力的任务是特权任务,而没有
+	任何能力的任务是非特权任务。
+
+  特权(Privilege):
+	被允许违反系统安全策略的任务称为拥有特权。截至本文撰写时,任务
+	可以通过拥有 capabilities 或者拥有 root 的有效用户来获得特权。
+
+Smack 基础
+----------
+
+Smack 是对 Linux 系统的扩展。它根据附加在主体和客体上的标签,对哪些主体可
+以访问哪些客体施加额外的限制。
+
+标签
+~~~~
+
+Smack 标签是 ASCII 字符串。标签最长可达 255 个字符,但建议控制在 23 个字符
+以内。使用特殊字符(即字母或数字以外的任何字符)的单字符标签保留给 Smack
+开发团队使用。Smack 标签是无结构的、大小写敏感的,对标签执行的唯一操作是相
+等性比较。Smack 标签不能包含不可打印字符、"/"(斜线)、"\"(反斜线)、"'"
+(单引号)和 '"'(双引号)字符。
+
+Smack 标签不能以 '-' 开头。该字符保留给特殊选项使用。
+
+存在一些预定义的标签::
+
+	_ 	读作 "floor",单个下划线字符。
+	^ 	读作 "hat", 单个抑扬符号字符。
+	* 	读作 "star",单个星号字符。
+	? 	读作 "huh",  单个问号字符。
+	@ 	读作 "web", 单个 at 符号字符。
+
+Smack 系统上的每个任务都会被分配一个标签。进程的 Smack 标签通常由系统初始
+化机制分配。
+
+访问规则
+~~~~~~~~
+
+Smack 使用 Linux 的传统访问模式。这些模式包括读(read)、执行(execute)、
+写(write),有时还包括追加(append)。有几种情况下访问模式可能不那么显而易
+见,包括:
+
+  信号(Signals):
+	信号是从主体任务到客体任务的一次写操作。
+
+  Internet 域 IPC:
+	数据包的传输被视为从源任务到目标任务的一次写操作。
+
+Smack 根据主体所附标签及其试图访问的客体所附标签来限制访问。所强制执行的规
+则按优先级依次为:
+
+	1. 任何由标签为 "*" 的任务请求的访问一律拒绝。
+	2. 任何由标签为 "^" 的任务请求的读或执行访问一律允许。
+	3. 任何对标签为 "_" 的客体请求的读或执行访问一律允许。
+	4. 任何对标签为 "*" 的客体请求的访问一律允许。
+	5. 任何由任务对其标签相同的客体请求的访问一律允许。
+	6. 任何在已加载规则集中明确定义的访问请求一律允许。
+	7. 任何其他访问一律拒绝。
+
+Smack 访问规则
+~~~~~~~~~~~~~~
+
+借助 Smack 提供的隔离性,访问分离变得简单。同时也有许多有趣的场景,需要主体
+对不同标签的客体进行有限制的访问。一个例子是熟悉的敏感性间谍模型:从事高度
+机密项目的科学家可以阅读低密级文档,而她撰写的内容将会"诞生"为高密级。为
+了适应此类方案,Smack 包含了一种用于指定允许跨标签访问的规则机制。
+
+访问规则格式
+~~~~~~~~~~~~
+
+访问规则的格式为::
+
+	subject-label object-label access
+
+其中 subject-label 是任务的 Smack 标签,object-label 是被访问对象的 Smack
+标签,access 是一个指定所允许访问类型的字符串。访问规格说明中会查找以下描述
+访问模式的字母:
+
+	a:	表示应授予追加(append)访问权限。
+	r:	表示应授予读(read)访问权限。
+	w:	表示应授予写(write)访问权限。
+	x:	表示应授予执行(execute)访问权限。
+	t:	表示该规则请求 transmutation(标签转变)。
+	b:	表示该规则应报告用于 bring-up 调试。
+
+规格说明字母的大写形式同样有效。访问模式规格说明的顺序可以任意。以下是一些
+可接受规则的示例::
+
+	TopSecret Secret  rx
+	Secret    Unclass R
+	Manager   Game    x
+	User      HR      w
+	Snap      Crackle rwxatb
+	New       Old     rRrRr
+	Closed    Off     -
+
+以下是一些不可接受规则的示例::
+
+	Top Secret Secret     rx
+	Ace        Ace        r
+	Odd        spells     waxbeans
+
+标签中不允许包含空格。由于主体总是可以访问具有相同标签的文件,因此为此类情
+况指定规则没有意义。访问规格说明中只允许有效的字母(rwxatbRWXATB)和破折号
+('-')。破折号是占位符,因此 "a-r" 与 "ar" 相同。单独一个破折号用于指明不
+允许任何访问。
+
+应用访问规则
+~~~~~~~~~~~~
+
+Linux 的开发者很少定义全新的事物类型,通常是从其他系统中引入方案和概念。最
+常见的情况是,这些其他系统是 Unix 的变体。Unix 有许多讨人喜欢的特性,但访
+问控制模型的一致性并非其中之一。Smack 力求在尽可能保持一致地处理访问的同时,
+不失底层机制的精神。
+
+文件系统对象(包括文件、目录、命名管道、符号链接和设备)所需的访问权限与
+mode bit 访问所使用的权限紧密匹配。打开文件进行读取需要对文件具有读访问权限。
+搜索目录需要执行访问权限。创建文件并写访问需要对包含目录具有读写访问权限。
+删除文件需要对文件以及包含目录具有读写访问权限。用户可能能够看到某个文件存
+在,但由于对包含目录具有读访问权限而对具有不同标签的文件没有读访问权限,因
+此看不到它的任何属性。这是文件名作为目录中的数据而非文件一部分所带来的现象。
+
+如果目录被标记为 transmuting(SMACK64TRANSMUTE=TRUE),并且某访问规则允许
+进程在该目录中创建对象且该规则包含 't' 访问模式,则新对象将被赋予目录的标
+签而非创建进程的标签。这使得两个拥有不同标签的进程更容易共享数据,而无需授
+予对彼此所有文件的访问权限。
+
+IPC 对象(消息队列、信号量集合和内存段)存在于扁平的名字空间中,访问请求只
+需与目标对象匹配即可。
+
+进程对象反映系统上的任务,用于访问它们的 Smack 标签与任务用于自身访问尝试
+的 Smack 标签相同。通过 kill() 系统调用发送信号是从发送者到接收者的一次写
+操作。调试进程需要读写两种访问权限。创建新任务是一个内部操作,会产生两个具
+有相同 Smack 标签的任务,并且无需访问检查。
+
+套接字(Socket)是附加在进程上的数据结构,从一个进程向另一个进程发送数据包
+要求发送者对接收者具有写访问权限。接收者不需要对发送者具有读访问权限。
+
+设置访问规则
+~~~~~~~~~~~~
+
+配置文件 /etc/smack/accesses 包含需要在系统启动时设置的规则。其内容会被写
+入特殊文件 /sys/fs/smackfs/load2。规则可以随时添加并立即生效。对于任意一对
+主体标签和客体标签,只能存在一条规则,最后指定的规则将覆盖之前的规定。
+
+任务属性
+~~~~~~~~
+
+进程的 Smack 标签可以从 ``/proc/<pid>/attr/current`` 读取。进程可以从
+``/proc/self/attr/current`` 读取自身的 Smack 标签。特权进程可以通过向
+``/proc/self/attr/current`` 写入来更改自身的 Smack 标签,但不能更改其他进程
+的标签。
+
+写入格式为:仅写入标签本身,或者标签后跟以下三种尾部字符之一:``\n``
+(根据 ``/proc/...`` 接口的通用约定)、``\0`` (因为某些应用程序会错误地包含
+它)、``\n\0`` (因为我们认为某些应用程序可能会错误地包含它)。
+
+文件属性
+~~~~~~~~
+
+文件系统对象的 Smack 标签以扩展属性形式存储在文件上,属性名为 SMACK64。此
+属性位于 security 名字空间。只能由具有特权的进程更改。
+
+特权
+~~~~
+
+拥有 CAP_MAC_OVERRIDE 或 CAP_MAC_ADMIN 的进程是特权进程。CAP_MAC_OVERRIDE
+允许进程访问原本会被拒绝的客体。CAP_MAC_ADMIN 允许进程更改 Smack 数据,包括
+规则和属性。
+
+Smack 网络
+~~~~~~~~~~
+
+如前所述,Smack 对网络协议传输强制执行访问控制。Smack 进程发送的每个数据包
+都带有其 Smack 标签。这是通过在 IP 数据包头部添加 CIPSO 标签实现的。每个接
+收的数据包都应有一个标识标签的 CIPSO 标签,如果没有,则假定为网络环境标签。
+在数据包投递之前,会检查数据包上标签对应的主体是否对接收进程具有写访问权限,
+如果不满足,数据包将被丢弃。
+
+CIPSO 配置
+~~~~~~~~~~
+
+通常不需要指定 CIPSO 配置。系统使用的默认值可以处理所有内部情况。Smack 会
+自动组合 CIPSO 标签值以匹配正在使用的 Smack 标签,无需管理员干预。进入系统
+的未标记数据包将被赋予环境标签。
+
+当可能遇到来自非 Smack 但支持 CIPSO 的系统时,Smack 需要额外配置。通常这
+会是 Trusted Solaris 系统,但还有其他较少部署的系统存在。CIPSO 为每个数据包
+提供 3 个重要值:解释域(DOI)、级别(level)和类别集(category set)。DOI
+用于标识一组使用兼容标记方案的系统,Smack 系统上指定的 DOI 必须与远程系统
+匹配,否则数据包将被丢弃。DOI 的默认值为 3。该值可以从 /sys/fs/smackfs/doi
+读取,也可以通过写入 /sys/fs/smackfs/doi 来更改。
+
+标签和类别集被映射为 /etc/smack/cipso 中定义的 Smack 标签。
+
+Smack/CIPSO 映射的形式为::
+
+	smack level [category [category]*]
+
+Smack 不要求 level 或 category 集合以任何特定方式相互关联,也不基于它们假
+定或分配访问权限。一些映射示例::
+
+	TopSecret 7
+	TS:A,B    7 1 2
+	SecBDE    5 2 4 6
+	RAFTERS   7 12 26
+
+Smack 标签中允许使用 ":" 和 "," 字符,但没有特殊含义。
+
+Smack 标签到 CIPSO 值的映射通过写入 /sys/fs/smackfs/cipso2 来定义。
+
+除显式映射外,Smack 还支持直接 CIPSO 映射。其中一个 CIPSO 级别用于指示数据
+包中传递的类别集实际上是 Smack 标签的编码。默认使用的级别是 250。该值可以
+从 /sys/fs/smackfs/direct 读取,并通过写入 /sys/fs/smackfs/direct 来更改。
+
+套接字属性
+~~~~~~~~~~
+
+有两个属性与套接字相关联。这些属性只能由特权任务设置,但任何任务都可以读取
+自己套接字的这些属性。
+
+  SMACK64IPIN:
+	任务对象的 Smack 标签。一个将强制执行策略的特权程序可以将其设置
+	为 star 标签。
+
+  SMACK64IPOUT:
+	随传出数据包传输的 Smack 标签。特权程序可以将其设置为希望与之通
+	信的另一个任务的标签。
+
+带有 BSD 地址的 UNIX 域套接字(UDS)既是文件系统中的文件也是套接字。作为文
+件,它携带 SMACK64 属性。此属性不参与 Smack 安全强制执行,并被不可变地分配
+标签 "*"。
+
+Smack Netlabel 例外
+~~~~~~~~~~~~~~~~~~~
+
+你会发现带有标签的应用程序经常需要与外部未标记的世界通信。为此,有一个特殊
+文件 /sys/fs/smackfs/netlabel,你可以在其中添加例外,格式为::
+
+	@IP1	   LABEL1 或
+	@IP2/MASK  LABEL2
+
+这意味着,如果你的应用程序对 LABEL1 具有写访问权限,则它将对 @IP1 具有未标
+记访问权限;如果它对 LABEL2 具有写访问权限,则将对子网 @IP2/MASK 具有访问
+权限。
+
+/sys/fs/smackfs/netlabel 文件中的条目按最长掩码优先进行匹配,类似于无类别
+IPv4 路由。
+
+其中还可以使用特殊标签 '@' 和选项 '-CIPSO'::
+
+	@      表示互联网,任何标签的应用程序都可以访问它
+	-CIPSO 表示标准 CIPSO 网络
+
+如果你不知道 CIPSO 是什么并且不打算使用它,只需执行::
+
+	echo 127.0.0.1 -CIPSO > /sys/fs/smackfs/netlabel
+	echo 0.0.0.0/0 @      > /sys/fs/smackfs/netlabel
+
+如果在 192.168.0.0/16 局域网中使用 CIPSO 同时还需要未标记的互联网访问::
+
+	echo 127.0.0.1      -CIPSO > /sys/fs/smackfs/netlabel
+	echo 192.168.0.0/16 -CIPSO > /sys/fs/smackfs/netlabel
+	echo 0.0.0.0/0      @      > /sys/fs/smackfs/netlabel
+
+为 Smack 编写应用程序
+---------------------
+
+在 Smack 系统上将运行三类应用程序。应用程序如何与 Smack 交互,决定了它需要
+做些什么才能在 Smack 下正常工作。
+
+不了解 Smack 的应用程序
+-----------------------
+
+绝大多数应用程序完全不需要关心 Smack 的独特属性。由于调用程序不会影响与进程
+关联的 Smack 标签,唯一可能引起关注的是进程是否对程序具有执行访问权限。
+
+了解 Smack 的应用程序
+---------------------
+
+有些程序可以通过了解 Smack 而变得更好用,但它们本身不做安全决策。ls(1) 命令
+就是这样一个程序的例子。
+
+强制执行 Smack 的应用程序
+-------------------------
+
+这些是特殊的程序,不仅了解 Smack,还参与系统策略的执行。在大多数情况下,这
+些是设置用户会话的程序。还有一些网络服务,为以不同标签运行的进程提供信息。
+
+文件系统接口
+------------
+
+Smack 使用扩展属性在文件系统对象上维护标签。可以使用 getxattr(2) 获取文件、
+目录或其他文件系统对象的 Smack 标签::
+
+	len = getxattr("/", "security.SMACK64", value, sizeof (value));
+
+将把根目录的 Smack 标签放入 value 中。特权进程可以使用 setxattr(2) 设置文件
+系统对象的 Smack 标签::
+
+	len = strlen("Rubble");
+	rc = setxattr("/foo", "security.SMACK64", "Rubble", len, 0);
+
+如果程序具有适当特权,将把 /foo 的 Smack 标签设置为 "Rubble"。
+
+套接字接口
+----------
+
+可以使用 fgetxattr(2) 读取套接字属性。
+
+特权进程可以使用 fsetxattr(2) 设置传出数据包的 Smack 标签::
+
+	len = strlen("Rubble");
+	rc = fsetxattr(fd, "security.SMACK64IPOUT", "Rubble", len, 0);
+
+如果程序具有适当特权,将把从该套接字发出的数据包的 Smack 标签设置为
+"Rubble"::
+
+	rc = fsetxattr(fd, "security.SMACK64IPIN, "*", strlen("*"), 0);
+
+如果程序具有适当特权,将把 Smack 标签 "*" 设置为检查传入数据包的客体标签。
+
+管理
+----
+
+Smack 支持一些挂载选项:
+
+  smackfsdef=label:
+	指定分配给缺少 Smack 标签扩展属性的文件的标签。
+
+  smackfsroot=label:
+	指定分配给文件系统根目录的标签(如果它缺少 Smack 扩展属性)。
+
+  smackfshat=label:
+	指定一个标签,该标签必须对文件系统上设置的所有标签具有读访问权限。
+	尚未强制执行。
+
+  smackfsfloor=label:
+	指定一个标签,文件系统上设置的所有标签必须对其具有读访问权限。
+	尚未强制执行。
+
+  smackfstransmute=label:
+	行为与 smackfsroot 完全一致,只是在挂载的根目录上同时设置
+	transmute 标志。
+
+这些挂载选项适用于所有文件系统类型。
+
+Smack 审计
+----------
+
+如果需要对安全事件进行 Smack 审计,需要在内核配置中设置 CONFIG_AUDIT。
+默认情况下,所有被拒绝的事件都会被审计。可以通过向 /sys/fs/smackfs/logging
+文件写入单个字符来更改此行为::
+
+	0 : 不记录日志
+	1 : 记录被拒绝的事件(默认)
+	2 : 记录被接受的事件
+	3 : 记录被拒绝和被接受的事件
+
+事件以 'key=value' 对的形式记录。对于每个事件,至少会得到主体、客体、请求
+的权限、动作、触发事件的内核函数,以及根据审计事件类型而定的其他键值对。
+
+Bringup 模式
+------------
+
+Bringup 模式提供了日志记录功能,可以使应用程序配置和系统 bringup 更加容易。
+在内核中配置 CONFIG_SECURITY_SMACK_BRINGUP 以启用这些功能。启用 bringup 模
+式后,由于标记了 "b" 访问模式的规则而成功的访问将被记录。当为进程引入新标签
+时,可以积极地添加标记了 "b" 的规则。日志记录使你可以跟踪哪些规则实际上被用
+于该标签。
+
+Bringup 模式的另一个功能是 "unconfined" 选项。向 /sys/fs/smackfs/unconfined
+写入一个标签,将使具有该标签的主体能够访问任何客体,同时使具有该标签的客体
+能被所有主体访问。任何由于标签被设置为 unconfined 而被允许的访问都将被记录。
+此功能是危险的,因为文件和目录可能会在策略正常执行时不允许的位置被创建。
-- 
2.43.0


^ permalink raw reply related

* [PATCH 01/10] docs/zh_CN: add LSM/index Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/index.rst into Chinese, and
update admin-guide/index.rst to include LSM/index in the toctree.

Update the translation through commit 504f231cda56
("doc: ReSTify and split LSM.txt")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/index.rst           | 46 +++++++++++++++++++
 .../translations/zh_CN/admin-guide/index.rst  |  3 +-
 2 files changed, 48 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/index.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/index.rst b/Documentation/translations/zh_CN/admin-guide/LSM/index.rst
new file mode 100644
index 000000000000..21e2b00af544
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/index.rst
@@ -0,0 +1,46 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/index.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+==================
+Linux 安全模块用法
+==================
+
+Linux 安全模块(LSM)框架提供了一种机制,允许新内核扩展在各种安全检查点挂载钩
+子。“module” 一词实际上有些误导,因为这些扩展并不是可加载的内核模块。它们在编
+译时通过 CONFIG_DEFAULT_SECURITY 选择,当多个 LSM 被编译进同一内核时,可以在
+启动时通过 “security=…” 内核命令行参数覆盖。
+
+LSM 接口的主要使用者是强制访问控制(MAC)扩展,提供完整的安全策略。典型例子包
+括 SELinux、Smack、Tomoyo、AppArmor。除了这些大型 MAC 扩展外,还可以利用 LSM
+构建其他扩展,在 Linux 本身未提供的系统行为上做特定修改。
+
+Linux capability 模块始终会被包含。其后可能出现任意数量的“次要”模块,且最多
+只有一个“主要”模块。有关 capability 模块的详细信息,请参见 Linux man‑pages
+项目中的 ``capabilities(7)`` 手册页。
+
+可以通过读取 ``/sys/kernel/security/lsm`` 查看当前激活的安全模块列表。该列表
+以逗号分隔,并始终包括 capability 模块。列表的顺序即检查顺序。capability 模
+块始终排在第一位,如果系统配置了的话,随后是所有“次要”模块(例如 Yama),然后
+是“主要”模块(例如 SELinux)。
+
+与“主要”安全模块关联的进程属性应通过 ``/proc/.../attr`` 中的特殊文件访问和维
+护。安全模块可能在该目录下维护以其名字命名的子目录,例如
+``/proc/.../attr/smack`` 由 Smack 模块提供,包含其所有专用文件。
+``/proc/.../attr`` 中的文件仍然是为提供子目录的模块保留的旧接口。
+
+.. toctree::
+   :maxdepth: 1
+
+   apparmor
+   LoadPin
+   SELinux
+   Smack
+   tomoyo
+   Yama
+   SafeSetID
+   ipe
+   landlock
diff --git a/Documentation/translations/zh_CN/admin-guide/index.rst b/Documentation/translations/zh_CN/admin-guide/index.rst
index bd01cf6474c8..10f9e3c577c3 100644
--- a/Documentation/translations/zh_CN/admin-guide/index.rst
+++ b/Documentation/translations/zh_CN/admin-guide/index.rst
@@ -52,10 +52,11 @@ Todolist:
 .. toctree::
    :maxdepth: 1
 
+   LSM/index
+
 Todolist:
 
 *   hw-vuln/index
-*   LSM/index
 *   perf-security
 
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH 00/10] docs/zh_CN: add LSM admin-guide Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module

This patch series adds the Chinese translation for the Linux Security Module
(LSM) admin-guide documentation, including the main index and all ten
sub-pages: apparmor, LoadPin, SELinux, Smack, tomoyo, Yama, SafeSetID,
ipe, and landlock.  The admin-guide toctree is also updated to list the
newly translated LSM/index.

The original English documentation was restructured into reStructuredText
by Kees Cook in commit 504f231cda56 ("doc: ReSTify and split LSM.txt"),
and each sub-page has been kept up-to-date with subsequent mainline changes.


base: https://git.kernel.org/pub/scm/linux/kernel/git/alexs/linux.git docs-next



Yan Zhu (10):
  docs/zh_CN: add LSM/index Chinese translation
  docs/zh_CN: add LSM/apparmor Chinese translation
  docs/zh_CN: add LSM/LoadPin Chinese translation
  docs/zh_CN: add LSM/SELinux Chinese translation
  docs/zh_CN: add LSM/Smack Chinese translation
  docs/zh_CN: add LSM/tomoyo Chinese translation
  docs/zh_CN: add LSM/Yama Chinese translation
  docs/zh_CN: add LSM/SafeSetID Chinese translation
  docs/zh_CN: add LSM/ipe Chinese translation
  docs/zh_CN: add LSM/landlock Chinese translation

 .../zh_CN/admin-guide/LSM/LoadPin.rst         |  33 +
 .../zh_CN/admin-guide/LSM/SELinux.rst         |  45 ++
 .../zh_CN/admin-guide/LSM/SafeSetID.rst       |  82 ++
 .../zh_CN/admin-guide/LSM/Smack.rst           | 722 +++++++++++++++++
 .../zh_CN/admin-guide/LSM/Yama.rst            |  71 ++
 .../zh_CN/admin-guide/LSM/apparmor.rst        |  59 ++
 .../zh_CN/admin-guide/LSM/index.rst           |  46 ++
 .../zh_CN/admin-guide/LSM/ipe.rst             | 723 ++++++++++++++++++
 .../zh_CN/admin-guide/LSM/landlock.rst        | 169 ++++
 .../zh_CN/admin-guide/LSM/tomoyo.rst          |  63 ++
 .../translations/zh_CN/admin-guide/index.rst  |   3 +-
 11 files changed, 2015 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/LoadPin.rst
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/SELinux.rst
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/SafeSetID.rst
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/Smack.rst
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/Yama.rst
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/apparmor.rst
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/index.rst
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/ipe.rst
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/landlock.rst
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/tomoyo.rst

-- 
2.43.0


^ permalink raw reply

* [PATCH 07/10] docs/zh_CN: add LSM/Yama Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/Yama.rst into Chinese.

Update the translation through commit 9d1bd9e8e028
("doc: yama: Swap HTTP for HTTPS and replace dead link")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/Yama.rst            | 71 +++++++++++++++++++
 1 file changed, 71 insertions(+)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/Yama.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/Yama.rst b/Documentation/translations/zh_CN/admin-guide/LSM/Yama.rst
new file mode 100644
index 000000000000..ada3ec079432
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/Yama.rst
@@ -0,0 +1,71 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/Yama.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+
+====
+Yama
+====
+
+Yama 是一种 Linux 安全模块(LSM),用于收集系统范围内的 DAC(自主访问控制)
+安全保护,这些保护并不是由内核本身直接处理的。在编译时可通过
+``CONFIG_SECURITY_YAMA`` 选择,并可在运行时通过 ``/proc/sys/kernel/yama``
+中的 sysctl 接口控制:
+
+ptrace_scope
+============
+
+随着 Linux 的流行度提升,它将成为更大的恶意软件攻击目标。Linux 进程接口的一个
+突出弱点是单个用户能够检查其拥有的任意进程的内存和运行状态。例如,若 Pidgin
+被入侵,攻击者即可附加到其他运行中的进程(如 Firefox、SSH 会话、GPG 代理等),
+提取更多凭证,并在不依赖用户钓鱼的情况下扩大攻击范围。
+
+这不是理论上的问题。已有文献记载 `SSH 会话劫持 <yama_ssh_hijack_>`_ 和
+`任意代码注入 <yama_code_injection_>`_ 攻击,这些攻击在允许 ptrace 如前所述
+时仍然可能发生。由于 ptrace 并不是非开发者和非管理员常用的功能,系统构建者应
+当能够选择关闭此调试机制。
+
+一种解决方案是某些应用使用 ``prctl(PR_SET_DUMPABLE, ...)`` 明确禁止 ptrace
+附加(如 ssh‑agent),但大多数并未如此。更通用的方案是仅允许父进程向子进程的
+ptrace(即 ``gdb <child>``、``strace <child>`` 仍可工作),或通过
+``CAP_SYS_PTRACE``(即 root 仍可使用 ``gdb --pid=PID``、``strace -p PID``)。
+
+在模式 1 中,软件可以通过 ``prctl(PR_SET_PTRACER, pid, ...)`` 为调试进程与其
+子进程之间定义特定关系。子进程可声明哪些进程(及其后代)被允许调用
+``PTRACE_ATTACH``。每个子进程同一时间只能有一个此类声明的调试进程。例如 KDE、
+Chromium、Firefox 的崩溃处理器以及 Wine 用于相互 ptrace 的进程均采用此方式。
+若进程希望完全禁用这些限制,可调用
+``prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, ...)``,从而允许任何已被允许的进
+程(即使在外部 pid 命名空间)进行附加。
+
+sysctl 配置如下(仅在拥有 ``CAP_SYS_PTRACE`` 时可写):
+
+0 - 经典 ptrace 权限:
+    一个进程可以对任何在相同 uid 下运行的其他进程执行 `PTRACE_ATTACH`
+    操作,只要目标进程是可转储的(即未转换 uid、未以特权启动或未调用过
+    `prctl(PR_SET_DUMPABLE...)`)。同样,`PTRACE_TRACEME` 保持不变。
+
+1 - 限制性 ptrace:
+    进程必须与其欲 attach 的子进程预先建立关系。默认关系为仅限其后代,且满
+    足上述经典条件。若要更改这种关系,子进程可以调用
+    ``prctl(PR_SET_PTRACER, debugger, ...)`` 来声明允许的调试器 PID,以便在
+    该子进程上调用 ``PTRACE_ATTACH``。使用 ``PTRACE_TRACEME`` 则保持不变。
+
+2 - 仅管理员可 attach:
+    只有具备 ``CAP_SYS_PTRACE`` 权限的进程才能使用 ptrace,无论是通过
+    ``PTRACE_ATTACH`` 还是通过子进程调用 ``PTRACE_TRACEME``)。
+
+3 - 禁止 attach:
+    任意进程不可使用 ``PTRACE_ATTACH`` 或 ``PTRACE_TRACEME``。一旦设定,此值
+    不可更改。
+
+最初的仅限于子进程的逻辑源自 grsecurity 中的限制条件制定的。
+
+.. _yama_ssh_hijack:
+ https://www.blackhat.com/presentations/bh-usa-05/bh-us-05-boileau.pdf
+
+.. _yama_code_injection:
+ https://c-skills.blogspot.com/2007/05/injectso.html
-- 
2.43.0


^ permalink raw reply related

* [PATCH 06/10] docs/zh_CN: add LSM/tomoyo Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/tomoyo.rst into Chinese.

Update the translation through commit c6144a21169f
("tomoyo: update project links")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/tomoyo.rst          | 63 +++++++++++++++++++
 1 file changed, 63 insertions(+)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/tomoyo.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/tomoyo.rst b/Documentation/translations/zh_CN/admin-guide/LSM/tomoyo.rst
new file mode 100644
index 000000000000..a354c2ee1b35
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/tomoyo.rst
@@ -0,0 +1,63 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/tomoyo.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+
+======
+TOMOYO
+======
+
+TOMOYO 是什么?
+==============
+
+TOMOYO 是 Linux 内核中一种基于名称的 MAC(强制访问控制)扩展(LSM 模块)。
+
+LiveCD 示例教程可在以下地址获取:
+
+https://tomoyo.sourceforge.net/1.8/ubuntu12.04-live.html
+https://tomoyo.sourceforge.net/1.8/centos6-live.html
+
+虽然这些教程使用的是非 LSM 版本的 TOMOYO,但对理解 TOMOYO 的概念仍有帮助。
+
+如何启用 TOMOYO?
+================
+
+构建内核时启用 ``CONFIG_SECURITY_TOMOYO=y``,并在内核命令行加入
+``security=tomoyo`` 参数。
+
+详情请参阅 https://tomoyo.sourceforge.net/2.6/ 。
+
+文档在哪里?
+===========
+
+用户 ↔ 内核接口文档位于:
+
+https://tomoyo.sourceforge.net/2.6/policy-specification/index.html
+
+我们为研讨会和会议准备的材料可在以下地址获取:
+
+https://sourceforge.net/projects/tomoyo/files/docs/
+
+
+以下列出了三个方面精选的资料:
+
+TOMOYO 是什么?
+  TOMOYO Linux Overview
+    https://sourceforge.net/projects/tomoyo/files/docs/lca2009-takeda.pdf
+  TOMOYO Linux: pragmatic and manageable security for Linux
+    https://sourceforge.net/projects/tomoyo/files/docs/freedomhectaipei-tomoyo.pdf
+  TOMOYO Linux: A Practical Method to Understand and Protect Your Own Linux Box
+    https://sourceforge.net/projects/tomoyo/files/docs/PacSec2007-en-no-demo.pdf
+
+TOMOYO 能干什么?
+  Deep inside TOMOYO Linux
+    https://sourceforge.net/projects/tomoyo/files/docs/lca2009-kumaneko.pdf
+  The role of "pathname based access control" in security.
+    https://sourceforge.net/projects/tomoyo/files/docs/lfj2008-bof.pdf
+
+TOMOYO 的历史?
+  Realities of Mainlining
+    https://sourceforge.net/projects/tomoyo/files/docs/lfj2008.pdf
-- 
2.43.0


^ permalink raw reply related

* [PATCH 04/10] docs/zh_CN: add LSM/SELinux Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/SELinux.rst into Chinese.

Update the translation through commit 17bd3c01667a
("documentation: add links to SELinux resources")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/SELinux.rst         | 45 +++++++++++++++++++
 1 file changed, 45 insertions(+)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/SELinux.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/SELinux.rst b/Documentation/translations/zh_CN/admin-guide/LSM/SELinux.rst
new file mode 100644
index 000000000000..4962c7c24ec2
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/SELinux.rst
@@ -0,0 +1,45 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/SELinux.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+
+=======
+SELinux
+=======
+
+关于 SELinux 内核子系统的信息可以在以下链接获取:
+
+    https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git/tree/README.md
+
+    https://github.com/selinuxproject/selinux-kernel/wiki
+
+关于 SELinux 用户空间的资料可以在以下地址找到:
+
+    https://github.com/SELinuxProject/selinux/wiki
+
+如果你想使用 SELinux,通常需要使用发行版提供的策略,或者从以下地址获取最新的
+参考策略:
+
+    https://github.com/SELinuxProject/refpolicy
+
+如果你仅需安装一个用于测试的示例策略,可以使用位于 scripts/selinux 下的
+``mdp`` 工具。注意这要求系统已经安装了 SELinux 用户空间工具,尤其需要
+``checkpolicy`` 来编译内核策略,以及 ``setfiles`` 和 ``fixfiles`` 来为文件系
+统打标签。
+
+    1. 编译内核并启用 SELinux。
+    2. 运行 ``make`` 编译 ``mdp``。
+    3. 确认当前未启用 SELinux 且未加载真实策略。
+       如果已启用,请在继续前重启并在 SELinux 已禁用状态下操作。
+    4. 执行 ``install_policy.sh``::
+
+        cd scripts/selinux
+        sh install_policy.sh
+
+第 4 步会为当前内核生成一个新的示例策略,包含单一的 SELinux 用户、角色和类型。
+它会编译该策略,将 ``SELINUXTYPE`` 设置为 ``dummy``
+(写入 ``/etc/selinux/config``),并将策略以 ``dummy`` 名称安装,同时重新标
+记文件系统。
-- 
2.43.0


^ permalink raw reply related

* [PATCH 02/10] docs/zh_CN: add LSM/apparmor Chinese translation
From: Yan Zhu @ 2026-06-12 15:58 UTC (permalink / raw)
  To: alexs, si.yanteng, corbet, mic
  Cc: dzm91, skhan, gnoack, zhuyan2015, linux-doc,
	linux-security-module
In-Reply-To: <cover.1781105672.git.zhuyan2015@qq.com>

Translate Documentation/admin-guide/LSM/apparmor.rst into Chinese.

Update the translation through commit d00c2359fc18
("Docs: Update LSM/apparmor.rst")

Assisted-by: Claude:deepseek-4-pro
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
 .../zh_CN/admin-guide/LSM/apparmor.rst        | 59 +++++++++++++++++++
 1 file changed, 59 insertions(+)
 create mode 100644 Documentation/translations/zh_CN/admin-guide/LSM/apparmor.rst

diff --git a/Documentation/translations/zh_CN/admin-guide/LSM/apparmor.rst b/Documentation/translations/zh_CN/admin-guide/LSM/apparmor.rst
new file mode 100644
index 000000000000..6b0638aedacb
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/LSM/apparmor.rst
@@ -0,0 +1,59 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/LSM/apparmor.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+
+========
+AppArmor
+========
+
+AppArmor 是什么?
+================
+
+AppArmor 是 Linux 内核的 MAC(强制访问控制)安全扩展。它实现了一个基于任务
+(profile)的策略,即从用户空间创建并加载任务的配置文件。系统中未为任务定义
+profile 的进程将在 unconfined(未限制)状态下运行,这相当于标准的 Linux DAC
+权限。
+
+如何启用和禁用
+==============
+
+设置 ``CONFIG_SECURITY_APPARMOR=y``
+
+如果希望将 AppArmor 设为默认的安全模块,请使用以下配置::
+
+   CONFIG_DEFAULT_SECURITY_APPARMOR=y
+
+``CONFIG_LSM`` 参数用于管理 LSM(Linux Security Module)的顺序和选择。请在列
+表中将 apparmor 指定为第一个 “主要” 模块(例如 AppArmor、SELinux、Smack)。
+
+构建内核
+--------
+
+如果 AppArmor 不是默认的安全模块,可以在内核命令行上添加
+``security=apparmor`` 来启用。
+
+如果 AppArmor 是默认的安全模块,则可以在内核命令行上添加
+``apparmor=0, security=XXXX``(其中 ``XXXX`` 为有效的安全模块)来禁用它。
+
+要让 AppArmor 在标准的 Linux DAC 权限之外施加任何限制,必须从用户空间向内核
+加载策略,具体请参阅文档和工具链接。
+
+文档
+====
+
+文档可在 wiki 上找到,链接如下。
+
+链接
+====
+
+Mailing List - apparmor@lists.ubuntu.com
+
+Wiki - http://wiki.apparmor.net
+
+User space tools - https://gitlab.com/apparmor
+
+Kernel module - git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor
-- 
2.43.0



^ permalink raw reply related

* Re: [PATCH] Add LoadPin support for eBPF program loading
From: Alexei Starovoitov @ 2026-06-12 15:20 UTC (permalink / raw)
  To: David Windsor
  Cc: alex.roberts109, Kees Cook, Paul Moore, James Morris,
	Serge E . Hallyn, LKML, LSM List, bpf, Alexei Starovoitov,
	KP Singh
In-Reply-To: <20260612000825.105100-1-dwindsor@gmail.com>

On Thu, Jun 11, 2026 at 5:08 PM David Windsor <dwindsor@gmail.com> wrote:
>
> On Thu, Jun 11, 2026 at 01:59:10PM -0500, Alex Roberts wrote:
> > +static int loadpin_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
> > +                               struct bpf_token *token, bool is_kernel)
> > +{
> > +     int res = 0;
> > +     struct file *exe_file = NULL;
> > +     struct mm_struct *mm = current->mm;
> > +
> > +     if (is_kernel || !mm)
> > +             return 0;
> > +
> > +     exe_file = get_mm_exe_file(mm);
> > +     if (!exe_file)
> > +             return 0;
> > +
> > +     res = loadpin_check(exe_file, READING_EBPF);
>
> Why are we checking current here? IIUC this will be whoever calls
> bpf(2), which would be the loader, which would then be able to load bpf
> programs from an untrusted source.
>
> In the kmod case loadpin_check() sees the .ko itself.

See sashiko comments:
- [High] The LoadPin eBPF trust mechanism can be trivially bypassed
using standard system interpreters like the dynamic linker (`ld.so`).
- [High] The LoadPin eBPF trust mechanism can be bypassed by a
privileged attacker using prctl(PR_SET_MM_EXE_FILE).

and the bot is correct.
This patch is pointless.

^ permalink raw reply

* Re: [PATCH bpf-next 0/5] Verify BPF signed loader at load time
From: Daniel Borkmann @ 2026-06-12  9:33 UTC (permalink / raw)
  To: Paul Moore
  Cc: ast, kpsingh, James.Bottomley, bboscaccy, memxor, torvalds, bpf,
	linux-security-module
In-Reply-To: <CAHC9VhTA7tOp16wGzb1QYnkJsVz1X3oNcmT6bb_9334=B2smEw@mail.gmail.com>

On 6/12/26 12:56 AM, Paul Moore wrote:
> On Wed, Jun 10, 2026 at 7:03 PM Daniel Borkmann <daniel@iogearbox.net> wrote:
>>
>> The BPF signing scheme signs a light skeleton's loader program and lets
>> the loader vouch for everything else: bpftool bakes the SHA256 of the
>> metadata map into the loader's instructions, signs the instructions, and
>> the loader compares the (frozen, exclusive) map against that hash from
>> within BPF once it runs. The construction is sound as a trusted hash
>> chain, but the kernel itself never attests the metadata, and that split
>> has been the recurring objection from the LSM / integrity side since the
>> scheme was proposed.
>>
>> This proposal closes both gaps by having the kernel verify the metadata
>> at BPF_PROG_LOAD time, before the LSM admission hook and before the
>> verifier, /without/ growing the UAPI. A signed loader binds its metadata
>> map(s) through the existing fd_array/fd_array_cnt, and exclusive maps
>> are already bound to the loader's digest via excl_prog_hash. When a
>> signature is present, the kernel collects the exclusive maps from the
>> fd_array and appends their frozen contents to the instructions before
>> PKCS#7 verification, so the signature covers ...
>>
>>      insns || metadata_0 || metadata_1 || [...]
>>
>> ... in fd_array order. The in-loader hash check is dropped from the
>> gen_loader entirely: generated loaders carry no verification logic
>> anymore, and signing or verifying a skeleton becomes an ordinary CMS
>> operation over bytes that sit verbatim in the skeleton, reproducible
>> offline. A signed program is either BPF_SIG_UNSIGNED or BPF_SIG_VERIFIED
>> with nothing in between.
> 
> I'll be honest and say I'm a bit surprised to see this patchset,
> especially since KP and Alexei argued so strongly against this
> signature scheme, preferring KP's scheme where the loader verified the
> maps.  I'd be curious to hear the reason for the change of heart if
> you can share it.  Regardless of the motivation for this change, I
> obviously think this is a significant improvement over KP's signature
> scheme which shipped in Linux v6.18.
> 
> I also think it is worth mentioning the similarities to work Blaise
> did before the most recent Hornet version:
> 
> https://lore.kernel.org/linux-security-module/20250929213520.1821223-1-bboscaccy@linux.microsoft.com/
> 
> While Blaise's patchset added to the UAPI, that was done simply to
> retain compatibility with KP's signature scheme; your patchset does
> without any UAPI additions, but loses compatibility with existing
> signed lskels.  Beyond that, the basic signature scheme between
> Blaise's patchset and what you are proposing appears the same ...
> which is a good thing as far as I'm concerned.

For the rework, I've mainly been looking at KP's most recent series as
well as some of the past discussions. Mainly trying to see if we can get
away with a simpler model and without having to pull in BTF support for
the signed loader just for the extra kfunc. Over the last couple of weeks,
I've also build user space tooling on my side to experiment with some
real world application which the signed loader would have to deal with.

For sending a v2, I'll have to move this slightly deeper into verifier
side but before the main verifier work happens in order to solve the
exclusive map race issue, and additionally we could also utilize the
verifier log to tell the user that the signature validation failed.

I think a couple of things would still stand out imo: i) we sign over the
raw bytes, not the derived hash anymore, so the hashing is only used in the
context to tie the map to the loader prog, but not anymore for the signature.
ii) its one single scheme and not a parallel branch, so the main loader is
built upon the updated signing scheme rather than having this as an option
on the side; iow this replaces the in-loader check and there's a single PKCS#7-
over-bytes path, not an 'if (signature_maps_size)' fork; and iii) given we
expose the verification result in the BPF prog, we also don't need a new LSM
hook and can just piggy back on the existing security_bpf_prog() which also
has the possibility to still reject late at this point.

Anyway, I hope we can find a compromise here.. will keep you posted on v2
of the series.

Thanks,
Daniel

^ permalink raw reply

* Re: [PATCH v3 1/3] landlock: Require LANDLOCK_ACCESS_FS_MAKE_WHITEOUT for RENAME_WHITEOUT
From: Günther Noack @ 2026-06-12  8:34 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Christian Brauner, linux-security-module, Paul Moore,
	Amir Goldstein, Miklos Szeredi, Serge Hallyn, Stephen Smalley
In-Reply-To: <20260610.uoMee2quoo9k@digikod.net>

Thanks for the review!

On Wed, Jun 10, 2026 at 03:38:56PM +0200, Mickaël Salaün wrote:
> Making MAKE_CHAR not covering MAKE_WHITEOUT is not addressed (see
> previous discussion).  MAKE_CHAR should not restrict whiteout creation
> *if* MAKE_WHITEOUT is handled.

(This is option (3) from your reply to V1 [1].)
        
I am skeptical of this approach, because it complicates how userspace
needs to deal with this access right.  Consider the following
scenario: A program wants to install the policy:

 * DENY  MAKE_WHITEOUT, MAKE_CHAR
 * ALLOW MAKE_WHITEOUT             in /foo  (path_beneath rule)

Then, if the kernel ABI predates make-whiteout, with the usual
best-effort fallback (clearing out the unsupported bits), this ruleset
becomes:

 * DENY  MAKE_CHAR
 * (no ALLOW rule)

But this ruleset is incorrect, because it denies mknod("/foo/x",
S_IFCHR | mode, makedev(0, 0)) in /foo, which was explicitly allowed
in the earlier ruleset.

So in order to implement the best-effort fallback, I guess userspace
libraries would now have to take into account whether there are any
rules where MAKE_WHITEOUT is specifically allowed, and if so, they
can't restrict MAKE_CHAR either?  I find this a bit complicated and I
think it's foreseeable that library implementers will predominantly
get this wrong.


Let me circle back to the other options you mentioned in [1], quoting
them here for reference:
> I see four options:
> 
> 1. Consider whiteouts as regular files and make them handled by
>    LANDLOCK_ACCESS_FS_MAKE_REG.  This would require an erratum and would
>    make sense for direct mknod calls, but it would be weird for
>    renameat2 calls than move a file and should only require
>    LANDLOCK_ACCESS_FS_REMOVE_FILE from the user point of view.

It would be weird for renameat2 calls to require MAKE_REG in the
source directory, but the weirdness would only affect
fuse-overlayfs-style programs and could be documented explicitly for
them for the case that they start using Landlock.

Normal programs that just call rename() on an existing FUSE-Overlayfs
filesystem would *not* require the MAKE_REG right, because the FUSE
process would do that on their behalf with the FUSE processes'
credentials.

> 
> 2. Add a new LANDLOCK_ACCESS_FS_MAKE_WHITEOUT right to handle whitout
>    creation (direct and indirect?) and keep LANDLOCK_ACCESS_FS_MAKE_CHAR
>    handle direct whiteout creation (and don't backport anything).  It
>    looks inconsistent from an access control point of view.

MAKE_WHITEOUT to handle rename(RENAME_WHITEOUT) and MAKE_CHAR to
handle mknod(chardev (0, 0)) -- This is a bit inconsistent, but it
does not make a difference for any programs other than the ones
calling rename(RENAME_WHITEOUT) (i.e., overlayfs-fuse), and it could
be documented for that one use case.

I find this a pragmatic balance, and it does not require special logic
for the best-effort fallback either.  Could you be persuaded to go
this route instead?

> 3. Add a new LANDLOCK_ACCESS_FS_MAKE_WHITEOUT right and, when handled,
>    make LANDLOCK_ACCESS_FS_MAKE_CHAR not handle whiteout.  This would be
>    a bit weird from a kernel point of view but it should work well for
>    users while still forbidding direct whiteout creation.

Except for the best-effort fallback, which is IMHO prone to
implementation bugs. (see above)

On the side, the implementation of this is also non-trivial: In order
to check for mknod(..., makedev(0, 0)), we need to check
layer-by-layer whether the layer handles MAKE_WHITEOUT and then either
check for MAKE_CHAR or MAKE_WHITEOUT.


> 4. Add a new LANDLOCK_ACCESS_FS_MAKE_WHITEOUT right and make
>    LANDLOCK_ACCESS_FS_MAKE_CHAR never handle whiteout (and backport
>    MAKE_CHAR fix with an errata).  This would be consistent but backport
>    a way to directly create whiteouts (e.g. with mknod).

It's mostly theoretical, but lifting the mknod(chardev (0,0))
restriction for normal mknod() calls and calling it an erratum seems
surprising as well, because it would relax security guarantees for
existing programs.

I also pondered the alternative of creating an erratum but
intentionally *not* backporting it, but even in that case, that
surprising erratum still affects older programs which are deployed on
newer kernels.


Revisiting this discussion, I'd lean towards option 1 or 2 -- could
you be persuaded towards one of these?

I have a slight preference for option 1 (using MAKE_REG) because it
would be a narrow fix that could be backported to older kernels as
well and would not require a new access right.  Given that the use
case for RENAME_WHITEOUT is really only for FUSE-OverlayFS and given
that FUSE-OverlayFS anyway needs MAKE_REG permissions there, I have
trouble imagining a scenario where a separate access right for
MAKE_WHITEOUT is needed in a policy.  It seems like a pragmatic
choice.


> Specific tests should check that all
> these cases are proprely handled.
>
> There is no documentation update related to the new feature.  A note
> should also explain what exactly is a whiteout and why it is not
> considered a character device (see previous discussions).
> 
> The sandboxer is not updated.
> 
> There is no audit tests.

Acknowledged, these were missing.

(I was initially hoping that this bug report wouldn't expand into a
full-fledged feature with its own access right constant, but it is
correct that this is all required in that case... :-/)

Will add this for the next patch set revision if it is still needed.

—Günther

[1] https://lore.kernel.org/all/20260414.Lae5ida1eeGh@digikod.net/

^ permalink raw reply

* Re: [PATCH] Add LoadPin support for eBPF program loading
From: kernel test robot @ 2026-06-12  6:17 UTC (permalink / raw)
  To: Alex Roberts via B4 Relay, Kees Cook, Paul Moore, James Morris,
	Serge E. Hallyn
  Cc: oe-kbuild-all, linux-kernel, linux-security-module, bpf,
	Alexei Starovoitov, KP Singh, Alex Roberts
In-Reply-To: <20260611-b4-rfc-loadpin-ebpf-v1-1-11a6c8e6170d@outlook.com>

Hi Alex,

kernel test robot noticed the following build errors:

[auto build test ERROR on 122b52f0bab007ebeb414c8280c1def17b9ed1f4]

url:    https://github.com/intel-lab-lkp/linux/commits/Alex-Roberts-via-B4-Relay/Add-LoadPin-support-for-eBPF-program-loading/20260612-031559
base:   122b52f0bab007ebeb414c8280c1def17b9ed1f4
patch link:    https://lore.kernel.org/r/20260611-b4-rfc-loadpin-ebpf-v1-1-11a6c8e6170d%40outlook.com
patch subject: [PATCH] Add LoadPin support for eBPF program loading
config: nios2-randconfig-002-20260612 (https://download.01.org/0day-ci/archive/20260612/202606121426.NrJtdO3K-lkp@intel.com/config)
compiler: nios2-linux-gcc (GCC) 11.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260612/202606121426.NrJtdO3K-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202606121426.NrJtdO3K-lkp@intel.com/

All errors (new ones prefixed by >>):

   In file included from <command-line>:
   security/selinux/hooks.c: In function 'selinux_kernel_read_file':
>> include/linux/compiler_types.h:699:45: error: call to '__compiletime_assert_916' declared with attribute error: New kernel_read_file_id introduced; update SELinux!
     699 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |                                             ^
   include/linux/compiler_types.h:680:25: note: in definition of macro '__compiletime_assert'
     680 |                         prefix ## suffix();                             \
         |                         ^~~~~~
   include/linux/compiler_types.h:699:9: note: in expansion of macro '_compiletime_assert'
     699 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/linux/build_bug.h:40:37: note: in expansion of macro 'compiletime_assert'
      40 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
         |                                     ^~~~~~~~~~~~~~~~~~
   security/selinux/hooks.c:4412:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
    4412 |         BUILD_BUG_ON_MSG(READING_MAX_ID > 8,
         |         ^~~~~~~~~~~~~~~~
   security/selinux/hooks.c: In function 'selinux_kernel_load_data':
>> include/linux/compiler_types.h:699:45: error: call to '__compiletime_assert_917' declared with attribute error: New kernel_load_data_id introduced; update SELinux!
     699 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |                                             ^
   include/linux/compiler_types.h:680:25: note: in definition of macro '__compiletime_assert'
     680 |                         prefix ## suffix();                             \
         |                         ^~~~~~
   include/linux/compiler_types.h:699:9: note: in expansion of macro '_compiletime_assert'
     699 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/linux/build_bug.h:40:37: note: in expansion of macro 'compiletime_assert'
      40 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
         |                                     ^~~~~~~~~~~~~~~~~~
   security/selinux/hooks.c:4449:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
    4449 |         BUILD_BUG_ON_MSG(LOADING_MAX_ID > 8,
         |         ^~~~~~~~~~~~~~~~


vim +/__compiletime_assert_916 +699 include/linux/compiler_types.h

eb5c2d4b45e3d2 Will Deacon 2020-07-21  685  
eb5c2d4b45e3d2 Will Deacon 2020-07-21  686  #define _compiletime_assert(condition, msg, prefix, suffix) \
eb5c2d4b45e3d2 Will Deacon 2020-07-21  687  	__compiletime_assert(condition, msg, prefix, suffix)
eb5c2d4b45e3d2 Will Deacon 2020-07-21  688  
eb5c2d4b45e3d2 Will Deacon 2020-07-21  689  /**
eb5c2d4b45e3d2 Will Deacon 2020-07-21  690   * compiletime_assert - break build and emit msg if condition is false
eb5c2d4b45e3d2 Will Deacon 2020-07-21  691   * @condition: a compile-time constant condition to check
eb5c2d4b45e3d2 Will Deacon 2020-07-21  692   * @msg:       a message to emit if condition is false
eb5c2d4b45e3d2 Will Deacon 2020-07-21  693   *
eb5c2d4b45e3d2 Will Deacon 2020-07-21  694   * In tradition of POSIX assert, this macro will break the build if the
eb5c2d4b45e3d2 Will Deacon 2020-07-21  695   * supplied condition is *false*, emitting the supplied error message if the
eb5c2d4b45e3d2 Will Deacon 2020-07-21  696   * compiler has support to do so.
eb5c2d4b45e3d2 Will Deacon 2020-07-21  697   */
eb5c2d4b45e3d2 Will Deacon 2020-07-21  698  #define compiletime_assert(condition, msg) \
eb5c2d4b45e3d2 Will Deacon 2020-07-21 @699  	_compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
eb5c2d4b45e3d2 Will Deacon 2020-07-21  700  

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* [PATCH] apparmor: fix use-after-free in policy replacement path
From: Junxiao Chang @ 2026-06-13  6:04 UTC (permalink / raw)
  To: john.johansen, paul, jmorris, serge, apparmor,
	linux-security-module, linux-kernel
  Cc: junxiao.chang

A use-after-free issue can be triggered when running the
following stress-ng workload:

```
sudo stress-ng --apparmor 0 --timeout 30 \
    --oom-avoid-bytes 10% --skip-silent --verbose
```

The warning looks like:

```
refcount_t: addition on 0; use-after-free
aa_replace_profiles+0xbe5/0x12a0
policy_update+0xdb/0x170
profile_replace+0x4b/0xb0
```

The issue can be reproduced on both v7.1-rc7 and Ubuntu
6.17.0-35-generic kernels.

aa_get_profile_loaddata() requires the supplied loaddata object
to hold a valid reference. However, the loaddata reference count
may already have reached zero in the replacement loop, resulting
in a use-after-free condition.

Avoid calling aa_get_profile_loaddata() on loaddata objects with
a zero reference count and skip those entries instead.

Fixes: a0b7091c4de4 ("apparmor: fix race on rawdata dereference")
Signed-off-by: Junxiao Chang <junxiao.chang@intel.com>
---
 security/apparmor/policy.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index b6a5eb4021dbd..98f84d4552697 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -1220,7 +1220,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
 	/* check for duplicate rawdata blobs: space and file dedup */
 	if (!list_empty(&ns->rawdata_list)) {
 		list_for_each_entry(rawdata_ent, &ns->rawdata_list, list) {
-			if (aa_rawdata_eq(rawdata_ent, udata)) {
+			if (kref_read(&rawdata_ent->pcount) && aa_rawdata_eq(rawdata_ent, udata)) {
 				struct aa_loaddata *tmp;
 
 				tmp = aa_get_profile_loaddata(rawdata_ent);
-- 
2.43.0


^ permalink raw reply related

* [PATCH v11 9/9] selftests/landlock: Add tests for invalid use of quiet flag
From: Tingmao Wang @ 2026-06-12  1:48 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
	Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1781228815.git.m@maowtm.org>

Make sure that these calls return EINVAL.

Assisted-by: GitHub-Copilot:claude-opus-4.8
Signed-off-by: Tingmao Wang <m@maowtm.org>
---

Changes in v11:
- Test net/scope quiet fields too

Changes in v4:
- New patch

 tools/testing/selftests/landlock/base_test.c | 116 +++++++++++++++++++
 1 file changed, 116 insertions(+)

diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 84e91fcaa1b2..cbd3c1669951 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -526,4 +526,120 @@ TEST(cred_transfer)
 	EXPECT_EQ(EACCES, errno);
 }
 
+TEST(useless_quiet_rule_fs)
+{
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
+		.quiet_access_fs = 0,
+	};
+	struct landlock_path_beneath_attr path_beneath_attr = {
+		.allowed_access = LANDLOCK_ACCESS_FS_READ_DIR,
+	};
+	int ruleset_fd, root_fd;
+
+	drop_caps(_metadata);
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	root_fd = open("/", O_PATH | O_CLOEXEC);
+	ASSERT_LE(0, root_fd);
+	path_beneath_attr.parent_fd = root_fd;
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
+					&path_beneath_attr,
+					LANDLOCK_ADD_RULE_QUIET));
+	ASSERT_EQ(EINVAL, errno);
+
+	/* Check that the rule had not been added. */
+	ASSERT_EQ(0, close(root_fd));
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	ASSERT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
+	ASSERT_EQ(EACCES, errno);
+}
+
+TEST(useless_quiet_rule_net)
+{
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
+		.quiet_access_net = 0,
+	};
+	struct landlock_net_port_attr net_port_attr = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+		.port = 1024,
+	};
+	int ruleset_fd;
+
+	drop_caps(_metadata);
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	ASSERT_EQ(-1,
+		  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+				    &net_port_attr, LANDLOCK_ADD_RULE_QUIET));
+	ASSERT_EQ(EINVAL, errno);
+
+	ASSERT_EQ(0, close(ruleset_fd));
+}
+
+TEST(invalid_quiet_bits_1)
+{
+	const struct landlock_ruleset_attr ruleset_attr_fs = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
+		.quiet_access_fs = LANDLOCK_ACCESS_FS_WRITE_FILE,
+	};
+	const struct landlock_ruleset_attr ruleset_attr_net = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
+		.quiet_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	const struct landlock_ruleset_attr ruleset_attr_scoped = {
+		.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+		.quiet_scoped = LANDLOCK_SCOPE_SIGNAL,
+	};
+
+	/* Quiet bit set but not part of the handled mask. */
+	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_fs,
+					      sizeof(ruleset_attr_fs), 0));
+	ASSERT_EQ(EINVAL, errno);
+
+	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_net,
+					      sizeof(ruleset_attr_net), 0));
+	ASSERT_EQ(EINVAL, errno);
+
+	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_scoped,
+					      sizeof(ruleset_attr_scoped), 0));
+	ASSERT_EQ(EINVAL, errno);
+}
+
+TEST(invalid_quiet_bits_2)
+{
+	const struct landlock_ruleset_attr ruleset_attr_fs = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
+		.quiet_access_fs = 1ULL << 63,
+	};
+	const struct landlock_ruleset_attr ruleset_attr_net = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
+		.quiet_access_net = 1ULL << 63,
+	};
+	const struct landlock_ruleset_attr ruleset_attr_scoped = {
+		.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+		.quiet_scoped = 1ULL << 63,
+	};
+
+	/* Quiet bit outside of the valid access range. */
+	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_fs,
+					      sizeof(ruleset_attr_fs), 0));
+	ASSERT_EQ(EINVAL, errno);
+
+	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_net,
+					      sizeof(ruleset_attr_net), 0));
+	ASSERT_EQ(EINVAL, errno);
+
+	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_scoped,
+					      sizeof(ruleset_attr_scoped), 0));
+	ASSERT_EQ(EINVAL, errno);
+}
+
 TEST_HARNESS_MAIN
-- 
2.54.0

^ permalink raw reply related

* [PATCH v11 8/9] selftests/landlock: Add tests for quiet flag with scope
From: Tingmao Wang @ 2026-06-12  1:48 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
	Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1781228815.git.m@maowtm.org>

Enhance scoped_audit.connect_to_child and audit_flags.signal to test
interaction with various quiet flag settings.

Signed-off-by: Tingmao Wang <m@maowtm.org>
---

Changes in v4:
- New patch

 tools/testing/selftests/landlock/audit_test.c | 25 ++++--
 .../landlock/scoped_abstract_unix_test.c      | 77 ++++++++++++++++---
 2 files changed, 87 insertions(+), 15 deletions(-)

diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
index d55fe4c5b531..3b344b03199e 100644
--- a/tools/testing/selftests/landlock/audit_test.c
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -794,30 +794,42 @@ FIXTURE(audit_flags)
 FIXTURE_VARIANT(audit_flags)
 {
 	const int restrict_flags;
+	const __u64 quiet_scoped;
 };
 
 /* clang-format off */
 FIXTURE_VARIANT_ADD(audit_flags, default) {
 	/* clang-format on */
 	.restrict_flags = 0,
+	.quiet_scoped = 0,
 };
 
 /* clang-format off */
 FIXTURE_VARIANT_ADD(audit_flags, same_exec_off) {
 	/* clang-format on */
 	.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF,
+	.quiet_scoped = 0,
 };
 
 /* clang-format off */
 FIXTURE_VARIANT_ADD(audit_flags, subdomains_off) {
 	/* clang-format on */
 	.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
+	.quiet_scoped = 0,
 };
 
 /* clang-format off */
 FIXTURE_VARIANT_ADD(audit_flags, cross_exec_on) {
 	/* clang-format on */
 	.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON,
+	.quiet_scoped = 0,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit_flags, signal_quieted) {
+	/* clang-format on */
+	.restrict_flags = 0,
+	.quiet_scoped = LANDLOCK_SCOPE_SIGNAL,
 };
 
 FIXTURE_SETUP(audit_flags)
@@ -861,12 +873,16 @@ TEST_F(audit_flags, signal)
 	pid_t child;
 	struct audit_records records;
 	__u64 deallocated_dom = 2;
+	bool expect_audit = !(variant->restrict_flags &
+			      LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) &&
+			    !(variant->quiet_scoped & LANDLOCK_SCOPE_SIGNAL);
 
 	child = fork();
 	ASSERT_LE(0, child);
 	if (child == 0) {
 		const struct landlock_ruleset_attr ruleset_attr = {
 			.scoped = LANDLOCK_SCOPE_SIGNAL,
+			.quiet_scoped = variant->quiet_scoped,
 		};
 		int ruleset_fd;
 
@@ -883,8 +899,7 @@ TEST_F(audit_flags, signal)
 		EXPECT_EQ(-1, kill(getppid(), 0));
 		EXPECT_EQ(EPERM, errno);
 
-		if (variant->restrict_flags &
-		    LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) {
+		if (!expect_audit) {
 			EXPECT_EQ(-EAGAIN, matches_log_signal(
 						   _metadata, self->audit_fd,
 						   getppid(), self->domain_id));
@@ -911,8 +926,7 @@ TEST_F(audit_flags, signal)
 
 		/* Makes sure there is no superfluous logged records. */
 		EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
-		if (variant->restrict_flags &
-		    LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) {
+		if (!expect_audit) {
 			EXPECT_EQ(0, records.access);
 		} else {
 			EXPECT_EQ(1, records.access);
@@ -936,8 +950,7 @@ TEST_F(audit_flags, signal)
 	    WEXITSTATUS(status) != EXIT_SUCCESS)
 		_metadata->exit_code = KSFT_FAIL;
 
-	if (variant->restrict_flags &
-	    LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) {
+	if (!expect_audit) {
 		/*
 		 * No deallocation record: denials=0 never matches a real
 		 * record.
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
index 72f97648d4a7..d16555f7b0d3 100644
--- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
@@ -293,6 +293,45 @@ FIXTURE_TEARDOWN_PARENT(scoped_audit)
 	EXPECT_EQ(0, audit_cleanup(-1, NULL));
 }
 
+FIXTURE_VARIANT(scoped_audit)
+{
+	const __u64 scoped;
+	const __u64 quiet_scoped;
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, no_quiet)
+{
+	// clang-format on
+	.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+	.quiet_scoped = 0,
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, quiet_abstract_socket)
+{
+	// clang-format on
+	.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+	.quiet_scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, quiet_abstract_socket_2)
+{
+	// clang-format on
+	.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL,
+	.quiet_scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
+			LANDLOCK_SCOPE_SIGNAL,
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, quiet_unrelated)
+{
+	// clang-format on
+	.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL,
+	.quiet_scoped = LANDLOCK_SCOPE_SIGNAL,
+};
+
 /* python -c 'print(b"\0selftests-landlock-abstract-unix-".hex().upper())' */
 #define ABSTRACT_SOCKET_PATH_PREFIX \
 	"0073656C6674657374732D6C616E646C6F636B2D61627374726163742D756E69782D"
@@ -308,6 +347,13 @@ TEST_F(scoped_audit, connect_to_child)
 	char buf;
 	int dgram_client;
 	struct audit_records records;
+	int ruleset_fd;
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.scoped = variant->scoped,
+		.quiet_scoped = variant->quiet_scoped,
+	};
+	bool should_audit =
+		!(variant->quiet_scoped & LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
 
 	/* Makes sure there is no superfluous logged records. */
 	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
@@ -345,7 +391,14 @@ TEST_F(scoped_audit, connect_to_child)
 	EXPECT_EQ(0, close(pipe_child[1]));
 	EXPECT_EQ(0, close(pipe_parent[0]));
 
-	create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd)
+	{
+		TH_LOG("Failed to create a ruleset: %s", strerror(errno));
+	}
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
 
 	/* Signals that the parent is in a domain, if any. */
 	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
@@ -360,14 +413,20 @@ TEST_F(scoped_audit, connect_to_child)
 	EXPECT_EQ(-1, err_dgram);
 	EXPECT_EQ(EPERM, errno);
 
-	EXPECT_EQ(
-		0,
-		audit_match_record(
-			self->audit_fd, AUDIT_LANDLOCK_ACCESS,
-			REGEX_LANDLOCK_PREFIX
-			" blockers=scope\\.abstract_unix_socket path=" ABSTRACT_SOCKET_PATH_PREFIX
-			"[0-9A-F]\\+$",
-			NULL));
+	if (should_audit) {
+		EXPECT_EQ(
+			0,
+			audit_match_record(
+				self->audit_fd, AUDIT_LANDLOCK_ACCESS,
+				REGEX_LANDLOCK_PREFIX
+				" blockers=scope\\.abstract_unix_socket path=" ABSTRACT_SOCKET_PATH_PREFIX
+				"[0-9A-F]\\+$",
+				NULL));
+	}
+
+	/* No other logs */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
 
 	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
 	EXPECT_EQ(0, close(dgram_client));
-- 
2.54.0

^ permalink raw reply related

* [PATCH v11 7/9] selftests/landlock: add tests for quiet flag with net rules
From: Tingmao Wang @ 2026-06-12  1:48 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
	Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1781228815.git.m@maowtm.org>

Tests that:
- Quiet flag works on network rules
- Quiet flag applied to unrelated ports has no effect
- Denied access not in quiet_access_net is still logged

This is not as thorough as the fs tests, but given the shared logic it
should be sufficient.  There is also no "optional" access for network
rules.

Signed-off-by: Tingmao Wang <m@maowtm.org>
Assisted-by: GitHub-Copilot:claude-opus-4.7 copilot-review
---

Changes in v10:
- Fix comment: dst -> dest

Changes in v9:
- Rebased on top of UDP support series

Changes in v3:
- New patch

 tools/testing/selftests/landlock/net_test.c | 138 ++++++++++++++++++--
 1 file changed, 128 insertions(+), 10 deletions(-)

diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 05b41e4da28f..d35610401954 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -2742,12 +2742,22 @@ TEST_F(port_specific, bind_connect_1023)
 	EXPECT_EQ(0, close(bind_fd));
 }
 
+/**
+ * matches_auditlog - Check audit log for a network access denial
+ *
+ * @audit_fd:   Audit file descriptor.
+ * @blockers:   A regex-escaped blocker string, e.g., "net\.bind_tcp".
+ * @dir_addr:   Either "saddr" or "daddr", ignored if addr is NULL.
+ * @addr:       A regex-escaped IP address string, or NULL.
+ * @dir_port:   Either "src" or "dest", ignored if addr is NULL.
+ * @port:       A port number, ignored if addr is NULL.
+ */
 static int matches_auditlog(const int audit_fd, const char *const blockers,
 			    const char *const dir_addr, const char *const addr,
-			    const char *const dir_port)
+			    const char *const dir_port, const __u16 port)
 {
 	static const char log_with_addrport_tmpl[] = REGEX_LANDLOCK_PREFIX
-		" blockers=%s %s=%s %s=1024$";
+		" blockers=%s %s=%s %s=%u$";
 	static const char log_without_addrport_tmpl[] = REGEX_LANDLOCK_PREFIX
 		" blockers=%s";
 	/*
@@ -2755,8 +2765,9 @@ static int matches_auditlog(const int audit_fd, const char *const blockers,
 	 * Max strlen(dir_addr): 5
 	 * Max strlen(addr): 12
 	 * Max strlen(dir_port): 4
+	 * Max strlen(%u port): 5
 	 */
-	char log_match[sizeof(log_with_addrport_tmpl) + 37];
+	char log_match[sizeof(log_with_addrport_tmpl) + 42];
 	int log_match_len;
 
 	if (addr == NULL)
@@ -2765,7 +2776,7 @@ static int matches_auditlog(const int audit_fd, const char *const blockers,
 	else
 		log_match_len = snprintf(log_match, sizeof(log_match),
 					 log_with_addrport_tmpl, blockers,
-					 dir_addr, addr, dir_port);
+					 dir_addr, addr, dir_port, port);
 	if (log_match_len > sizeof(log_match))
 		return -E2BIG;
 
@@ -2777,6 +2788,8 @@ FIXTURE(audit)
 {
 	struct service_fixture srv0;
 	struct service_fixture srv1;
+	/* srv2 has a rule with no access but quiet bit set. */
+	struct service_fixture srv2;
 	struct service_fixture unspec_srv0;
 	struct audit_filter audit_filter;
 	int audit_fd;
@@ -2836,6 +2849,7 @@ FIXTURE_SETUP(audit)
 
 	ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
 	ASSERT_EQ(0, set_service(&self->srv1, variant->prot, 1));
+	ASSERT_EQ(0, set_service(&self->srv2, variant->prot, 2));
 	ASSERT_EQ(0, set_service(&self->unspec_srv0, prot_unspec, 0));
 
 	setup_loopback(_metadata);
@@ -2866,6 +2880,11 @@ TEST_F(audit, bind)
 				 LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_net = access_rights,
+		.quiet_access_net = access_rights,
+	};
+	const struct landlock_net_port_attr quiet_rule = {
+		.allowed_access = 0,
+		.port = self->srv2.port,
 	};
 	struct audit_records records;
 	int ruleset_fd, sock_fd;
@@ -2873,6 +2892,8 @@ TEST_F(audit, bind)
 	ruleset_fd =
 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
 	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+				       &quiet_rule, LANDLOCK_ADD_RULE_QUIET));
 	enforce_ruleset(_metadata, ruleset_fd);
 	EXPECT_EQ(0, close(ruleset_fd));
 
@@ -2880,13 +2901,24 @@ TEST_F(audit, bind)
 	ASSERT_LE(0, sock_fd);
 	EXPECT_EQ(-EACCES, bind_variant(sock_fd, &self->srv0));
 	EXPECT_EQ(0, matches_auditlog(self->audit_fd, audit_evt, "saddr",
-				      variant->addr, "src"));
+				      variant->addr, "src", self->srv0.port));
 
 	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
 	EXPECT_EQ(0, records.access);
 	EXPECT_EQ(1, records.domain);
 
 	EXPECT_EQ(0, close(sock_fd));
+
+	/* Bind to srv2 (with quiet rule): no new audit logs. */
+	sock_fd = socket_variant(&self->srv2);
+	ASSERT_LE(0, sock_fd);
+	EXPECT_EQ(-EACCES, bind_variant(sock_fd, &self->srv2));
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+
+	EXPECT_EQ(0, close(sock_fd));
 }
 
 TEST_F(audit, connect)
@@ -2903,11 +2935,16 @@ TEST_F(audit, connect)
 	const __u64 access_rights = bind_right | conn_right;
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_net = access_rights,
+		.quiet_access_net = access_rights,
 	};
 	const struct landlock_net_port_attr rule_connect_p1 = {
 		.allowed_access = conn_right,
 		.port = self->srv1.port,
 	};
+	const struct landlock_net_port_attr quiet_rule = {
+		.allowed_access = 0,
+		.port = self->srv2.port,
+	};
 	struct audit_records records;
 	int ruleset_fd, sock_fd;
 
@@ -2916,6 +2953,8 @@ TEST_F(audit, connect)
 	ASSERT_LE(0, ruleset_fd);
 	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 				       &rule_connect_p1, 0));
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+				       &quiet_rule, LANDLOCK_ADD_RULE_QUIET));
 	enforce_ruleset(_metadata, ruleset_fd);
 	EXPECT_EQ(0, close(ruleset_fd));
 
@@ -2923,7 +2962,7 @@ TEST_F(audit, connect)
 	ASSERT_LE(0, sock_fd);
 	EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv0));
 	EXPECT_EQ(0, matches_auditlog(self->audit_fd, audit_evt, "daddr",
-				      variant->addr, "dest"));
+				      variant->addr, "dest", self->srv0.port));
 
 	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
 	EXPECT_EQ(0, records.access);
@@ -2934,13 +2973,91 @@ TEST_F(audit, connect)
 		EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv1));
 
 		EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.bind_udp",
-					      NULL, NULL, NULL));
+					      NULL, NULL, NULL, 0));
 		EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
 		EXPECT_EQ(0, records.access);
 		EXPECT_EQ(0, records.domain);
 	}
 
 	EXPECT_EQ(0, close(sock_fd));
+
+	/* Connect to srv2 (with quiet rule): no new audit logs. */
+	sock_fd = socket_variant(&self->srv2);
+	ASSERT_LE(0, sock_fd);
+	EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv2));
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+
+	EXPECT_EQ(0, close(sock_fd));
+}
+
+/* Quieting bind access has no effect on connect. */
+TEST_F(audit, connect_quiet_bind)
+{
+	const char *audit_evt = (variant->prot.type == SOCK_STREAM ?
+					 "net\\.connect_tcp" :
+					 "net\\.connect_send_udp");
+	const int bind_right = (variant->prot.type == SOCK_STREAM ?
+					LANDLOCK_ACCESS_NET_BIND_TCP :
+					LANDLOCK_ACCESS_NET_BIND_UDP);
+	const int conn_right = (variant->prot.type == SOCK_STREAM ?
+					LANDLOCK_ACCESS_NET_CONNECT_TCP :
+					LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
+	const int access_rights = bind_right | conn_right;
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = access_rights,
+		.quiet_access_net = bind_right,
+	};
+	const struct landlock_ruleset_attr ruleset_attr_2 = {
+		.handled_access_net = access_rights,
+		.quiet_access_net = conn_right,
+	};
+	const struct landlock_net_port_attr quiet_rule = {
+		.allowed_access = 0,
+		.port = self->srv2.port,
+	};
+	struct audit_records records;
+	int ruleset_fd, sock_fd;
+
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+				       &quiet_rule, LANDLOCK_ADD_RULE_QUIET));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	sock_fd = socket_variant(&self->srv2);
+	ASSERT_LE(0, sock_fd);
+	EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv2));
+	EXPECT_EQ(0, matches_auditlog(self->audit_fd, audit_evt, "daddr",
+				      variant->addr, "dest", self->srv2.port));
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+
+	EXPECT_EQ(0, close(sock_fd));
+
+	/* New layer that also denies connect but has the correct quiet bit. */
+	ruleset_fd = landlock_create_ruleset(&ruleset_attr_2,
+					     sizeof(ruleset_attr_2), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+				       &quiet_rule, LANDLOCK_ADD_RULE_QUIET));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	sock_fd = socket_variant(&self->srv2);
+	ASSERT_LE(0, sock_fd);
+	EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv2));
+
+	/* Quieted - no logs expected. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+
+	EXPECT_EQ(0, close(sock_fd));
 }
 
 TEST_F(audit, sendmsg)
@@ -2973,7 +3090,8 @@ TEST_F(audit, sendmsg)
 	ASSERT_LE(0, sock_fd);
 	EXPECT_EQ(-EACCES, sendto_variant(sock_fd, &self->srv0, "A", 1, 0));
 	EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.connect_send_udp",
-				      "daddr", variant->addr, "dest"));
+				      "daddr", variant->addr, "dest",
+				      self->srv0.port));
 
 	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
 	EXPECT_EQ(0, records.access);
@@ -2982,7 +3100,7 @@ TEST_F(audit, sendmsg)
 	/* Check that autobind generates a denied bind event. */
 	EXPECT_EQ(-EACCES, sendto_variant(sock_fd, &self->srv1, "A", 1, 0));
 	EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.bind_udp", NULL,
-				      NULL, NULL));
+				      NULL, NULL, 0));
 	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
 	EXPECT_EQ(0, records.access);
 	EXPECT_EQ(0, records.domain);
@@ -2990,7 +3108,7 @@ TEST_F(audit, sendmsg)
 	EXPECT_EQ(-EACCES,
 		  sendto_variant(sock_fd, &self->unspec_srv0, "B", 1, 0));
 	EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.connect_send_udp",
-				      "daddr", NULL, "dest"));
+				      "daddr", NULL, "dest", 0));
 	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
 	EXPECT_EQ(0, records.access);
 	EXPECT_EQ(0, records.domain);
-- 
2.54.0

^ permalink raw reply related

* [PATCH v11 6/9] selftests/landlock: add tests for quiet flag with fs rules
From: Tingmao Wang @ 2026-06-12  1:48 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
	Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1781228815.git.m@maowtm.org>

Test various interactions of the quiet flag with filesystem rules:
- Non-optional access (tested with open and rename).
- Optional access (tested with truncate and ioctl).
- Behaviour around mounts matches with normal Landlock rules.
- Behaviour around disconnected directories matches with normal Landlock
  rules (test expected behaviour of 9a868cdbe66a ("landlock: Fix handling of
  disconnected directories") applied to the collected quiet flag).
- Multiple layers works as expected.

Assisted-by: GitHub-Copilot:claude-opus-4.6 copilot-review
Signed-off-by: Tingmao Wang <m@maowtm.org>
---

Changes in v11:
- Remove broken if brackets change

Changes in v10:
- Fix grammar in some comments
- if brackets

Changes in v8:
- Rebase, resolve conflict, then clang-format
- Remove previously added comment about domain allocation record leakage -
  this is now documented properly by 239fd9a6f948 ("selftests/landlock:
  Drain stale audit records on init")
- Fix missing EXPECT_EQ on audit_count_records() return value

Changes in v6:
- Change quiet bool argument of add_path_beneath into a __u32 flags
  (suggested by Justin Suess)
- Rename quiet_behind_mountpoint_ignored_disconnected to
  quiet_behind_mountpoint_disconnected and fix test due to disconnected
  directory handling changes

Changes in v5:
- Add quiet_two_layers_different_handled_{1,2,3} variants.

Changes in v3:
- New patch

 tools/testing/selftests/landlock/fs_test.c | 2448 +++++++++++++++++++-
 1 file changed, 2439 insertions(+), 9 deletions(-)

diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 10d9355ade5f..702f0dba462e 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -720,7 +720,7 @@ TEST_F_FORK(layout1, rule_with_unhandled_access)
 
 static void add_path_beneath(struct __test_metadata *const _metadata,
 			     const int ruleset_fd, const __u64 allowed_access,
-			     const char *const path)
+			     const char *const path, __u32 flags)
 {
 	struct landlock_path_beneath_attr path_beneath = {
 		.allowed_access = allowed_access,
@@ -733,7 +733,7 @@ static void add_path_beneath(struct __test_metadata *const _metadata,
 		       strerror(errno));
 	}
 	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
-				       &path_beneath, 0))
+				       &path_beneath, flags))
 	{
 		TH_LOG("Failed to update the ruleset with \"%s\": %s", path,
 		       strerror(errno));
@@ -780,7 +780,7 @@ static int create_ruleset(struct __test_metadata *const _metadata,
 				continue;
 
 			add_path_beneath(_metadata, ruleset_fd, rules[i].access,
-					 rules[i].path);
+					 rules[i].path, 0);
 		}
 	return ruleset_fd;
 }
@@ -1310,7 +1310,7 @@ TEST_F_FORK(layout1, inherit_subset)
 	 * ANDed with the previous ones.
 	 */
 	add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
-			 dir_s1d2);
+			 dir_s1d2, 0);
 	/*
 	 * According to ruleset_fd, dir_s1d2 should now have the
 	 * LANDLOCK_ACCESS_FS_READ_FILE and LANDLOCK_ACCESS_FS_WRITE_FILE
@@ -1342,7 +1342,7 @@ TEST_F_FORK(layout1, inherit_subset)
 	 * Try to get more privileges by adding new access rights to the parent
 	 * directory: dir_s1d1.
 	 */
-	add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1);
+	add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1, 0);
 	enforce_ruleset(_metadata, ruleset_fd);
 
 	/* Same tests and results as above. */
@@ -1365,7 +1365,7 @@ TEST_F_FORK(layout1, inherit_subset)
 	 * that there was no rule tied to it before.
 	 */
 	add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
-			 dir_s1d3);
+			 dir_s1d3, 0);
 	enforce_ruleset(_metadata, ruleset_fd);
 	ASSERT_EQ(0, close(ruleset_fd));
 
@@ -1417,7 +1417,7 @@ TEST_F_FORK(layout1, inherit_superset)
 	add_path_beneath(_metadata, ruleset_fd,
 			 LANDLOCK_ACCESS_FS_READ_FILE |
 				 LANDLOCK_ACCESS_FS_READ_DIR,
-			 dir_s1d2);
+			 dir_s1d2, 0);
 	enforce_ruleset(_metadata, ruleset_fd);
 	EXPECT_EQ(0, close(ruleset_fd));
 
@@ -3970,7 +3970,7 @@ static int ioctl_error(struct __test_metadata *const _metadata, int fd,
 		       unsigned int cmd)
 {
 	char buf[128]; /* sufficiently large */
-	int res, stdinbak_fd;
+	int res, stdinbak_fd, err;
 
 	/*
 	 * Depending on the IOCTL command, parts of the zeroed-out buffer might
@@ -3985,13 +3985,14 @@ static int ioctl_error(struct __test_metadata *const _metadata, int fd,
 	/* Invokes the IOCTL with a zeroed-out buffer. */
 	bzero(&buf, sizeof(buf));
 	res = ioctl(fd, cmd, &buf);
+	err = errno;
 
 	/* Restores the old FD 0 and closes the backup FD. */
 	ASSERT_EQ(0, dup2(stdinbak_fd, 0));
 	ASSERT_EQ(0, close(stdinbak_fd));
 
 	if (res < 0)
-		return errno;
+		return err;
 
 	return 0;
 }
@@ -4789,6 +4790,7 @@ FIXTURE(layout1_bind) {};
 
 static const char bind_dir_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3";
 static const char bind_file1_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f1";
+static const char bind_file2_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f2";
 
 /* Move targets for disconnected path tests. */
 static const char dir_s4d1[] = TMP_DIR "/s4d1";
@@ -7764,4 +7766,2432 @@ TEST_F(audit_layout1, mount)
 	EXPECT_EQ(1, records.domain);
 }
 
+static bool debug_quiet_tests;
+
+FIXTURE(audit_quiet_layout1)
+{
+	struct audit_filter audit_filter;
+	int audit_fd;
+};
+
+FIXTURE_SETUP(audit_quiet_layout1)
+{
+	prepare_layout(_metadata);
+	create_layout1(_metadata);
+
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
+	EXPECT_LE(0, self->audit_fd);
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+
+	if (getenv("DEBUG_QUIET_TESTS"))
+		debug_quiet_tests = true;
+}
+
+FIXTURE_TEARDOWN_PARENT(audit_quiet_layout1)
+{
+	remove_layout1(_metadata);
+	cleanup_layout(_metadata);
+
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	EXPECT_EQ(0, audit_cleanup(-1, NULL));
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+struct a_rule {
+	const char *path;
+	__u64 access;
+	bool quiet;
+};
+
+struct a_layer {
+	__u64 handled_access_fs;
+	__u64 quiet_access_fs;
+	struct a_rule rules[6];
+	__u64 restrict_flags;
+};
+
+struct a_target {
+	/* File/dir to try open. */
+	const char *target;
+	/* Open mode (one of O_RDONLY, O_WRONLY, or O_RDWR). */
+	int open_mode;
+	/* Should open succeed? */
+	bool expect_open_success;
+	/* If open fails, whether to expect an audit log for read. */
+	bool audit_read_blocked;
+	/* If open fails, whether to expect an audit log for write. */
+	bool audit_write_blocked;
+	/* If ftruncate() is expected to be allowed. */
+	bool expect_truncate_success;
+	/* If ftruncate fails, whether to expect an audit log. */
+	bool audit_truncate;
+	/*
+	 * If ioctl() is expected to be allowed (ioctl not attempted if
+	 * neither this nor expect_ioctl_denied is set).
+	 */
+	bool expect_ioctl_allowed;
+	/* If ioctl() is expected to be denied. */
+	bool expect_ioctl_denied;
+	/* If ioctl fails, whether to expect an audit log. */
+	bool audit_ioctl;
+};
+
+#define AUDIT_QUIET_MAX_TARGETS 10
+
+FIXTURE_VARIANT(audit_quiet_layout1)
+{
+	struct a_layer layers[3];
+	struct a_target targets[AUDIT_QUIET_MAX_TARGETS];
+};
+
+#define FS_R LANDLOCK_ACCESS_FS_READ_FILE
+#define FS_W LANDLOCK_ACCESS_FS_WRITE_FILE
+#define FS_TRUNC LANDLOCK_ACCESS_FS_TRUNCATE
+#define FS_IOCTL LANDLOCK_ACCESS_FS_IOCTL_DEV
+
+static int sprint_access_bits(char *buf, size_t buflen, __u64 access)
+{
+	size_t offset = 0;
+
+	if (buflen < strlen("rwti make_reg remove_file refer") + 1)
+		abort();
+
+	buf[0] = '\0';
+	if (access & FS_R)
+		offset += snprintf(buf + offset, buflen - offset, "r");
+	if (access & FS_W)
+		offset += snprintf(buf + offset, buflen - offset, "w");
+	if (access & FS_TRUNC)
+		offset += snprintf(buf + offset, buflen - offset, "t");
+	if (access & FS_IOCTL)
+		offset += snprintf(buf + offset, buflen - offset, "i");
+	if (access & LANDLOCK_ACCESS_FS_MAKE_REG)
+		offset += snprintf(buf + offset, buflen - offset, ",make_reg");
+	if (access & LANDLOCK_ACCESS_FS_REMOVE_FILE)
+		offset +=
+			snprintf(buf + offset, buflen - offset, ",remove_file");
+	if (access & LANDLOCK_ACCESS_FS_REFER)
+		offset += snprintf(buf + offset, buflen - offset, ",refer");
+
+	if (buf[0] == ',') {
+		offset--;
+		memmove(buf, buf + 1, offset);
+		buf[offset] = '\0';
+	}
+
+	return offset;
+}
+
+static int apply_a_layer(struct __test_metadata *const _metadata,
+			 const struct a_layer *l)
+{
+	struct landlock_ruleset_attr rs_attr = {
+		.handled_access_fs = l->handled_access_fs,
+		.quiet_access_fs = l->quiet_access_fs,
+	};
+	int rs_fd;
+	int i;
+	const struct a_rule *r;
+	char handled_access_s[33], quiet_access_s[33], rule_access_s[33];
+
+	if (!l->handled_access_fs)
+		return 0;
+
+	rs_fd = landlock_create_ruleset(&rs_attr, sizeof(rs_attr), 0);
+	ASSERT_LE(0, rs_fd);
+
+	for (i = 0; i < ARRAY_SIZE(l->rules); i++) {
+		r = &l->rules[i];
+		if (!r->path)
+			continue;
+
+		add_path_beneath(_metadata, rs_fd, r->access, r->path,
+				 r->quiet ? LANDLOCK_ADD_RULE_QUIET : 0);
+	}
+
+	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+	ASSERT_EQ(0, landlock_restrict_self(rs_fd, l->restrict_flags))
+	{
+		TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
+	}
+	ASSERT_EQ(0, close(rs_fd));
+
+	if (debug_quiet_tests) {
+		sprint_access_bits(handled_access_s, sizeof(handled_access_s),
+				   l->handled_access_fs);
+		sprint_access_bits(quiet_access_s, sizeof(quiet_access_s),
+				   l->quiet_access_fs);
+		TH_LOG("applied layer: handled=%s quiet=%s restrict_flags=0x%llx",
+		       handled_access_s, quiet_access_s,
+		       (unsigned long long)l->restrict_flags);
+		for (i = 0; i < ARRAY_SIZE(l->rules); i++) {
+			r = &l->rules[i];
+			if (!r->path)
+				continue;
+
+			sprint_access_bits(rule_access_s, sizeof(rule_access_s),
+					   r->access);
+			TH_LOG("  rule[%d]: path=%s access=%s quiet=%d", i,
+			       r->path, rule_access_s, r->quiet);
+		}
+	}
+	return 0;
+}
+
+void audit_quiet_layout1_test_body(struct __test_metadata *const _metadata,
+				   FIXTURE_DATA(audit_quiet_layout1) * self,
+				   const struct a_target *targets)
+{
+	struct audit_records records = {};
+	int i;
+	const struct a_target *target;
+	int fd = -1;
+	int open_mode;
+	int ret;
+	bool expect_audit;
+	const char *blocker;
+
+	for (i = 0; i < AUDIT_QUIET_MAX_TARGETS; i++) {
+		target = &targets[i];
+		if (!target->target)
+			continue;
+
+		open_mode = target->open_mode & (O_RDONLY | O_WRONLY | O_RDWR);
+
+		EXPECT_TRUE(open_mode == O_RDONLY || open_mode == O_WRONLY ||
+			    open_mode == O_RDWR);
+
+		if (target->expect_open_success) {
+			EXPECT_FALSE(target->audit_read_blocked);
+			EXPECT_FALSE(target->audit_write_blocked);
+		}
+		if (target->expect_truncate_success)
+			EXPECT_TRUE(target->expect_open_success &&
+				    !target->audit_truncate);
+
+		if (debug_quiet_tests)
+			TH_LOG("Try open \"%s\" with %s%s", target->target,
+			       open_mode != O_WRONLY ? "r" : "",
+			       open_mode != O_RDONLY ? "w" : "");
+
+		fd = openat(AT_FDCWD, target->target, open_mode | O_CLOEXEC);
+		if (target->expect_open_success) {
+			ASSERT_LE(0, fd)
+			{
+				TH_LOG("Failed to open \"%s\": %s",
+				       target->target, strerror(errno));
+			};
+		} else {
+			ASSERT_EQ(-1, fd);
+			ASSERT_EQ(EACCES, errno);
+		}
+
+		expect_audit = true;
+
+		if (target->audit_read_blocked && target->audit_write_blocked)
+			blocker = "fs\\.write_file,fs\\.read_file";
+		else if (target->audit_read_blocked)
+			blocker = "fs\\.read_file";
+		else if (target->audit_write_blocked)
+			blocker = "fs\\.write_file";
+		else
+			expect_audit = false;
+
+		if (expect_audit)
+			ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+						    blocker, target->target));
+
+		/* Check that we see no (other) logs. */
+		EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+		ASSERT_EQ(0, records.access);
+
+		if (target->expect_open_success && fd >= 0) {
+			if (debug_quiet_tests)
+				TH_LOG("Try ftruncate \"%s\"", target->target);
+
+			ret = ftruncate(fd, 0);
+			if (target->expect_truncate_success) {
+				ASSERT_EQ(0, ret);
+			} else {
+				ASSERT_EQ(-1, ret);
+				if (open_mode != O_RDONLY)
+					ASSERT_EQ(EACCES, errno);
+			}
+
+			if (target->audit_truncate)
+				ASSERT_EQ(0, matches_log_fs(_metadata,
+							    self->audit_fd,
+							    "fs\\.truncate",
+							    target->target));
+
+			if (target->expect_ioctl_allowed ||
+			    target->expect_ioctl_denied) {
+				if (debug_quiet_tests)
+					TH_LOG("Try ioctl FIONREAD on \"%s\"",
+					       target->target);
+
+				ret = ioctl_error(_metadata, fd, FIONREAD);
+				if (target->expect_ioctl_allowed) {
+					ASSERT_NE(EACCES, ret);
+				} else {
+					ASSERT_EQ(EACCES, ret);
+				}
+			}
+
+			if (target->audit_ioctl)
+				ASSERT_EQ(0, matches_log_fs_extra(
+						     _metadata, self->audit_fd,
+						     "fs\\.ioctl_dev",
+						     target->target,
+						     " ioctlcmd=0x541b\\+"));
+
+			/* Check that we see no other logs. */
+			EXPECT_EQ(0, audit_count_records(self->audit_fd,
+							 &records));
+			ASSERT_EQ(0, records.access);
+			ASSERT_EQ(0, close(fd));
+		}
+	}
+}
+
+TEST_F(audit_quiet_layout1, base)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(variant->layers); i++)
+		ASSERT_EQ(0, apply_a_layer(_metadata, &variant->layers[i]));
+
+	audit_quiet_layout1_test_body(_metadata, self, variant->targets);
+}
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_simple) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC,
+			.quiet_access_fs = FS_R,
+			.rules = {
+				{ .path = dir_s1d1, .access = 0, .quiet = true },
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+		},
+		/* Not covered by quiet */
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+		/* Access not quieted */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_WRONLY,
+			.audit_write_blocked = true,
+		},
+		/*
+		 * Quiet flag only takes effect if all blocked access bits are
+		 * quieted, otherwise audit log emitted as normal (with all blockers)
+		 */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+			.audit_read_blocked = true,
+			.audit_write_blocked = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_allow_read) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC,
+			.quiet_access_fs = FS_W,
+			.rules = {
+				{ .path = dir_s1d1, .access = FS_R, .quiet = true },
+				/* Quiet flags inherit down and are not overridden */
+				{ .path = file1_s1d1, .access = FS_R, .quiet = false },
+				{ .path = file1_s2d3, .access = 0, .quiet = true },
+			},
+		},
+	},
+	.targets = {
+		/* Read ok */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+		},
+		/* Write quieted */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_WRONLY,
+		},
+		/* Read allowed, write quieted so no audit */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+		},
+		/* Not covered by quiet */
+		{
+			.target = file1_s2d2,
+			.open_mode = O_WRONLY,
+			.audit_write_blocked = true,
+		},
+		{
+			.target = file1_s2d2,
+			.open_mode = O_RDWR,
+			.audit_read_blocked = true,
+			.audit_write_blocked = true,
+		},
+		/* Single file quiet */
+		{
+			.target = file1_s2d3,
+			.open_mode = O_WRONLY,
+		},
+		/* Wrong file */
+		{
+			.target = file2_s2d3,
+			.open_mode = O_WRONLY,
+			.audit_write_blocked = true,
+		},
+		/* Access not quieted */
+		{
+			.target = file1_s2d3,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+		/* Some access not quieted */
+		{
+			.target = file1_s2d3,
+			.open_mode = O_RDWR,
+			.audit_read_blocked = true,
+			.audit_write_blocked = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_allow_write) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC,
+			.quiet_access_fs = FS_R,
+			.rules = {
+				{ .path = dir_s1d1, .access = FS_W, .quiet = true },
+			},
+		},
+	},
+	.targets = {
+		/* Read quieted */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+		},
+		/* Truncate not quieted */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+			.audit_truncate = true,
+		},
+		/* Not covered by quiet */
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+		/* Write allowed, read quieted so no audit */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, allow_write_quiet_trunc) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC,
+			.quiet_access_fs = FS_TRUNC,
+			.rules = {
+				{ .path = dir_s1d1, .access = FS_W, .quiet = true },
+				{ .path = dir_s2d1, .access = FS_W, .quiet = false },
+			},
+		},
+	},
+	.targets = {
+		/* Read not allowed and not quieted */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+		/* Truncate quieted */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+		},
+		/* Not covered by quiet (truncate) */
+		{
+			.target = file1_s2d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+			.audit_truncate = true,
+		},
+		/* Not covered by quiet (read/write) */
+		{
+			.target = file1_s3d1,
+			.open_mode = O_RDWR,
+			.audit_read_blocked = true,
+			.audit_write_blocked = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, allow_rw_quiet_trunc) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC,
+			.quiet_access_fs = FS_TRUNC,
+			.rules = {
+				{ .path = dir_s1d1, .access = FS_R | FS_W, .quiet = true },
+				{ .path = dir_s2d1, .access = FS_R | FS_W, .quiet = false },
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+			.audit_truncate = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_all) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.rules = {
+				{ .path = dir_s1d1, .access = 0, .quiet = true },
+				{ .path = file1_s2d1, .access = FS_R | FS_W, .quiet = true },
+				{ .path = file1_s2d3, .access = 0, .quiet = true },
+				{ .path = dir_s3d1, .access = FS_W, .quiet = false },
+				{ .path = "/dev/zero", .access = FS_R, .quiet = false },
+				{ .path = "/dev/null", .access = FS_R, .quiet = true },
+			},
+		},
+	},
+	.targets = {
+		/* No logs */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+		},
+		{
+			.target = file1_s1d1,
+			.open_mode = O_WRONLY,
+		},
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+		},
+		/* Truncate quieted - no log */
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+		},
+		/* Truncate not covered by quiet */
+		{
+			.target = file1_s3d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+			.audit_truncate = true,
+		},
+		/* Not covered by quiet */
+		{
+			.target = file1_s3d1,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+		/* Single file quiet */
+		{
+			.target = file1_s2d3,
+			.open_mode = O_RDWR,
+		},
+		/* Wrong file */
+		{
+			.target = file2_s2d3,
+			.open_mode = O_RDWR,
+			.audit_read_blocked = true,
+			.audit_write_blocked = true,
+		},
+		/* Ioctl quieted */
+		{
+			.target = "/dev/null",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+		},
+		/* Ioctl not quieted */
+		{
+			.target = "/dev/zero",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+			.audit_ioctl = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_across_mountpoint) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC,
+			.quiet_access_fs = FS_R,
+			.rules = {
+				{ .path = dir_s3d1, .access = 0, .quiet = true },
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s3d3,
+			.open_mode = O_RDONLY,
+		},
+		/* Not covered by quiet */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+			.audit_read_blocked = true,
+			.audit_write_blocked = true,
+		},
+		/* Access not quieted */
+		{
+			.target = file1_s3d3,
+			.open_mode = O_WRONLY,
+			.audit_write_blocked = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, allow_all_quiet) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.rules = {
+				{
+					.path = dir_s1d1,
+					.access = FS_R | FS_W | FS_TRUNC,
+					.quiet = true
+				},
+				{
+					.path = "/dev/null",
+					.access = FS_R | FS_W | FS_IOCTL,
+					.quiet = true
+				},
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+			.expect_truncate_success = true,
+		},
+		{
+			.target = "/dev/null",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_allowed = true,
+		},
+	},
+};
+
+/*
+ * With LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF, it doesn't matter what
+ * the quiet flags below the layer say.
+ */
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, subdomains_off) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R,
+			.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
+			.rules = {
+				{ .path = "/", .access = FS_R, .quiet = false },
+			}
+		},
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R,
+			.rules = {
+				{ .path = dir_s1d1, .access = 0, .quiet = true },
+				{ .path = file1_s2d2, .access = FS_R | FS_W, .quiet = true },
+				{ .path = file1_s2d3, .access = FS_R | FS_W, .quiet = false },
+				{ .path = "/dev/null", .access = FS_R | FS_W, .quiet = true },
+				{ .path = "/dev/zero", .access = FS_R | FS_W, .quiet = false },
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDWR,
+		},
+		{
+			.target = file1_s2d2,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+			/* No audit_truncate */
+		},
+		{
+			.target = file1_s2d3,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+			/* No audit_truncate */
+		},
+		{
+			.target = "/dev/null",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+			/* No audit_ioctl */
+		},
+		{
+			.target = "/dev/zero",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+			/* No audit_ioctl */
+		},
+	},
+};
+
+/*
+ * With LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF, it doesn't matter what
+ * the quiet flags on the layer say.
+ */
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, same_exec_off) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R,
+			.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF,
+			.rules = {
+				{ .path = dir_s1d1, .access = 0, .quiet = true },
+				{ .path = file1_s2d2, .access = FS_R | FS_W, .quiet = true },
+				{ .path = file1_s2d3, .access = FS_R | FS_W, .quiet = false },
+				{ .path = "/dev/null", .access = FS_R | FS_W, .quiet = true },
+				{ .path = "/dev/zero", .access = FS_R | FS_W, .quiet = false },
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDWR,
+		},
+		{
+			.target = file1_s2d2,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+			/* No audit_truncate */
+		},
+		{
+			.target = file1_s2d3,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+			/* No audit_truncate */
+		},
+		{
+			.target = "/dev/null",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+			/* No audit_ioctl */
+		},
+		{
+			.target = "/dev/zero",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+			/* No audit_ioctl */
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_1) {
+	/* Here, rules that deny access are always quiet. */
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.rules = {
+				{
+					.path = dir_s1d1,
+					.access = FS_W,
+					.quiet = true,
+				},
+				{
+					.path = dir_s2d1,
+					.access = FS_R | FS_W | FS_TRUNC,
+					.quiet = false,
+				},
+				{
+					.path = "/dev/null",
+					.access = FS_R,
+					.quiet = true,
+				},
+				{
+					.path = "/dev/zero",
+					.access = FS_R | FS_W | FS_IOCTL,
+					.quiet = false,
+				},
+			},
+		},
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.rules = {
+				{
+					.path = dir_s1d1,
+					.access = FS_R | FS_W | FS_TRUNC,
+					.quiet = false,
+				},
+				{
+					.path = dir_s2d1,
+					.access = FS_W,
+					.quiet = true,
+				},
+				{
+					.path = "/dev/null",
+					.access = FS_R | FS_W | FS_IOCTL,
+					.quiet = false,
+				},
+				{
+					.path = "/dev/zero",
+					.access = FS_R,
+					.quiet = true,
+				},
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+		},
+		{
+			.target = file1_s1d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDONLY,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+		},
+		{
+			.target = "/dev/null",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+		},
+		{
+			.target = "/dev/zero",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_2) {
+	/* Here, rules that deny access are never quiet. */
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.rules = {
+				{
+					.path = dir_s1d1,
+					.access = FS_W,
+					.quiet = false
+				},
+				{
+					.path = dir_s2d1,
+					.access = FS_R | FS_W | FS_TRUNC,
+					.quiet = true
+				},
+				{
+					.path = "/dev/null",
+					.access = FS_R,
+					.quiet = false
+				},
+				{
+					.path = "/dev/zero",
+					.access = FS_R | FS_W | FS_IOCTL,
+					.quiet = true
+				},
+			},
+		},
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.rules = {
+				{
+					.path = dir_s1d1,
+					.access = FS_R | FS_W | FS_TRUNC,
+					.quiet = true
+				},
+				{
+					.path = dir_s2d1,
+					.access = FS_W,
+					.quiet = false
+				},
+				{
+					.path = "/dev/null",
+					.access = FS_R | FS_W | FS_IOCTL,
+					.quiet = true
+				},
+				{
+					.path = "/dev/zero",
+					.access = FS_R,
+					.quiet = false
+				},
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+		{
+			.target = file1_s1d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+			.audit_truncate	= true,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+			.audit_truncate	= true,
+		},
+		{
+			.target = "/dev/null",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+			.audit_ioctl = true,
+		},
+		{
+			.target = "/dev/zero",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+			.audit_ioctl = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_3) {
+	/* This time only the second layer quiets things. */
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.rules = {
+				{
+					.path = dir_s1d1,
+					.access = FS_W,
+					.quiet = false,
+				},
+				{
+					.path = dir_s2d1,
+					.access = FS_R | FS_W | FS_TRUNC,
+					.quiet = false,
+				},
+				{
+					.path = "/dev/null",
+					.access = FS_R,
+					.quiet = false,
+				},
+				{
+					.path = "/dev/zero",
+					.access = FS_R | FS_W | FS_IOCTL,
+					.quiet = false,
+				},
+			},
+		},
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.rules = {
+				{
+					.path = dir_s1d1,
+					.access = FS_R | FS_W | FS_TRUNC,
+					.quiet = false,
+				},
+				{
+					.path = dir_s2d1,
+					.access = FS_W,
+					.quiet = true,
+				},
+				{
+					.path = "/dev/null",
+					.access = FS_R | FS_W | FS_IOCTL,
+					.quiet = false,
+				},
+				{
+					.path = "/dev/zero",
+					.access = FS_R,
+					.quiet = true,
+				},
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+		{
+			.target = file1_s1d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+			.audit_truncate = true,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDONLY,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+		},
+		{
+			.target = "/dev/null",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+			.audit_ioctl = true,
+		},
+		{
+			.target = "/dev/zero",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_different_quiet_access) {
+	/* Here, rules that deny access are always quiet. */
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.rules = {
+				{
+					.path = dir_s1d1,
+					.access = FS_W,
+					.quiet = true,
+				},
+				{
+					.path = dir_s2d1,
+					.access = FS_R | FS_W | FS_TRUNC,
+					.quiet = false,
+				},
+				{
+					.path = "/dev/null",
+					.access = FS_R,
+					.quiet = true,
+				},
+				{
+					.path = "/dev/zero",
+					.access = FS_R | FS_W | FS_IOCTL,
+					.quiet = false,
+				},
+			},
+		},
+		{
+			.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+			.quiet_access_fs = FS_IOCTL,
+			.rules = {
+				{
+					.path = dir_s1d1,
+					.access = FS_R | FS_W | FS_TRUNC,
+					.quiet = false,
+				},
+				{
+					.path = dir_s2d1,
+					.access = FS_W,
+					.quiet = true,
+				},
+				{
+					.path = "/dev/null",
+					.access = FS_R | FS_W | FS_IOCTL,
+					.quiet = false,
+				},
+				{
+					.path = "/dev/zero",
+					.access = FS_R,
+					.quiet = true,
+				},
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+		},
+		{
+			.target = file1_s1d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+		{
+			.target = file1_s2d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+			.audit_truncate	= true,
+		},
+		{
+			.target = "/dev/null",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+		},
+		{
+			.target = "/dev/zero",
+			.open_mode = O_RDONLY,
+			.expect_open_success = true,
+			.expect_ioctl_denied = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_different_handled_1) {
+	/* Quiet from layer 1 */
+	.layers = {
+		{
+			.handled_access_fs = FS_R,
+			.quiet_access_fs = FS_R,
+			.rules = {
+				{
+					.path = file1_s1d1,
+					.access = FS_R,
+					.quiet = true,
+				},
+				{
+					.path = file2_s1d1,
+					.access = 0,
+					.quiet = true,
+				},
+				{
+					.path = file1_s1d2,
+					.access = 0,
+					.quiet = true,
+				},
+				{
+					.path = file2_s1d2,
+					.access = FS_R,
+					.quiet = true,
+				},
+			},
+		},
+		{
+			.handled_access_fs = FS_W,
+			.quiet_access_fs = FS_W,
+			.rules = {
+				{
+					.path = file1_s1d1,
+					.access = FS_W,
+					.quiet = false,
+				},
+				/* Nothing for file2_s1d1 */
+				{
+					.path = file1_s1d2,
+					.access = FS_W,
+					.quiet = false,
+				},
+				/* Nothing for file2_s1d2 */
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+			.expect_truncate_success = true,
+		},
+		/* Missing both, youngest layer denies write, not quiet */
+		{
+			.target = file2_s1d1,
+			.open_mode = O_RDWR,
+			.audit_write_blocked = true,
+		},
+		/* Missing read, denied and quieted by layer 1 */
+		{
+			.target = file1_s1d2,
+			.open_mode = O_RDWR,
+		},
+		/* Missing write, denied and not quieted by layer 2 */
+		{
+			.target = file2_s1d2,
+			.open_mode = O_RDWR,
+			.audit_write_blocked = true,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_different_handled_2) {
+	/* Quiet from layer 2 */
+	.layers = {
+		{
+			.handled_access_fs = FS_R,
+			.quiet_access_fs = FS_R,
+			.rules = {
+				{
+					.path = file1_s1d1,
+					.access = FS_R,
+					.quiet = false,
+				},
+				/* Nothing for file2_s1d1 and file1_s1d2 */
+				{
+					.path = file2_s1d2,
+					.access = FS_R,
+					.quiet = false,
+				},
+			},
+		},
+		{
+			.handled_access_fs = FS_W,
+			.quiet_access_fs = FS_W,
+			.rules = {
+				{
+					.path = file1_s1d1,
+					.access = FS_W,
+					.quiet = true,
+				},
+				{
+					.path = file2_s1d1,
+					.access = 0,
+					.quiet = true,
+				},
+				{
+					.path = file1_s1d2,
+					.access = FS_W,
+					.quiet = true,
+				},
+				{
+					.path = file2_s1d2,
+					.access = 0,
+					.quiet = true,
+				},
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+			.expect_truncate_success = true,
+		},
+		/* Missing both, youngest layer denies write, quiet */
+		{
+			.target = file2_s1d1,
+			.open_mode = O_RDWR,
+		},
+		/* Missing read, denied and not quieted by layer 1 */
+		{
+			.target = file1_s1d2,
+			.open_mode = O_RDWR,
+			.audit_read_blocked = true,
+		},
+		/* Missing write, denied and quieted by layer 2 */
+		{
+			.target = file2_s1d2,
+			.open_mode = O_RDWR,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_different_handled_3) {
+	/* Quiet from both layers */
+	.layers = {
+		{
+			.handled_access_fs = FS_R,
+			.quiet_access_fs = FS_R,
+			.rules = {
+				{
+					.path = file1_s1d1,
+					.access = FS_R,
+					.quiet = true,
+				},
+				{
+					.path = file2_s1d1,
+					.access = 0,
+					.quiet = true,
+				},
+				{
+					.path = file1_s1d2,
+					.access = 0,
+					.quiet = true,
+				},
+				{
+					.path = file2_s1d2,
+					.access = FS_R,
+					.quiet = true,
+				},
+			},
+		},
+		{
+			.handled_access_fs = FS_W,
+			.quiet_access_fs = FS_W,
+			.rules = {
+				{
+					.path = file1_s1d1,
+					.access = FS_W,
+					.quiet = true,
+				},
+				{
+					.path = file2_s1d1,
+					.access = 0,
+					.quiet = true,
+				},
+				{
+					.path = file1_s1d2,
+					.access = FS_W,
+					.quiet = true,
+				},
+				{
+					.path = file2_s1d2,
+					.access = 0,
+					.quiet = true,
+				},
+			},
+		},
+	},
+	.targets = {
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+			.expect_open_success = true,
+			.expect_truncate_success = true,
+		},
+		{
+			.target = file2_s1d1,
+			.open_mode = O_RDWR,
+		},
+		{
+			.target = file1_s1d2,
+			.open_mode = O_RDWR,
+		},
+		{
+			.target = file2_s1d2,
+			.open_mode = O_RDWR,
+		},
+	},
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, without_quiet_then_with_quiet) {
+	.layers = {
+		{
+			.handled_access_fs = FS_R | FS_W,
+			.quiet_access_fs = FS_R,
+			.rules = {
+				{ .path = dir_s1d1, .access = FS_W, .quiet = false },
+				{ .path = dir_s1d1, .access = 0, .quiet = true },
+			},
+		},
+	},
+	.targets = {
+		/* Read denied and quieted */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDONLY,
+		},
+		/* Write ok */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_WRONLY,
+			.expect_open_success = true,
+			.expect_truncate_success = true,
+		},
+		/* Write ok, read denied and quieted */
+		{
+			.target = file1_s1d1,
+			.open_mode = O_RDWR,
+		},
+		/* Not covered by quiet */
+		{
+			.target = file1_s2d1,
+			.open_mode = O_RDONLY,
+			.audit_read_blocked = true,
+		},
+	},
+};
+
+/*
+ * The following TEST_F extend the above test cases to test more layers,
+ * with the inserted layers having varying configurations.
+ */
+
+/* Extra allow all layers, quiet or not, does not change any behaviour. */
+TEST_F(audit_quiet_layout1, allow_all_layer)
+{
+	struct a_layer allow_all_layer = {
+		.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+		.quiet_access_fs = 0,
+		.rules = {
+			{
+				.path = "/",
+				.access = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+				.quiet = false,
+			},
+		},
+	};
+	int i;
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &allow_all_layer));
+	for (i = 0; i < ARRAY_SIZE(variant->layers); i++)
+		ASSERT_EQ(0, apply_a_layer(_metadata, &variant->layers[i]));
+
+	audit_quiet_layout1_test_body(_metadata, self, variant->targets);
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &allow_all_layer));
+
+	audit_quiet_layout1_test_body(_metadata, self, variant->targets);
+
+	/*
+	 * SELF_LOG flags or quiet bits from inner allowing layers should not
+	 * affect behaviour.
+	 */
+	allow_all_layer.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL;
+	allow_all_layer.rules[0].quiet = true;
+	/*
+	 * Note: this only works because we're not checking counts of domain
+	 * alloc/dealloc logs
+	 */
+	allow_all_layer.restrict_flags =
+		LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF |
+		LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF;
+	ASSERT_EQ(0, apply_a_layer(_metadata, &allow_all_layer));
+
+	audit_quiet_layout1_test_body(_metadata, self, variant->targets);
+}
+
+/*
+ * Add useless outer layers until we reach the layer limit.  Should not
+ * change anything.
+ */
+TEST_F(audit_quiet_layout1, many_outer_layers)
+{
+	struct a_layer useless_layer = {
+		.handled_access_fs = FS_R | FS_W | FS_TRUNC,
+		.quiet_access_fs = FS_R | FS_W | FS_TRUNC,
+		.rules = {
+			{ .path = "/", .access = FS_R | FS_W | FS_TRUNC, .quiet = true },
+		},
+	};
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(variant->layers); i++) {
+		if (variant->layers[i].handled_access_fs == 0)
+			break;
+	}
+
+	for (; i < LANDLOCK_MAX_NUM_LAYERS; i++)
+		ASSERT_EQ(0, apply_a_layer(_metadata, &useless_layer));
+
+	for (i = 0; i < ARRAY_SIZE(variant->layers); i++)
+		ASSERT_EQ(0, apply_a_layer(_metadata, &variant->layers[i]));
+
+	audit_quiet_layout1_test_body(_metadata, self, variant->targets);
+}
+
+/*
+ * An inner layer that denies and quiets everything should result in no
+ * logs.
+ */
+TEST_F(audit_quiet_layout1, deny_all_quiet_layer)
+{
+	struct a_layer deny_all_layer = {
+		.handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+		.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+		.rules = {
+			{ .path = "/", .access = 0, .quiet = true },
+		},
+	};
+	int i;
+	FIXTURE_VARIANT(audit_quiet_layout1) variant_2 = {};
+
+	/* Any open should fail with no logs. */
+	for (i = 0; i < ARRAY_SIZE(variant->targets); i++) {
+		const struct a_target *target = &variant->targets[i];
+
+		variant_2.targets[i] = (struct a_target){
+			.target = target->target,
+			.open_mode = target->open_mode,
+			/* We denied everything, open should always fail. */
+			.expect_open_success = false,
+		};
+	}
+
+	for (i = 0; i < ARRAY_SIZE(variant->layers); i++)
+		ASSERT_EQ(0, apply_a_layer(_metadata, &variant->layers[i]));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &deny_all_layer));
+
+	audit_quiet_layout1_test_body(_metadata, self, variant_2.targets);
+}
+
+/*
+ * An inner layer that denies everything without quiet should produce logs
+ * for all access.
+ */
+TEST_F(audit_quiet_layout1, deny_all_layer)
+{
+	struct a_layer deny_all_layer = {
+		.handled_access_fs = FS_R | FS_W,
+		.quiet_access_fs = FS_R | FS_W,
+	};
+	int i;
+	FIXTURE_VARIANT(audit_quiet_layout1) variant_2 = {};
+	bool test_has_subdomains_off = false;
+
+	for (i = 0; i < ARRAY_SIZE(variant->layers); i++) {
+		if (variant->layers[i].restrict_flags &
+		    LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF) {
+			test_has_subdomains_off = true;
+			break;
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(variant->targets); i++) {
+		const struct a_target *target = &variant->targets[i];
+
+		variant_2.targets[i] = (struct a_target){
+			.target = target->target,
+			.open_mode = target->open_mode,
+
+			/* We denied everything, open should always fail. */
+			.expect_open_success = false,
+			/* Audit should always happen as long as open request contains read. */
+			.audit_read_blocked = !test_has_subdomains_off &&
+					      target->open_mode != O_WRONLY,
+			/* Audit should always happen as long as open request contains write. */
+			.audit_write_blocked = !test_has_subdomains_off &&
+					       target->open_mode != O_RDONLY,
+		};
+	}
+
+	for (i = 0; i < ARRAY_SIZE(variant->layers); i++)
+		ASSERT_EQ(0, apply_a_layer(_metadata, &variant->layers[i]));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &deny_all_layer));
+
+	audit_quiet_layout1_test_body(_metadata, self, variant_2.targets);
+}
+
+/* Uses layout1_bind hierarchy */
+FIXTURE(audit_quiet_rename)
+{
+	struct audit_filter audit_filter;
+	int audit_fd;
+};
+
+FIXTURE_SETUP(audit_quiet_rename)
+{
+	prepare_layout(_metadata);
+	create_layout1(_metadata);
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, mount(dir_s1d2, dir_s2d2, NULL, MS_BIND, NULL));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
+	EXPECT_LE(0, self->audit_fd);
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+
+	if (getenv("DEBUG_QUIET_TESTS"))
+		debug_quiet_tests = true;
+}
+
+FIXTURE_TEARDOWN_PARENT(audit_quiet_rename)
+{
+	remove_layout1(_metadata);
+	cleanup_layout(_metadata);
+
+	/* umount(dir_s2d2)) is handled by namespace lifetime. */
+
+	remove_path(file1_s4d1);
+	remove_path(file2_s4d1);
+
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	EXPECT_EQ(0, audit_cleanup(-1, NULL));
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+static void simple_quiet_rename(struct __test_metadata *const _metadata,
+				FIXTURE_DATA(audit_quiet_rename) *const self,
+				__u64 handled_access, __u64 quiet_access,
+				bool source_allow, bool dest_allow,
+				bool source_quiet, bool dest_quiet,
+				const char *source_blockers,
+				const char *dest_blockers)
+{
+	/* We will move file1_s1d1 to file1_s2d1 */
+	struct a_layer layer = {
+		.handled_access_fs = handled_access,
+		.quiet_access_fs = quiet_access,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = source_allow ? handled_access : 0,
+				.quiet = source_quiet,
+			},
+			{
+				.path = dir_s2d1,
+				.access = dest_allow ? handled_access : 0,
+				.quiet = dest_quiet,
+			},
+		},
+	};
+	struct audit_records records = {};
+	int ret, err;
+
+	/* Skip landlock_add_rule for useless rules. */
+	if (!source_allow && !source_quiet)
+		layer.rules[0].path = NULL;
+	if (!dest_allow && !dest_quiet)
+		layer.rules[1].path = NULL;
+
+	EXPECT_EQ(0, unlink(file1_s2d1));
+	EXPECT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	if (debug_quiet_tests)
+		TH_LOG("Try renameat \"%s\" to \"%s\"", file1_s1d1, file1_s2d1);
+	ret = renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1);
+	err = errno;
+	if (ret != 0 && debug_quiet_tests) {
+		TH_LOG("renameat error: %s", err == EXDEV  ? "EXDEV" :
+					     err == EACCES ? "EACCES" :
+							     strerror(err));
+	}
+	if (source_allow && dest_allow) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(-1, ret);
+		if (handled_access & (LANDLOCK_ACCESS_FS_MAKE_REG |
+				      LANDLOCK_ACCESS_FS_REMOVE_FILE)) {
+			ASSERT_EQ(EACCES, err);
+		} else {
+			ASSERT_EQ(EXDEV, err);
+		}
+
+		if (source_blockers)
+			ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+						    source_blockers, dir_s1d1));
+		if (dest_blockers)
+			ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+						    dest_blockers, dir_s2d1));
+	}
+	/*
+	 * No other logs. records.domain not checked per reasoning in
+	 * audit_quiet_layout1_test_body.
+	 */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, rename_ok)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+
+	simple_quiet_rename(_metadata, self, access, access, true, true, false,
+			    false, NULL, NULL);
+}
+
+TEST_F(audit_quiet_rename, no_quiet)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+
+	simple_quiet_rename(_metadata, self, access, access, false, false,
+			    false, false, "fs\\.remove_file,fs\\.refer",
+			    "fs\\.make_reg,fs\\.refer");
+}
+
+TEST_F(audit_quiet_rename, quiet)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+
+	simple_quiet_rename(_metadata, self, access, access, false, false, true,
+			    true, NULL, NULL);
+}
+
+TEST_F(audit_quiet_rename, source_no_quiet_dest_quiet)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+
+	simple_quiet_rename(_metadata, self, access, access, false, false,
+			    false, true, "fs\\.remove_file,fs\\.refer", NULL);
+}
+
+TEST_F(audit_quiet_rename, source_quiet_dest_no_quiet)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+
+	simple_quiet_rename(_metadata, self, access, access, false, false, true,
+			    false, NULL, "fs\\.make_reg,fs\\.refer");
+}
+
+TEST_F(audit_quiet_rename, only_quiet_refer)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+
+	simple_quiet_rename(_metadata, self, access, LANDLOCK_ACCESS_FS_REFER,
+			    false, false, true, true,
+			    "fs\\.remove_file,fs\\.refer",
+			    "fs\\.make_reg,fs\\.refer");
+}
+
+TEST_F(audit_quiet_rename, source_allow_dest_quiet)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+
+	simple_quiet_rename(_metadata, self, access, access, true, false, false,
+			    true, NULL, NULL);
+}
+
+TEST_F(audit_quiet_rename, source_quiet_dest_allow)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+
+	simple_quiet_rename(_metadata, self, access, access, false, true, true,
+			    false, NULL, NULL);
+}
+
+TEST_F(audit_quiet_rename, handle_all_deny_quiet_refer)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer = {
+		.handled_access_fs = access,
+		.quiet_access_fs = LANDLOCK_ACCESS_FS_REFER,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = LANDLOCK_ACCESS_FS_MAKE_REG |
+					LANDLOCK_ACCESS_FS_REMOVE_FILE,
+				.quiet = true,
+			},
+			{
+				.path = dir_s2d1,
+				.access = LANDLOCK_ACCESS_FS_MAKE_REG |
+					LANDLOCK_ACCESS_FS_REMOVE_FILE,
+				.quiet = true,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	EXPECT_EQ(0, unlink(file1_s2d1));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+	ASSERT_EQ(EXDEV, errno);
+
+	/* No logs */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, handle_all_deny_not_quiet_refer)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer = {
+		.handled_access_fs = access,
+		.quiet_access_fs = 0,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = LANDLOCK_ACCESS_FS_MAKE_REG |
+					LANDLOCK_ACCESS_FS_REMOVE_FILE,
+				.quiet = false,
+			},
+			{
+				.path = dir_s2d1,
+				.access = LANDLOCK_ACCESS_FS_MAKE_REG |
+					LANDLOCK_ACCESS_FS_REMOVE_FILE,
+				.quiet = false,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	EXPECT_EQ(0, unlink(file1_s2d1));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+	ASSERT_EQ(EXDEV, errno);
+
+	ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
+				    dir_s1d1));
+	ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
+				    dir_s2d1));
+
+	/* No other logs */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, handle_all_deny_refer_quiet_source_not_quiet_dest)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer = {
+		.handled_access_fs = access,
+		.quiet_access_fs = LANDLOCK_ACCESS_FS_REFER,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = LANDLOCK_ACCESS_FS_MAKE_REG |
+					LANDLOCK_ACCESS_FS_REMOVE_FILE,
+				.quiet = true,
+			},
+			{
+				.path = dir_s2d1,
+				.access = LANDLOCK_ACCESS_FS_MAKE_REG |
+					LANDLOCK_ACCESS_FS_REMOVE_FILE,
+				.quiet = false,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	EXPECT_EQ(0, unlink(file1_s2d1));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+	ASSERT_EQ(EXDEV, errno);
+
+	ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
+				    dir_s2d1));
+
+	/* No other logs */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_same_dir)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file2_s1d1));
+	ASSERT_EQ(EACCES, errno);
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_flag_on_file_ignored)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = file1_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+			{
+				.path = file1_s2d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+	ASSERT_EQ(EACCES, errno);
+
+	ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+				    "fs\\.remove_file,fs\\.refer", dir_s1d1));
+	/* We didn't unlink destination file */
+	ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+				    "fs\\.remove_file,fs\\.make_reg,fs\\.refer",
+				    dir_s2d1));
+
+	/* No other logs */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_flag_on_file_ignored_same_dir)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = file1_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+			{
+				.path = file2_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file2_s1d1));
+	ASSERT_EQ(EACCES, errno);
+
+	ASSERT_EQ(0,
+		  matches_log_fs(_metadata, self->audit_fd,
+				 "fs\\.remove_file,fs\\.make_reg", dir_s1d1));
+
+	/* No other logs */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, two_layers_different_quiet1)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer1 = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = access,
+				.quiet = false,
+			},
+			{
+				.path = dir_s2d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct a_layer layer2 = {
+		.handled_access_fs = access,
+		.quiet_access_fs = LANDLOCK_ACCESS_FS_REFER,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+			{
+				.path = dir_s2d1,
+				.access = access,
+				.quiet = false,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	EXPECT_EQ(0, unlink(file1_s2d1));
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer1));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer2));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+	ASSERT_EQ(EACCES, errno);
+
+	/*
+	 * The youngest denial will be layer 2.  Refer is quieted but we are
+	 * also missing remove_file on source.
+	 */
+	ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+				    "fs\\.remove_file,fs\\.refer", dir_s1d1));
+	/* No other logs */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, two_layers_different_quiet2)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer1 = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = access,
+				.quiet = false,
+			},
+			{
+				.path = dir_s2d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct a_layer layer2 = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_REFER,
+		.quiet_access_fs = LANDLOCK_ACCESS_FS_REFER,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+			{
+				.path = dir_s2d1,
+				.access = LANDLOCK_ACCESS_FS_REFER,
+				.quiet = false,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	EXPECT_EQ(0, unlink(file1_s2d1));
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer1));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer2));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+	ASSERT_EQ(EACCES, errno);
+
+	/*
+	 * The youngest denial will be layer 2, but refer is quieted (and that
+	 * layer does not handle any other accesses).
+	 */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, two_layers_different_quiet3)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer1 = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = access,
+				.quiet = false,
+			},
+			{
+				.path = dir_s2d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct a_layer layer2 = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+			{
+				.path = dir_s2d1,
+				.access = access,
+				.quiet = false,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	EXPECT_EQ(0, unlink(file1_s2d1));
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer1));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer2));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+	ASSERT_EQ(EACCES, errno);
+
+	/*
+	 * The youngest denial will be layer 2, in which everything is
+	 * quieted.
+	 */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename,
+       first_layer_quiet_deny_all_second_layer_not_quiet_deny_all)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer1 = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+			{
+				.path = dir_s2d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct a_layer layer2 = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {},
+	};
+	struct audit_records records = {};
+
+	EXPECT_EQ(0, unlink(file1_s2d1));
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer1));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer2));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+	ASSERT_EQ(EACCES, errno);
+
+	ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+				    "fs\\.remove_file,fs\\.refer", dir_s1d1));
+	ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+				    "fs\\.make_reg,fs\\.refer", dir_s2d1));
+	/* No other logs. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename,
+       first_layer_quiet_deny_all_second_layer_dest_not_quiet)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer1 = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+			{
+				.path = dir_s2d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct a_layer layer2 = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	EXPECT_EQ(0, unlink(file1_s2d1));
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer1));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer2));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+	ASSERT_EQ(EACCES, errno);
+
+	/*
+	 * Source is quieted but destination is not.
+	 */
+	ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+				    "fs\\.make_reg,fs\\.refer", dir_s2d1));
+	/* No other logs. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, rename_xchg)
+{
+	struct a_layer layer = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_REG |
+				     LANDLOCK_ACCESS_FS_REMOVE_FILE |
+				     LANDLOCK_ACCESS_FS_REFER,
+		.quiet_access_fs = LANDLOCK_ACCESS_FS_MAKE_REG,
+		.rules = { {
+				   .path = dir_s1d1,
+				   .access = LANDLOCK_ACCESS_FS_REMOVE_FILE |
+					     LANDLOCK_ACCESS_FS_REFER,
+				   .quiet = true,
+			   },
+			   {
+				   .path = dir_s2d1,
+				   .access = LANDLOCK_ACCESS_FS_MAKE_REG |
+					     LANDLOCK_ACCESS_FS_REMOVE_FILE |
+					     LANDLOCK_ACCESS_FS_REFER,
+				   .quiet = false,
+			   } },
+	};
+	struct audit_records records = {};
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1,
+				RENAME_EXCHANGE));
+	ASSERT_EQ(EACCES, errno);
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_on_parent_mount)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s2d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	EXPECT_EQ(0, unlink(file2_s1d3));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, bind_file1_s1d3, AT_FDCWD,
+			       bind_file2_s1d3));
+	ASSERT_EQ(EACCES, errno);
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_behind_mountpoint_ignored)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s1d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct audit_records records = {};
+
+	EXPECT_EQ(0, unlink(file2_s1d3));
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1, renameat(AT_FDCWD, bind_file1_s1d3, AT_FDCWD,
+			       bind_file2_s1d3));
+	ASSERT_EQ(EACCES, errno);
+	ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+				    "fs\\.remove_file,fs\\.make_reg",
+				    bind_dir_s1d3));
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_on_parent_mount_disconnected)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s2d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct audit_records records = {};
+	int bind_s1d3_fd;
+
+	EXPECT_EQ(0, unlink(file2_s1d3));
+
+	bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_DIRECTORY);
+	ASSERT_GE(bind_s1d3_fd, 0);
+
+	/* Make s1d3 disconnected. */
+	create_directory(_metadata, dir_s4d1);
+	ASSERT_EQ(0, renameat(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s4d2));
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1,
+		  renameat(bind_s1d3_fd, file1_name, bind_s1d3_fd, file2_name));
+	ASSERT_EQ(EACCES, errno);
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_behind_mountpoint_disconnected)
+{
+	__u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+		       LANDLOCK_ACCESS_FS_REMOVE_FILE |
+		       LANDLOCK_ACCESS_FS_REFER;
+	struct a_layer layer = {
+		.handled_access_fs = access,
+		.quiet_access_fs = access,
+		.rules = {
+			{
+				.path = dir_s4d1,
+				.access = 0,
+				.quiet = true,
+			},
+		},
+	};
+	struct audit_records records = {};
+	int bind_s1d3_fd;
+
+	EXPECT_EQ(0, unlink(file2_s1d3));
+
+	bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_DIRECTORY);
+	ASSERT_GE(bind_s1d3_fd, 0);
+
+	/* Make s1d3 disconnected. */
+	create_directory(_metadata, dir_s4d1);
+	ASSERT_EQ(0, renameat(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s4d2));
+
+	ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+	ASSERT_EQ(-1,
+		  renameat(bind_s1d3_fd, file1_name, bind_s1d3_fd, file2_name));
+	ASSERT_EQ(EACCES, errno);
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	ASSERT_EQ(0, records.access);
+}
+
 TEST_HARNESS_MAIN
-- 
2.54.0

^ permalink raw reply related

* [PATCH v11 5/9] selftests/landlock: Replace hard-coded 16 with a constant
From: Tingmao Wang @ 2026-06-12  1:48 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
	Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1781228815.git.m@maowtm.org>

The next commit will reuse this number.  Make it a shared constant to
future-proof changes.

Signed-off-by: Tingmao Wang <m@maowtm.org>
---

Changes in v3:
- New patch

 tools/testing/selftests/landlock/audit_test.c | 2 +-
 tools/testing/selftests/landlock/common.h     | 2 ++
 tools/testing/selftests/landlock/fs_test.c    | 2 +-
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
index 376931511629..d55fe4c5b531 100644
--- a/tools/testing/selftests/landlock/audit_test.c
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -79,7 +79,7 @@ TEST_F(audit, layers)
 		.scoped = LANDLOCK_SCOPE_SIGNAL,
 	};
 	int status, ruleset_fd, i;
-	__u64(*domain_stack)[16];
+	__u64(*domain_stack)[LANDLOCK_MAX_NUM_LAYERS];
 	__u64 prev_dom = 3;
 	pid_t child;
 
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 90551650299c..7206d5105d66 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -25,6 +25,8 @@
 /* TEST_F_FORK() should not be used for new tests. */
 #define TEST_F_FORK(fixture_name, test_name) TEST_F(fixture_name, test_name)
 
+#define LANDLOCK_MAX_NUM_LAYERS 16
+
 static const char bin_sandbox_and_launch[] = "./sandbox-and-launch";
 static const char bin_wait_pipe[] = "./wait-pipe";
 static const char bin_wait_pipe_sandbox[] = "./wait-pipe-sandbox";
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index cdb47fc1fc0a..10d9355ade5f 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -1441,7 +1441,7 @@ TEST_F_FORK(layout0, max_layers)
 	};
 	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
 
-	for (i = 0; i < 16; i++)
+	for (i = 0; i < LANDLOCK_MAX_NUM_LAYERS; i++)
 		enforce_ruleset(_metadata, ruleset_fd);
 
 	for (i = 0; i < 2; i++) {
-- 
2.54.0

^ permalink raw reply related

* [PATCH v11 4/9] samples/landlock: Add quiet flag support to sandboxer
From: Tingmao Wang @ 2026-06-12  1:48 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
	Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1781228815.git.m@maowtm.org>

Adds ability to set which access bits to quiet via LL_*_QUIET_ACCESS (FS,
NET or SCOPED), and attach quiet flags to individual objects via
LL_*_QUIET for FS and NET.

Assisted-by: GitHub-Copilot:claude-opus-4.8 copilot-reviepickw
Signed-off-by: Tingmao Wang <m@maowtm.org>
---

Changes in v11:
- Error if quiet flags not supported by current kernel but quiet envs provided
- Fix comment
- Refactor env vars in sandboxer: LL_{FS,NET,SCOPED}_QUIET_ACCESS are
  merged into one LL_QUIET_ACCESS, and used more sensible names.

Changes in v10:
- Remove stray __attribute__((fallthrough)); (Thanks Justin for
  spotting)

Changes in v9:
- Add udp connect / bind quiet flag support

Changes in v8:
- Rebase on top of mic/next
- populate_ruleset_net() already does not require the env var to be
  present, so remove redundant comment and check above
  populate_ruleset_net(ENV_NET_QUIET_NAME, ...).

Changes in v6:
- Make populate_ruleset_{fs,net} take a flags argument instead of a bool
  quiet (suggested by Justin Suess)
- Fix if braces style

Changes in v3:
- Minor change to the above commit message.

Changes in v2:
- Added new environment variables to control which quiet access bits to
  set on the rule, and populate quiet_access_* from it.
- Added support for quieting net rules and scoped access.  Renamed patch
  title.
- Increment ABI version

 samples/landlock/sandboxer.c | 138 ++++++++++++++++++++++++++++++++---
 1 file changed, 127 insertions(+), 11 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index f44db2857bbf..f18228ccf66a 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -58,9 +58,12 @@ static inline int landlock_restrict_self(const int ruleset_fd,
 
 #define ENV_FS_RO_NAME "LL_FS_RO"
 #define ENV_FS_RW_NAME "LL_FS_RW"
+#define ENV_FS_QUIET_NAME "LL_FS_QUIET"
 #define ENV_TCP_BIND_NAME "LL_TCP_BIND"
 #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
+#define ENV_NET_QUIET_NAME "LL_NET_QUIET"
 #define ENV_SCOPED_NAME "LL_SCOPED"
+#define ENV_QUIET_ACCESS_NAME "LL_QUIET_ACCESS"
 #define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
 #define ENV_UDP_BIND_NAME "LL_UDP_BIND"
 #define ENV_UDP_CONNECT_SEND_NAME "LL_UDP_CONNECT_SEND"
@@ -119,7 +122,7 @@ static int parse_path(char *env_path, const char ***const path_list)
 /* clang-format on */
 
 static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
-			       const __u64 allowed_access)
+			       const __u64 allowed_access, __u32 flags)
 {
 	int num_paths, i, ret = 1;
 	char *env_path_name;
@@ -169,7 +172,7 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
 		if (!S_ISDIR(statbuf.st_mode))
 			path_beneath.allowed_access &= ACCESS_FILE;
 		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
-				      &path_beneath, 0)) {
+				      &path_beneath, flags)) {
 			fprintf(stderr,
 				"Failed to update the ruleset with \"%s\": %s\n",
 				path_list[i], strerror(errno));
@@ -187,7 +190,7 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
 }
 
 static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
-				const __u64 allowed_access)
+				const __u64 allowed_access, __u32 flags)
 {
 	int ret = 1;
 	char *env_port_name, *env_port_name_next, *strport;
@@ -215,7 +218,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
 		}
 		net_port.port = port;
 		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
-				      &net_port, 0)) {
+				      &net_port, flags)) {
 			fprintf(stderr,
 				"Failed to update the ruleset with port \"%llu\": %s\n",
 				net_port.port, strerror(errno));
@@ -303,6 +306,69 @@ static bool check_ruleset_scope(const char *const env_var,
 
 /* clang-format on */
 
+/*
+ * Parses ENV_QUIET_ACCESS_NAME and sets the quiet_access_fs,
+ * quiet_access_net and quiet_scoped masks of @ruleset_attr accordingly.
+ */
+static int add_quiet_access(const char *const env_var,
+			    struct landlock_ruleset_attr *const ruleset_attr)
+{
+	char *env_quiet_access, *env_quiet_access_next, *str_access;
+
+	env_quiet_access = getenv(env_var);
+	if (!env_quiet_access)
+		return 0;
+
+	env_quiet_access = strdup(env_quiet_access);
+	env_quiet_access_next = env_quiet_access;
+	unsetenv(env_var);
+
+	while ((str_access = strsep(&env_quiet_access_next, ENV_DELIMITER))) {
+		if (strcmp(str_access, "") == 0)
+			continue;
+		else if (strcmp(str_access, "all") == 0) {
+			ruleset_attr->quiet_access_fs =
+				ruleset_attr->handled_access_fs;
+			ruleset_attr->quiet_access_net =
+				ruleset_attr->handled_access_net;
+			ruleset_attr->quiet_scoped = ruleset_attr->scoped;
+		} else if (strcmp(str_access, "read") == 0)
+			ruleset_attr->quiet_access_fs |= ACCESS_FS_ROUGHLY_READ;
+		else if (strcmp(str_access, "write") == 0)
+			ruleset_attr->quiet_access_fs |=
+				ACCESS_FS_ROUGHLY_WRITE;
+		else if (strcmp(str_access, "tcp_bind") == 0)
+			ruleset_attr->quiet_access_net |=
+				LANDLOCK_ACCESS_NET_BIND_TCP;
+		else if (strcmp(str_access, "tcp_connect") == 0)
+			ruleset_attr->quiet_access_net |=
+				LANDLOCK_ACCESS_NET_CONNECT_TCP;
+		else if (strcmp(str_access, "udp_bind") == 0)
+			ruleset_attr->quiet_access_net |=
+				LANDLOCK_ACCESS_NET_BIND_UDP;
+		else if (strcmp(str_access, "udp_connect") == 0)
+			ruleset_attr->quiet_access_net |=
+				LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
+		else if (strcmp(str_access, "abstract_unix_socket") == 0)
+			ruleset_attr->quiet_scoped |=
+				LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
+		else if (strcmp(str_access, "signal") == 0)
+			ruleset_attr->quiet_scoped |= LANDLOCK_SCOPE_SIGNAL;
+		else {
+			fprintf(stderr, "Unknown quiet access \"%s\"\n",
+				str_access);
+			free(env_quiet_access);
+			return -1;
+		}
+	}
+
+	free(env_quiet_access);
+	ruleset_attr->quiet_access_fs &= ruleset_attr->handled_access_fs;
+	ruleset_attr->quiet_access_net &= ruleset_attr->handled_access_net;
+	ruleset_attr->quiet_scoped &= ruleset_attr->scoped;
+	return 0;
+}
+
 #define LANDLOCK_ABI_LAST 10
 
 #define XSTR(s) #s
@@ -337,6 +403,19 @@ static const char help[] =
 	"\n"
 	"A sandboxer should not log denied access requests to avoid spamming logs, "
 	"but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
+	ENV_FS_QUIET_NAME " and " ENV_NET_QUIET_NAME ", both optional, can then be used "
+	"to make access to some denied paths or network ports not trigger audit logging.\n"
+	ENV_QUIET_ACCESS_NAME " can be used to specify which accesses should be quieted "
+	"(required when " ENV_FS_QUIET_NAME " or " ENV_NET_QUIET_NAME " is set):\n"
+	"  - \"all\" to quiet all of the accesses below\n"
+	"  - \"read\" to quiet all file/dir read accesses\n"
+	"  - \"write\" to quiet all file/dir write accesses\n"
+	"  - \"tcp_bind\" to quiet tcp bind denials\n"
+	"  - \"tcp_connect\" to quiet tcp connect denials\n"
+	"  - \"udp_bind\" to quiet udp bind denials\n"
+	"  - \"udp_connect\" to quiet udp connect / send denials\n"
+	"  - \"abstract_unix_socket\" to quiet abstract unix socket denials\n"
+	"  - \"signal\" to quiet signal denials\n"
 	"\n"
 	"Example:\n"
 	ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
@@ -369,7 +448,11 @@ int main(const int argc, char *const argv[], char *const *const envp)
 				      LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
 		.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
 			  LANDLOCK_SCOPE_SIGNAL,
+		.quiet_access_fs = 0,
+		.quiet_access_net = 0,
+		.quiet_scoped = 0,
 	};
+	bool quiet_supported = true;
 	int supported_restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
 	int set_restrict_flags = 0;
 
@@ -460,6 +543,8 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		ruleset_attr.handled_access_net &=
 			~(LANDLOCK_ACCESS_NET_BIND_UDP |
 			  LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
+		/* Removes quiet flags for ABI < 10 later on. */
+		quiet_supported = false;
 
 		/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
 		fprintf(stderr,
@@ -526,6 +611,25 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		unsetenv(ENV_FORCE_LOG_NAME);
 	}
 
+	/* Set the quiet access masks. */
+	if (quiet_supported) {
+		if ((getenv(ENV_FS_QUIET_NAME) || getenv(ENV_NET_QUIET_NAME)) &&
+		    !getenv(ENV_QUIET_ACCESS_NAME)) {
+			fprintf(stderr,
+				"%s must be set (e.g. to \"all\") when %s or %s is used\n",
+				ENV_QUIET_ACCESS_NAME, ENV_FS_QUIET_NAME,
+				ENV_NET_QUIET_NAME);
+			return 1;
+		}
+		if (add_quiet_access(ENV_QUIET_ACCESS_NAME, &ruleset_attr))
+			return 1;
+	} else if (getenv(ENV_FS_QUIET_NAME) || getenv(ENV_NET_QUIET_NAME) ||
+		   getenv(ENV_QUIET_ACCESS_NAME)) {
+		fprintf(stderr,
+			"Quiet flags not supported by current kernel\n");
+		return 1;
+	}
+
 	ruleset_fd =
 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
 	if (ruleset_fd < 0) {
@@ -533,30 +637,42 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		return 1;
 	}
 
-	if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
+	if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro, 0))
 		goto err_close_ruleset;
-	}
-	if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
+	if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw, 0))
 		goto err_close_ruleset;
+
+	/* Don't require this env to be present. */
+	if (quiet_supported && getenv(ENV_FS_QUIET_NAME)) {
+		if (populate_ruleset_fs(ENV_FS_QUIET_NAME, ruleset_fd, 0,
+					LANDLOCK_ADD_RULE_QUIET))
+			goto err_close_ruleset;
 	}
 
 	if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd,
-				 LANDLOCK_ACCESS_NET_BIND_TCP)) {
+				 LANDLOCK_ACCESS_NET_BIND_TCP, 0)) {
 		goto err_close_ruleset;
 	}
 	if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd,
-				 LANDLOCK_ACCESS_NET_CONNECT_TCP)) {
+				 LANDLOCK_ACCESS_NET_CONNECT_TCP, 0)) {
 		goto err_close_ruleset;
 	}
 	if (populate_ruleset_net(ENV_UDP_BIND_NAME, ruleset_fd,
-				 LANDLOCK_ACCESS_NET_BIND_UDP)) {
+				 LANDLOCK_ACCESS_NET_BIND_UDP, 0)) {
 		goto err_close_ruleset;
 	}
 	if (populate_ruleset_net(ENV_UDP_CONNECT_SEND_NAME, ruleset_fd,
-				 LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)) {
+				 LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP, 0)) {
 		goto err_close_ruleset;
 	}
 
+	if (quiet_supported) {
+		if (populate_ruleset_net(ENV_NET_QUIET_NAME, ruleset_fd, 0,
+					 LANDLOCK_ADD_RULE_QUIET)) {
+			goto err_close_ruleset;
+		}
+	}
+
 	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
 		perror("Failed to restrict privileges");
 		goto err_close_ruleset;
-- 
2.54.0

^ permalink raw reply related

* [PATCH v11 3/9] landlock: Suppress logging when quiet flag is present
From: Tingmao Wang @ 2026-06-12  1:48 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
	Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1781228815.git.m@maowtm.org>

The quietness behaviour is as documented in the previous patch.

For optional accesses, since the existing deny_masks can only store 2x4bit
of layer index, with no way to represent "no layer", we need to either
expand it or have another field to correctly handle quieting of those.
This commit uses the latter approach - we add another field to store which
optional access (of the 2) are covered by quiet rules in their respective
layers as stored in deny_masks.

Assisted-by: GitHub-Copilot:claude-opus-4.8 copilot-review
Signed-off-by: Tingmao Wang <m@maowtm.org>
---

Changes in v11:

- Add quiet_optional_accesses invariant check in is_valid_request
- Enhance docstring on landlock_get_quiet_optional_accesses
- Don't use bitfields for fown_layer and quiet_optional_accesses
- Also remove the newly added build_check_file_security() and use
  bits_per_field based check for quiet_optional_accesses since we now
  don't have a bitfield

Changes in v10:
- clang-format header file changes too
- Fix grammar in some comments

Changes in v9:
- Fix conflict
- Applied struct layer_masks changes to this as well.
- Replace 4 with HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1) in
  landlock_get_quiet_optional_accesses()
- Replace 4 with HWEIGHT in (existing) get_layer_from_deny_masks as
  well.
- Use optional_access_t typedef for all quiet_optional_accesses values
  instead of u8

Changes in v8:
- Rebase on top of mic/next
- Populate request.rule_flags in hook_unix_find()

Changes in v7:
- Following change in commit 1, now we need to copy rule_flags into
  landlock_request before calling landlock_log_denial for relevant fs
  denials
- Remove left over param comment

Changes in v5:
- Update code style and comment in get_layer_from_deny_masks() and
  landlock_log_denial()
- Now that rule_flags is moved into landlock_request, this version removes
  the extra parameter for landlock_log_denial and gets rid of
  no_rule_flags, simplifying some code.
- Fix build failure without CONFIG_AUDIT (reported by Justin Suess)

Changes in v3:
- Renamed patch title from "Check for quiet flag in landlock_log_denial"
  to this given the growth.
- Moved quiet bit check after domain_exec check
- Rename, style and comment fixes suggested by Mickaël.
- Squashed patch 6/6 from v2 "Implement quiet for optional accesses" into
  this one.  Changes to that below:
- Refactor the quiet flag setting in get_layer_from_deny_masks() to be
  more clear.
- Add KUnit tests
- Fix comments, add WARN_ON_ONCE, use __const_hweight64() as suggested by
  review
- Move build_check_file_security to fs.c
- Use a typedef for quiet_optional_accesses, add static_assert, and
  improve docs on landlock_get_quiet_optional_accesses.

Changes in v2:
- Supports the new quiet access masks.
- Support quieting scope requests (but not ptrace and attempted mounting
  for now)

 security/landlock/access.h |   5 +
 security/landlock/audit.c  | 268 ++++++++++++++++++++++++++++++++++---
 security/landlock/audit.h  |   1 +
 security/landlock/domain.c |  38 ++++++
 security/landlock/domain.h |   4 +
 security/landlock/fs.c     |   6 +
 security/landlock/fs.h     |  19 ++-
 security/landlock/net.c    |  15 +--
 8 files changed, 330 insertions(+), 26 deletions(-)

diff --git a/security/landlock/access.h b/security/landlock/access.h
index 94f4b9fb7238..e81164876c7d 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -143,4 +143,9 @@ static inline bool access_mask_subset(access_mask_t subset,
 	return (subset | superset) == superset;
 }
 
+/* A bitmask that is large enough to hold set of optional accesses. */
+typedef u8 optional_access_t;
+static_assert(BITS_PER_TYPE(optional_access_t) >=
+	      HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL));
+
 #endif /* _SECURITY_LANDLOCK_ACCESS_H */
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 8c56f7f6467a..738d8c810b2c 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -249,7 +249,9 @@ static void test_get_denied_layer(struct kunit *const test)
 static size_t
 get_layer_from_deny_masks(access_mask_t *const access_request,
 			  const access_mask_t all_existing_optional_access,
-			  const deny_masks_t deny_masks)
+			  const deny_masks_t deny_masks,
+			  optional_access_t quiet_optional_accesses,
+			  bool *quiet)
 {
 	const unsigned long access_opt = all_existing_optional_access;
 	const unsigned long access_req = *access_request;
@@ -257,6 +259,7 @@ get_layer_from_deny_masks(access_mask_t *const access_request,
 	size_t youngest_layer = 0;
 	size_t access_index = 0;
 	unsigned long access_bit;
+	bool should_quiet = false;
 
 	/* This will require change with new object types. */
 	WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
@@ -265,20 +268,33 @@ get_layer_from_deny_masks(access_mask_t *const access_request,
 			 BITS_PER_TYPE(access_mask_t)) {
 		if (access_req & BIT(access_bit)) {
 			const size_t layer =
-				(deny_masks >> (access_index * 4)) &
+				(deny_masks >>
+				 (access_index *
+				  HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
 				(LANDLOCK_MAX_NUM_LAYERS - 1);
+			const bool layer_has_quiet =
+				!!(quiet_optional_accesses & BIT(access_index));
 
 			if (layer > youngest_layer) {
 				youngest_layer = layer;
 				missing = BIT(access_bit);
+				should_quiet = layer_has_quiet;
 			} else if (layer == youngest_layer) {
 				missing |= BIT(access_bit);
+				/*
+				 * Whether the layer has rules with quiet flag covering
+				 * the file accessed does not depend on the access, and so
+				 * the following WARN_ON_ONCE() should not fail.
+				 */
+				WARN_ON_ONCE(should_quiet && !layer_has_quiet);
+				should_quiet = layer_has_quiet;
 			}
 		}
 		access_index++;
 	}
 
 	*access_request = missing;
+	*quiet = should_quiet;
 	return youngest_layer;
 }
 
@@ -288,42 +304,188 @@ static void test_get_layer_from_deny_masks(struct kunit *const test)
 {
 	deny_masks_t deny_mask;
 	access_mask_t access;
+	optional_access_t quiet_optional_accesses;
+	bool quiet;
 
 	/* truncate:0 ioctl_dev:2 */
 	deny_mask = 0x20;
+	quiet_optional_accesses = 0;
 
 	access = LANDLOCK_ACCESS_FS_TRUNCATE;
 	KUNIT_EXPECT_EQ(test, 0,
-			get_layer_from_deny_masks(&access,
-						  _LANDLOCK_ACCESS_FS_OPTIONAL,
-						  deny_mask));
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
 	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+	KUNIT_EXPECT_EQ(test, 2,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	KUNIT_EXPECT_EQ(test, quiet, false);
 
 	access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
 	KUNIT_EXPECT_EQ(test, 2,
-			get_layer_from_deny_masks(&access,
-						  _LANDLOCK_ACCESS_FS_OPTIONAL,
-						  deny_mask));
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	/* layer denying truncate: quiet, ioctl: not quiet */
+	quiet_optional_accesses = 0b01;
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE;
+	KUNIT_EXPECT_EQ(test, 0,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+	KUNIT_EXPECT_EQ(test, quiet, true);
+
+	access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+	KUNIT_EXPECT_EQ(test, 2,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
 	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+	KUNIT_EXPECT_EQ(test, 2,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	/* Reverse order - truncate:2 ioctl_dev:0 */
+	deny_mask = 0x02;
+	quiet_optional_accesses = 0;
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE;
+	KUNIT_EXPECT_EQ(test, 2,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+	KUNIT_EXPECT_EQ(test, 0,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+	KUNIT_EXPECT_EQ(test, 2,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	/* layer denying truncate: quiet, ioctl: not quiet */
+	quiet_optional_accesses = 0b01;
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE;
+	KUNIT_EXPECT_EQ(test, 2,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+	KUNIT_EXPECT_EQ(test, quiet, true);
+
+	access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+	KUNIT_EXPECT_EQ(test, 0,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+	KUNIT_EXPECT_EQ(test, 2,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+	KUNIT_EXPECT_EQ(test, quiet, true);
+
+	/* layer denying truncate: not quiet, ioctl: quiet */
+	quiet_optional_accesses = 0b10;
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE;
+	KUNIT_EXPECT_EQ(test, 2,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+	KUNIT_EXPECT_EQ(test, 0,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	KUNIT_EXPECT_EQ(test, quiet, true);
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+	KUNIT_EXPECT_EQ(test, 2,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+	KUNIT_EXPECT_EQ(test, quiet, false);
 
 	/* truncate:15 ioctl_dev:15 */
 	deny_mask = 0xff;
+	quiet_optional_accesses = 0;
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE;
+	KUNIT_EXPECT_EQ(test, 15,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+	KUNIT_EXPECT_EQ(test, 15,
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
+	KUNIT_EXPECT_EQ(test, access,
+			LANDLOCK_ACCESS_FS_TRUNCATE |
+				LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	KUNIT_EXPECT_EQ(test, quiet, false);
+
+	/* Both quiet (same layer so quietness must be the same) */
+	quiet_optional_accesses = 0b11;
 
 	access = LANDLOCK_ACCESS_FS_TRUNCATE;
 	KUNIT_EXPECT_EQ(test, 15,
-			get_layer_from_deny_masks(&access,
-						  _LANDLOCK_ACCESS_FS_OPTIONAL,
-						  deny_mask));
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
 	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+	KUNIT_EXPECT_EQ(test, quiet, true);
 
 	access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
 	KUNIT_EXPECT_EQ(test, 15,
-			get_layer_from_deny_masks(&access,
-						  _LANDLOCK_ACCESS_FS_OPTIONAL,
-						  deny_mask));
+			get_layer_from_deny_masks(
+				&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+				deny_mask, quiet_optional_accesses, &quiet));
 	KUNIT_EXPECT_EQ(test, access,
 			LANDLOCK_ACCESS_FS_TRUNCATE |
 				LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	KUNIT_EXPECT_EQ(test, quiet, true);
 }
 
 #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
@@ -349,11 +511,34 @@ static bool is_valid_request(const struct landlock_request *const request)
 	if (request->deny_masks) {
 		if (WARN_ON_ONCE(!request->all_existing_optional_access))
 			return false;
+		static_assert(sizeof(request->all_existing_optional_access) ==
+			      sizeof(u32));
+		if (WARN_ON_ONCE(
+			    request->quiet_optional_accesses >=
+			    BIT(hweight32(
+				    request->all_existing_optional_access))))
+			return false;
 	}
 
 	return true;
 }
 
+static access_mask_t
+pick_access_mask_for_request_type(const enum landlock_request_type type,
+				  const struct access_masks access_masks)
+{
+	switch (type) {
+	case LANDLOCK_REQUEST_FS_ACCESS:
+		return access_masks.fs;
+	case LANDLOCK_REQUEST_NET_ACCESS:
+		return access_masks.net;
+	default:
+		WARN_ONCE(1, "Invalid request type %d passed to %s", type,
+			  __func__);
+		return 0;
+	}
+}
+
 /**
  * landlock_log_denial - Create audit records related to a denial
  *
@@ -367,6 +552,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
 	struct landlock_hierarchy *youngest_denied;
 	size_t youngest_layer;
 	access_mask_t missing;
+	bool object_quiet_flag = false, quiet_applicable_to_access = false;
 
 	if (WARN_ON_ONCE(!subject || !subject->domain ||
 			 !subject->domain->hierarchy || !request))
@@ -382,10 +568,15 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
 			youngest_layer = get_denied_layer(subject->domain,
 							  &missing,
 							  request->layer_masks);
+			object_quiet_flag =
+				request->layer_masks->layers[youngest_layer]
+					.quiet;
 		} else {
 			youngest_layer = get_layer_from_deny_masks(
 				&missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
-				request->deny_masks);
+				request->deny_masks,
+				request->quiet_optional_accesses,
+				&object_quiet_flag);
 		}
 		youngest_denied =
 			get_hierarchy(subject->domain, youngest_layer);
@@ -420,6 +611,53 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
 			return;
 	}
 
+	/*
+	 * Checks if the object is marked quiet by the layer that denied the
+	 * request.  If it's a different layer that marked it as quiet, but
+	 * that layer is not the one that denied the request, we should still
+	 * audit log the denial.
+	 */
+	if (object_quiet_flag) {
+		/*
+		 * We now check if the denied requests are all covered by the
+		 * layer's quiet access bits.
+		 */
+		const access_mask_t quiet_mask =
+			pick_access_mask_for_request_type(
+				request->type, youngest_denied->quiet_masks);
+
+		quiet_applicable_to_access = (quiet_mask & missing) == missing;
+	} else {
+		/*
+		 * Either the object is not quiet, or this is a scope request.  We
+		 * check request->type to distinguish between the two cases.
+		 */
+		const access_mask_t quiet_mask =
+			youngest_denied->quiet_masks.scope;
+
+		switch (request->type) {
+		case LANDLOCK_REQUEST_SCOPE_SIGNAL:
+			quiet_applicable_to_access =
+				!!(quiet_mask & LANDLOCK_SCOPE_SIGNAL);
+			break;
+		case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
+			quiet_applicable_to_access =
+				!!(quiet_mask &
+				   LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+			break;
+		/*
+		 * Leave LANDLOCK_REQUEST_PTRACE and
+		 * LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY unhandled for now - they are
+		 * never quiet.
+		 */
+		default:
+			break;
+		}
+	}
+
+	if (quiet_applicable_to_access)
+		return;
+
 	/* Uses consistent allocation flags wrt common_lsm_audit(). */
 	ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
 			     AUDIT_LANDLOCK_ACCESS);
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index b85d752273ac..620f8a24291d 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -48,6 +48,7 @@ struct landlock_request {
 	/* Required fields for requests with deny masks. */
 	const access_mask_t all_existing_optional_access;
 	deny_masks_t deny_masks;
+	optional_access_t quiet_optional_accesses;
 };
 
 #ifdef CONFIG_AUDIT
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index d1a4d8b33ee1..6f1cff739ae8 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -157,6 +157,44 @@ get_layer_deny_mask(const access_mask_t all_existing_optional_access,
 	       << ((access_weight - 1) * HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1));
 }
 
+/**
+ * landlock_get_quiet_optional_accesses - Get optional accesses which are
+ *                                        covered by quiet rule flags.
+ *
+ * @all_existing_optional_access: Bitmask of valid optional accesses.
+ * @deny_masks: Domain layer levels that denied each optional access (the
+ *              deny_masks field on struct landlock_file_security).
+ * @masks: The struct layer_masks collected during the path walk.
+ *
+ * Return: a bitmask of which optional accesses are denied by layers for
+ * which the quiet flag was collected during the path walk.
+ */
+optional_access_t landlock_get_quiet_optional_accesses(
+	const access_mask_t all_existing_optional_access,
+	const deny_masks_t deny_masks, const struct layer_masks *const masks)
+{
+	const unsigned long access_opt = all_existing_optional_access;
+	size_t access_index = 0;
+	unsigned long access_bit;
+	optional_access_t quiet_optional_accesses = 0;
+
+	/* This will require change with new object types. */
+	WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
+
+	for_each_set_bit(access_bit, &access_opt,
+			 BITS_PER_TYPE(access_mask_t)) {
+		const u8 layer =
+			(deny_masks >> (access_index *
+					HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
+			(LANDLOCK_MAX_NUM_LAYERS - 1);
+
+		if (masks->layers[layer].quiet)
+			quiet_optional_accesses |= BIT(access_index);
+		access_index++;
+	}
+	return quiet_optional_accesses;
+}
+
 #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
 
 static void test_get_layer_deny_mask(struct kunit *const test)
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 9f560f3c3bd1..2a1660e3dea7 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -126,6 +126,10 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
 			const access_mask_t optional_access,
 			const struct layer_masks *const masks);
 
+optional_access_t landlock_get_quiet_optional_accesses(
+	const access_mask_t all_existing_optional_access,
+	const deny_masks_t deny_masks, const struct layer_masks *const masks);
+
 int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy);
 
 static inline void
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index a096e4aa7fcd..ccb2fe4fa056 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1805,6 +1805,10 @@ static int hook_file_open(struct file *const file)
 #ifdef CONFIG_AUDIT
 	landlock_file(file)->deny_masks = landlock_get_deny_masks(
 		_LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks);
+	landlock_file(file)->quiet_optional_accesses =
+		landlock_get_quiet_optional_accesses(
+			_LANDLOCK_ACCESS_FS_OPTIONAL,
+			landlock_file(file)->deny_masks, &layer_masks);
 #endif /* CONFIG_AUDIT */
 
 	if (access_mask_subset(open_access_request, allowed_access))
@@ -1841,6 +1845,7 @@ static int hook_file_truncate(struct file *const file)
 		.access = LANDLOCK_ACCESS_FS_TRUNCATE,
 #ifdef CONFIG_AUDIT
 		.deny_masks = landlock_file(file)->deny_masks,
+		.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
 #endif /* CONFIG_AUDIT */
 	});
 	return -EACCES;
@@ -1880,6 +1885,7 @@ static int hook_file_ioctl_common(const struct file *const file,
 		.access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
 #ifdef CONFIG_AUDIT
 		.deny_masks = landlock_file(file)->deny_masks,
+		.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
 #endif /* CONFIG_AUDIT */
 	});
 	return -EACCES;
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index e4c530511360..7efe9b172acf 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -63,6 +63,14 @@ struct landlock_file_security {
 	 * _LANDLOCK_ACCESS_FS_OPTIONAL).
 	 */
 	deny_masks_t deny_masks;
+	/**
+	 * @quiet_optional_accesses: Stores which optional accesses are
+	 * covered by quiet rules within the layer referred to in deny_masks,
+	 * one access per bit.  Does not take into account whether the quiet
+	 * access bits are actually set in the layer's corresponding
+	 * landlock_hierarchy.
+	 */
+	optional_access_t quiet_optional_accesses;
 	/**
 	 * @fown_layer: Layer level of @fown_subject->domain with
 	 * LANDLOCK_SCOPE_SIGNAL.
@@ -91,13 +99,18 @@ struct landlock_file_security {
 };
 
 #ifdef CONFIG_AUDIT
-
 /* Makes sure all layers can be identified. */
 /* clang-format off */
 static_assert((typeof_member(struct landlock_file_security, fown_layer))~0 >=
 	      LANDLOCK_MAX_NUM_LAYERS);
-/* clang-format off */
-
+/* clang-format on */
+/*
+ * Make sure quiet_optional_accesses has enough bits to cover all optional
+ * accesses.
+ */
+static_assert(BITS_PER_TYPE(typeof_member(struct landlock_file_security,
+					  quiet_optional_accesses)) >=
+	      HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL));
 #endif /* CONFIG_AUDIT */
 
 /**
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 4b4f974dc877..51050dd39a3a 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -250,14 +250,13 @@ static int current_check_access_socket(struct socket *const sock,
 
 	audit_net.family = address->sa_family;
 	audit_net.sk = sock->sk;
-	landlock_log_denial(subject,
-			    &(struct landlock_request){
-				    .type = LANDLOCK_REQUEST_NET_ACCESS,
-				    .audit.type = LSM_AUDIT_DATA_NET,
-				    .audit.u.net = &audit_net,
-				    .access = access_request,
-				    .layer_masks = &layer_masks,
-			    });
+	landlock_log_denial(
+		subject,
+		&(struct landlock_request){ .type = LANDLOCK_REQUEST_NET_ACCESS,
+					    .audit.type = LSM_AUDIT_DATA_NET,
+					    .audit.u.net = &audit_net,
+					    .access = access_request,
+					    .layer_masks = &layer_masks });
 	return -EACCES;
 }
 
-- 
2.54.0

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox