linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 00/30] Landlock audit support
@ 2025-01-08 15:43 Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 01/30] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set Mickaël Salaün
                   ` (29 more replies)
  0 siblings, 30 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Hi,

This patch series adds audit support to Landlock.

Logging denied requests is useful for different use cases:
- sysadmins: to look for users' issues,
- security experts: to detect attack attempts,
- power users: to understand denials,
- developers: to ease sandboxing support and get feedback from users.

Because of its unprivileged nature, Landlock can compose standalone
security policies (i.e. domains).  To make logs useful, they need to
contain the most relevant Landlock domain that denied an action, and the
reason of such denial.  This translates to the latest nested domain and
the related blockers: missing access rights or other kind of
restrictions.

# Changes from previous version

This fourth patch series mainly adds a new AUDIT_EXE_LANDLOCK_DENY rule
type to filter Landlock denials according to the executable that loaded
the policy responsible for this restriction.  New tests are added on top
of that.

Domain's metadata are now stored in a dedicated struct landlock_details
that contains the resolved exe's path, because we cannot keep a
reference to the related struct path.  This fixes umount of the
mount point containing a binary that restricted itself (if the domain is
still alive).  Add a dedicated test to check this issue.

Formatting of blockers are slightly improved.

Audit timestamps are no longer exported but dedicated Landlock
timestamps are use instead for domain creation.

The new landlock_restrict_self()'s flag is renamed to
LANDLOCK_RESTRICT_SELF_QUIET.

# Design

Log records are created for any denied actions caused by a Landlock
policy, which means that a well-sandboxed applications should not log
anything except for unattended access requests that might be the result
of attacks or bugs.

However, sandbox tools creating restricted environments could lead to
abundant log entries because the sandboxed processes may not be aware of
the related restrictions.  To avoid log spam, the
landlock_restrict_self(2) syscall gets a new
LANDLOCK_RESTRICT_SELF_QUIET flag to not log denials related to this
specific domain.  Except for well-understood exceptions, this flag
should not be set.  Indeed, applications sandboxing themselves should
only try to bypass their own sandbox if they are compromised, which
should ring a bell thanks to log events.

When an action is denied, the related Landlock domain ID is specified.
If this domain was not previously described in a log record, one is
created.  This record contains the domain ID, its creation time, and
informations about the process that enforced the restriction (at the
time of the call to landlock_restrict_self): PID, UID, executable path,
and name (comm).

This new approach also brings building blocks for an upcoming
unprivileged introspection interface.  The unique Landlock IDs will be
useful to tie audit log entries to running processes, and to get
properties of the related Landlock domains.  This will replace the
previously logged ruleset properties.

# Samples

Here are two examples of log events (see serial numbers):

$ LL_FS_RO=/ LL_FS_RW=/ LL_SCOPED=s LL_FORCE_LOG=1 ./sandboxer kill 1

  type=LANDLOCK_DENY msg=audit(1729738800.268:30): domain=1a6fdc66f blockers=scope.signal opid=1 ocomm="systemd"
  type=LANDLOCK_DOM_INFO msg=audit(1729738800.268:30): domain=1a6fdc66f creation=1729738800.264 pid=286 uid=0 exe="/root/sandboxer" comm="sandboxer"UID="root"
  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_DOM_DROP msg=audit(1729738800.324:31): domain=1a6fdc66f denials=1

$ LL_FS_RO=/ LL_FS_RW=/tmp LL_FORCE_LOG=1 ./sandboxer sh -c "echo > /etc/passwd"

  type=LANDLOCK_DENY msg=audit(1729738800.221:33): domain=1a6fdc679 blockers=fs.write_file path="/dev/tty" dev="devtmpfs" ino=9
  type=LANDLOCK_DOM_INFO msg=audit(1729738800.221:33): domain=1a6fdc679 creation=1729738800.217 pid=289 uid=0 exe="/root/sandboxer" comm="sandboxer"UID="root"
  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_DENY 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_DOM_DROP msg=audit(1729738800.261:35): domain=1a6fdc679 denials=2

# Future changes

I'll add more tests to check each kind of denied access.

We might want to add new audit rule types to filter according to other
domain properties (e.g. UID, AUID, session ID), but
AUDIT_EXE_LANDLOCK_DENY should be enough to mute buggy programs before
fixing them.

# Previous versions

v3: https://lore.kernel.org/r/20241122143353.59367-1-mic@digikod.net
v2: https://lore.kernel.org/r/20241022161009.982584-1-mic@digikod.net
v1: https://lore.kernel.org/r/20230921061641.273654-1-mic@digikod.net

Regards,

Mickaël Salaün (30):
  lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are
    set
  lsm: Add audit_log_lsm_data() helper
  landlock: Factor out check_access_path()
  landlock: Add unique ID generator
  landlock: Move access types
  landlock: Simplify initially denied access rights
  landlock: Move domain hierarchy management and export helpers
  landlock: Add AUDIT_LANDLOCK_DENY and log ptrace denials
  landlock: Add AUDIT_LANDLOCK_DOM_{INFO,DROP} and log domain properties
  landlock: Log mount-related denials
  landlock: Align partial refer access checks with final ones
  selftests/landlock: Add test to check partial access in a mount tree
  landlock: Optimize file path walks and prepare for audit support
  landlock: Log file-related denials
  landlock: Log truncate and IOCTL denials
  landlock: Log TCP bind and connect denials
  landlock: Log scoped denials
  landlock: Control log events with LANDLOCK_RESTRICT_SELF_QUIET
  samples/landlock: Do not log denials from the sandboxer by default
  selftests/landlock: Fix error message
  selftests/landlock: Add wrappers.h
  selftests/landlock: Add layout1.umount_sandboxer tests
  selftests/landlock: Extend tests for landlock_restrict_self()'s flags
  selftests/landlock: Add tests for audit and
    LANDLOCK_RESTRICT_SELF_QUIET
  selftests/landlock: Add audit tests for ptrace
  landlock: Export and rename landlock_get_inode_object()
  fs: Add iput() cleanup helper
  audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type
  selftests/landlock: Test audit rule with AUDIT_EXE_LANDLOCK_DOM
  selftests/landlock: Test compatibility with audit rule lists

 Documentation/userspace-api/landlock.rst      |   2 +-
 MAINTAINERS                                   |   1 +
 include/linux/audit.h                         |  11 +
 include/linux/fs.h                            |   6 +-
 include/linux/landlock.h                      |  41 ++
 include/linux/lsm_audit.h                     |  22 +
 include/uapi/linux/audit.h                    |   6 +-
 include/uapi/linux/landlock.h                 |  14 +
 kernel/audit.c                                |   4 +-
 kernel/audit.h                                |   5 +-
 kernel/auditfilter.c                          |  30 +-
 kernel/auditsc.c                              |  31 ++
 samples/landlock/sandboxer.c                  |  35 +-
 security/Kconfig                              |   5 +
 security/Makefile                             |   2 +-
 security/landlock/.kunitconfig                |   2 +
 security/landlock/Makefile                    |  15 +-
 security/landlock/access.h                    | 100 ++++
 security/landlock/audit.c                     | 510 ++++++++++++++++++
 security/landlock/audit.h                     |  76 +++
 security/landlock/domain.c                    | 339 ++++++++++++
 security/landlock/domain.h                    | 145 +++++
 security/landlock/fs.c                        | 305 ++++++++---
 security/landlock/fs.h                        |  12 +
 security/landlock/id.c                        | 249 +++++++++
 security/landlock/id.h                        |  25 +
 security/landlock/net.c                       |  51 +-
 security/landlock/object.h                    |   4 +-
 security/landlock/ruleset.c                   |  38 +-
 security/landlock/ruleset.h                   |  95 ++--
 security/landlock/setup.c                     |   2 +
 security/landlock/syscalls.c                  |  28 +-
 security/landlock/task.c                      | 152 +++++-
 security/lsm_audit.c                          |  27 +-
 tools/testing/kunit/configs/all_tests.config  |   2 +
 tools/testing/selftests/landlock/Makefile     |   2 +-
 tools/testing/selftests/landlock/audit.h      | 371 +++++++++++++
 tools/testing/selftests/landlock/audit_test.c | 389 +++++++++++++
 tools/testing/selftests/landlock/base_test.c  |  19 +-
 tools/testing/selftests/landlock/common.h     |  40 +-
 tools/testing/selftests/landlock/config       |   1 +
 tools/testing/selftests/landlock/fs_test.c    | 151 +++++-
 .../testing/selftests/landlock/ptrace_test.c  |  67 ++-
 .../selftests/landlock/sandbox-and-launch.c   |  82 +++
 tools/testing/selftests/landlock/wait-pipe.c  |  70 +++
 tools/testing/selftests/landlock/wrappers.h   |  47 ++
 46 files changed, 3360 insertions(+), 271 deletions(-)
 create mode 100644 include/linux/landlock.h
 create mode 100644 security/landlock/access.h
 create mode 100644 security/landlock/audit.c
 create mode 100644 security/landlock/audit.h
 create mode 100644 security/landlock/domain.c
 create mode 100644 security/landlock/domain.h
 create mode 100644 security/landlock/id.c
 create mode 100644 security/landlock/id.h
 create mode 100644 tools/testing/selftests/landlock/audit.h
 create mode 100644 tools/testing/selftests/landlock/audit_test.c
 create mode 100644 tools/testing/selftests/landlock/sandbox-and-launch.c
 create mode 100644 tools/testing/selftests/landlock/wait-pipe.c
 create mode 100644 tools/testing/selftests/landlock/wrappers.h


base-commit: 9d89551994a430b50c4fffcb1e617a057fa76e20
-- 
2.47.1


^ permalink raw reply	[flat|nested] 58+ messages in thread

* [PATCH v4 01/30] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 02/30] lsm: Add audit_log_lsm_data() helper Mickaël Salaün
                   ` (28 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

When CONFIG_AUDIT is set, its CONFIG_NET dependency is also set, and the
dev_get_by_index and init_net symbols (used by dump_common_audit_data)
are found by the linker.  dump_common_audit_data() should then failed to
build when CONFIG_NET is not set. However, because the compiler is
smart, it knows that audit_log_start() always return NULL when
!CONFIG_AUDIT, and it doesn't build the body of common_lsm_audit().  As
a side effect, dump_common_audit_data() is not built and the linker
doesn't error out because of missing symbols.

Let's only build lsm_audit.o when CONFIG_SECURITY and CONFIG_AUDIT are
both set, which is checked with the new CONFIG_HAS_SECURITY_AUDIT.

ipv4_skb_to_auditdata() and ipv6_skb_to_auditdata() are only used by
Smack if CONFIG_AUDIT is set, so they don't need fake implementations.

Because common_lsm_audit() is used in multiple places without
CONFIG_AUDIT checks, add a fake implementation.

Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: James Morris <jmorris@namei.org>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Serge E. Hallyn <serge@hallyn.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-2-mic@digikod.net
---

Merged in the LSM's next tree.  It will be part of Linux v6.13:
https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git/commit/?h=next&id=7ccbe076d987598b04b4b9c9b61f042291f9cc77

Changes since v2:
- Add CONFIG_HAS_SECURITY_AUDIT to fix the build with AUDIT &&
  !SECURITY, reported by Guenter Roeck.
---
 include/linux/lsm_audit.h | 14 ++++++++++++++
 security/Kconfig          |  5 +++++
 security/Makefile         |  2 +-
 3 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 97a8b21eb033..c2b01380262c 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -116,14 +116,28 @@ struct common_audit_data {
 #define v4info fam.v4
 #define v6info fam.v6
 
+#ifdef CONFIG_AUDIT
+
 int ipv4_skb_to_auditdata(struct sk_buff *skb,
 		struct common_audit_data *ad, u8 *proto);
 
+#if IS_ENABLED(CONFIG_IPV6)
 int ipv6_skb_to_auditdata(struct sk_buff *skb,
 		struct common_audit_data *ad, u8 *proto);
+#endif /* IS_ENABLED(CONFIG_IPV6) */
 
 void common_lsm_audit(struct common_audit_data *a,
 	void (*pre_audit)(struct audit_buffer *, void *),
 	void (*post_audit)(struct audit_buffer *, void *));
 
+#else /* CONFIG_AUDIT */
+
+static inline void common_lsm_audit(struct common_audit_data *a,
+	void (*pre_audit)(struct audit_buffer *, void *),
+	void (*post_audit)(struct audit_buffer *, void *))
+{
+}
+
+#endif /* CONFIG_AUDIT */
+
 #endif
diff --git a/security/Kconfig b/security/Kconfig
index 28e685f53bd1..f10dbf15c294 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -64,6 +64,11 @@ config SECURITY
 
 	  If you are unsure how to answer this question, answer N.
 
+config HAS_SECURITY_AUDIT
+	def_bool y
+	depends on AUDIT
+	depends on SECURITY
+
 config SECURITYFS
 	bool "Enable the securityfs filesystem"
 	help
diff --git a/security/Makefile b/security/Makefile
index cc0982214b84..22ff4c8bd8ce 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -15,7 +15,7 @@ obj-$(CONFIG_SECURITY)			+= security.o
 obj-$(CONFIG_SECURITYFS)		+= inode.o
 obj-$(CONFIG_SECURITY_SELINUX)		+= selinux/
 obj-$(CONFIG_SECURITY_SMACK)		+= smack/
-obj-$(CONFIG_SECURITY)			+= lsm_audit.o
+obj-$(CONFIG_HAS_SECURITY_AUDIT)	+= lsm_audit.o
 obj-$(CONFIG_SECURITY_TOMOYO)		+= tomoyo/
 obj-$(CONFIG_SECURITY_APPARMOR)		+= apparmor/
 obj-$(CONFIG_SECURITY_YAMA)		+= yama/
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 02/30] lsm: Add audit_log_lsm_data() helper
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 01/30] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 03/30] landlock: Factor out check_access_path() Mickaël Salaün
                   ` (27 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Extract code from dump_common_audit_data() into the audit_log_lsm_data()
helper. This helps reuse common LSM audit data while not abusing
AUDIT_AVC records because of the common_lsm_audit() helper.

Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: James Morris <jmorris@namei.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
Acked-by: Paul Moore <paul@paul-moore.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-3-mic@digikod.net
---

Changes since v3:
- Rebase on top of the v6.13's get_task_comm() fix.
- Add Acked-by Paul.

Changes since v1:
- Fix commit message (spotted by Paul).
- Constify dump_common_audit_data()'s and audit_log_lsm_data()'s "a"
  argument.
- Fix build without CONFIG_NET: see previous patch.
---
 include/linux/lsm_audit.h |  8 ++++++++
 security/lsm_audit.c      | 27 ++++++++++++++++++---------
 2 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index c2b01380262c..b62769a7c5fa 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -130,6 +130,9 @@ void common_lsm_audit(struct common_audit_data *a,
 	void (*pre_audit)(struct audit_buffer *, void *),
 	void (*post_audit)(struct audit_buffer *, void *));
 
+void audit_log_lsm_data(struct audit_buffer *ab,
+			const struct common_audit_data *a);
+
 #else /* CONFIG_AUDIT */
 
 static inline void common_lsm_audit(struct common_audit_data *a,
@@ -138,6 +141,11 @@ static inline void common_lsm_audit(struct common_audit_data *a,
 {
 }
 
+static inline void audit_log_lsm_data(struct audit_buffer *ab,
+			const struct common_audit_data *a)
+{
+}
+
 #endif /* CONFIG_AUDIT */
 
 #endif
diff --git a/security/lsm_audit.c b/security/lsm_audit.c
index 9a8352972086..0060b9275f49 100644
--- a/security/lsm_audit.c
+++ b/security/lsm_audit.c
@@ -189,16 +189,13 @@ static inline void print_ipv4_addr(struct audit_buffer *ab, __be32 addr,
 }
 
 /**
- * dump_common_audit_data - helper to dump common audit data
+ * audit_log_lsm_data - helper to log common LSM audit data
  * @ab : the audit buffer
  * @a : common audit data
- *
  */
-static void dump_common_audit_data(struct audit_buffer *ab,
-				   struct common_audit_data *a)
+void audit_log_lsm_data(struct audit_buffer *ab,
+			const struct common_audit_data *a)
 {
-	char comm[sizeof(current->comm)];
-
 	/*
 	 * To keep stack sizes in check force programmers to notice if they
 	 * start making this union too large!  See struct lsm_network_audit
@@ -206,9 +203,6 @@ static void dump_common_audit_data(struct audit_buffer *ab,
 	 */
 	BUILD_BUG_ON(sizeof(a->u) > sizeof(void *)*2);
 
-	audit_log_format(ab, " pid=%d comm=", task_tgid_nr(current));
-	audit_log_untrustedstring(ab, get_task_comm(comm, current));
-
 	switch (a->type) {
 	case LSM_AUDIT_DATA_NONE:
 		return;
@@ -428,6 +422,21 @@ static void dump_common_audit_data(struct audit_buffer *ab,
 	} /* switch (a->type) */
 }
 
+/**
+ * dump_common_audit_data - helper to dump common audit data
+ * @ab : the audit buffer
+ * @a : common audit data
+ */
+static void dump_common_audit_data(struct audit_buffer *ab,
+				   const struct common_audit_data *a)
+{
+	char comm[sizeof(current->comm)];
+
+	audit_log_format(ab, " pid=%d comm=", task_tgid_nr(current));
+	audit_log_untrustedstring(ab, get_task_comm(comm, current));
+	audit_log_lsm_data(ab, a);
+}
+
 /**
  * common_lsm_audit - generic LSM auditing function
  * @a:  auxiliary audit data
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 03/30] landlock: Factor out check_access_path()
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 01/30] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 02/30] lsm: Add audit_log_lsm_data() helper Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-10 11:23   ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 04/30] landlock: Add unique ID generator Mickaël Salaün
                   ` (26 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Merge check_access_path() into current_check_access_path() and make
hook_path_mknod() use it.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-4-mic@digikod.net
---

Changes since v1:
- Rebased on the TCP patch series.
- Remove inlining removal which was merged.
---
 security/landlock/fs.c | 32 +++++++++++---------------------
 1 file changed, 11 insertions(+), 21 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index e31b97a9f175..d911c924843f 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -908,28 +908,22 @@ static bool is_access_to_paths_allowed(
 	return allowed_parent1 && allowed_parent2;
 }
 
-static int check_access_path(const struct landlock_ruleset *const domain,
-			     const struct path *const path,
-			     access_mask_t access_request)
-{
-	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
-
-	access_request = landlock_init_layer_masks(
-		domain, access_request, &layer_masks, LANDLOCK_KEY_INODE);
-	if (is_access_to_paths_allowed(domain, path, access_request,
-				       &layer_masks, NULL, 0, NULL, NULL))
-		return 0;
-	return -EACCES;
-}
-
 static int current_check_access_path(const struct path *const path,
-				     const access_mask_t access_request)
+				     access_mask_t access_request)
 {
 	const struct landlock_ruleset *const dom = get_current_fs_domain();
+	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
 
 	if (!dom)
 		return 0;
-	return check_access_path(dom, path, access_request);
+
+	access_request = landlock_init_layer_masks(
+		dom, access_request, &layer_masks, LANDLOCK_KEY_INODE);
+	if (is_access_to_paths_allowed(dom, path, access_request, &layer_masks,
+				       NULL, 0, NULL, NULL))
+		return 0;
+
+	return -EACCES;
 }
 
 static access_mask_t get_mode_access(const umode_t mode)
@@ -1414,11 +1408,7 @@ static int hook_path_mknod(const struct path *const dir,
 			   struct dentry *const dentry, const umode_t mode,
 			   const unsigned int dev)
 {
-	const struct landlock_ruleset *const dom = get_current_fs_domain();
-
-	if (!dom)
-		return 0;
-	return check_access_path(dom, dir, get_mode_access(mode));
+	return current_check_access_path(dir, get_mode_access(mode));
 }
 
 static int hook_path_symlink(const struct path *const dir,
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 04/30] landlock: Add unique ID generator
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (2 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 03/30] landlock: Factor out check_access_path() Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 05/30] landlock: Move access types Mickaël Salaün
                   ` (25 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Landlock IDs can be generated to uniquely identify Landlock objects.
For now, only Landlock domains get an ID at creation time.  These IDs
map to immutable domain hierarchies.

Landlock IDs have important properties:
- They are unique during the lifetime of the running system thanks to
  the 64-bit values: at worse, 2^60 - 2*2^32 useful IDs.
- They are always greater than 2^32 and must then be stored in 64-bit
  integer types.
- The initial ID (at boot time) is randomly picked between 2^32 and
  2^33, which limits collisions in logs between different boots.
- IDs are sequential, which enables users to order them.
- IDs may not be consecutive but increase with a random 2^4 step, which
  limits side channels.

Such IDs can be exposed to unprivileged processes, even if it is not the
case with this audit patch series.  The domain IDs will be useful for
user space to identify sandboxes and get their properties.

These Landlock IDs are more robust that other absolute kernel IDs such
as pipe's inodes which rely on a shared global counter.

For checkpoint/restore features (i.e. CRIU), we could easily implement a
privileged interface (e.g. sysfs) to set the next ID counter.

IDR/IDA are not used because we only need a bijection from Landlock
objects to Landlock IDs, and we must not recycle IDs.  This enables us
to identify all Landlock objects during the lifetime of the system (e.g.
in logs), but not to access an object from an ID nor know if an ID is
assigned.   Using a counter is simpler, it scales (i.e. avoids growing
memory footprint), and it does not require locking.  We'll use proper
file descriptors (with IDs used as inode numbers) to access Landlock
objects.

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

Changes since v3:
- Rename landlock_get_id_range() helper to reflect the "range" of IDs.
- Add docstring for landlock_get_id_range().

Changes since v2:
- Extend commit message.
- Rename global_counter to next_id.
- Fix KUnit's test __init types, spotted by kernel test robot.

Changes since v1:
- New patch.
---
 security/landlock/.kunitconfig               |   2 +
 security/landlock/Makefile                   |   2 +
 security/landlock/id.c                       | 249 +++++++++++++++++++
 security/landlock/id.h                       |  25 ++
 security/landlock/setup.c                    |   2 +
 tools/testing/kunit/configs/all_tests.config |   2 +
 6 files changed, 282 insertions(+)
 create mode 100644 security/landlock/id.c
 create mode 100644 security/landlock/id.h

diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig
index 03e119466604..f9423f01ac5b 100644
--- a/security/landlock/.kunitconfig
+++ b/security/landlock/.kunitconfig
@@ -1,4 +1,6 @@
+CONFIG_AUDIT=y
 CONFIG_KUNIT=y
+CONFIG_NET=y
 CONFIG_SECURITY=y
 CONFIG_SECURITY_LANDLOCK=y
 CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index b4538b7cf7d2..e1777abbc413 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -4,3 +4,5 @@ landlock-y := setup.o syscalls.o object.o ruleset.o \
 	cred.o task.o fs.o
 
 landlock-$(CONFIG_INET) += net.o
+
+landlock-$(CONFIG_AUDIT) += id.o
diff --git a/security/landlock/id.c b/security/landlock/id.c
new file mode 100644
index 000000000000..b3c353d6e0cd
--- /dev/null
+++ b/security/landlock/id.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Unique identification number generator
+ *
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#include <kunit/test.h>
+#include <linux/atomic.h>
+#include <linux/random.h>
+#include <linux/spinlock.h>
+
+#include "common.h"
+#include "id.h"
+
+#define COUNTER_PRE_INIT 0
+
+static atomic64_t next_id = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+static void __init init_id(atomic64_t *const counter, const u32 random_32bits)
+{
+	u64 init;
+
+	/*
+	 * Ensures sure 64-bit values are always used by user space (or may
+	 * fail with -EOVERFLOW), and makes this testable.
+	 */
+	init = 1ULL << 32;
+
+	/*
+	 * Makes a large (2^32) boot-time value to limit ID collision in logs
+	 * from different boots, and to limit info leak about the number of
+	 * initially (relative to the reader) created elements (e.g. domains).
+	 */
+	init += random_32bits;
+
+	/* Sets first or ignores.  This will be the first ID. */
+	atomic64_cmpxchg(counter, COUNTER_PRE_INIT, init);
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void __init test_init_min(struct kunit *const test)
+{
+	atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+	init_id(&counter, 0);
+	KUNIT_EXPECT_EQ(test, atomic64_read(&counter), 1ULL + U32_MAX);
+}
+
+static void __init test_init_max(struct kunit *const test)
+{
+	atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+	init_id(&counter, ~0);
+	KUNIT_EXPECT_EQ(test, atomic64_read(&counter), 1 + (2ULL * U32_MAX));
+}
+
+static void __init test_init_once(struct kunit *const test)
+{
+	const u64 first_init = 1ULL + U32_MAX;
+	atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+	init_id(&counter, 0);
+	KUNIT_EXPECT_EQ(test, atomic64_read(&counter), first_init);
+
+	init_id(&counter, ~0);
+	KUNIT_EXPECT_EQ(test, atomic64_read(&counter), first_init);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+void __init landlock_init_id(void)
+{
+	return init_id(&next_id, get_random_u32());
+}
+
+/*
+ * It's not worth it to try to hide the monotonic counter because it can still
+ * be inferred (with N counter ranges), and if we are allowed to read the inode
+ * number we should also be allowed to read the time creation anyway, and it
+ * can be handy to store and sort domain IDs for user space.
+ *
+ * Returns the value of next_id and increment it to let some space for the next
+ * one.
+ */
+static u64 get_id_range(size_t number_of_ids, atomic64_t *const counter,
+			u8 random_4bits)
+{
+	u64 id, step;
+
+	/*
+	 * We should return at least 1 ID, and we may need a set of consecutive
+	 * ones (e.g. to generate a set of inodes).
+	 */
+	if (WARN_ON_ONCE(number_of_ids <= 0))
+		number_of_ids = 1;
+
+	/*
+	 * Blurs the next ID guess with 1/16 ratio.  We get 2^(64 - 4) -
+	 * (2 * 2^32), so a bit less than 2^60 available IDs, which should be
+	 * much more than enough considering the number of CPU cycles required
+	 * to get a new ID (e.g. a full landlock_restrict_self() call), and the
+	 * cost of draining all available IDs during the system's uptime.
+	 */
+	random_4bits = random_4bits % (1 << 4);
+	step = number_of_ids + random_4bits;
+
+	/* It is safe to cast a signed atomic to an unsigned value. */
+	id = atomic64_fetch_add(step, counter);
+
+	/* Warns if landlock_init_id() was not called. */
+	WARN_ON_ONCE(id == COUNTER_PRE_INIT);
+	return id;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_range1_rand0(struct kunit *const test)
+{
+	atomic64_t counter;
+	u64 init;
+
+	init = get_random_u32();
+	atomic64_set(&counter, init);
+	KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 0), init);
+	KUNIT_EXPECT_EQ(
+		test, get_id_range(get_random_u8(), &counter, get_random_u8()),
+		init + 1);
+}
+
+static void test_range1_rand1(struct kunit *const test)
+{
+	atomic64_t counter;
+	u64 init;
+
+	init = get_random_u32();
+	atomic64_set(&counter, init);
+	KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 1), init);
+	KUNIT_EXPECT_EQ(
+		test, get_id_range(get_random_u8(), &counter, get_random_u8()),
+		init + 2);
+}
+
+static void test_range1_rand16(struct kunit *const test)
+{
+	atomic64_t counter;
+	u64 init;
+
+	init = get_random_u32();
+	atomic64_set(&counter, init);
+	KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 16), init);
+	KUNIT_EXPECT_EQ(
+		test, get_id_range(get_random_u8(), &counter, get_random_u8()),
+		init + 1);
+}
+
+static void test_range2_rand0(struct kunit *const test)
+{
+	atomic64_t counter;
+	u64 init;
+
+	init = get_random_u32();
+	atomic64_set(&counter, init);
+	KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 0), init);
+	KUNIT_EXPECT_EQ(
+		test, get_id_range(get_random_u8(), &counter, get_random_u8()),
+		init + 2);
+}
+
+static void test_range2_rand1(struct kunit *const test)
+{
+	atomic64_t counter;
+	u64 init;
+
+	init = get_random_u32();
+	atomic64_set(&counter, init);
+	KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 1), init);
+	KUNIT_EXPECT_EQ(
+		test, get_id_range(get_random_u8(), &counter, get_random_u8()),
+		init + 3);
+}
+
+static void test_range2_rand2(struct kunit *const test)
+{
+	atomic64_t counter;
+	u64 init;
+
+	init = get_random_u32();
+	atomic64_set(&counter, init);
+	KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 2), init);
+	KUNIT_EXPECT_EQ(
+		test, get_id_range(get_random_u8(), &counter, get_random_u8()),
+		init + 4);
+}
+
+static void test_range2_rand16(struct kunit *const test)
+{
+	atomic64_t counter;
+	u64 init;
+
+	init = get_random_u32();
+	atomic64_set(&counter, init);
+	KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 16), init);
+	KUNIT_EXPECT_EQ(
+		test, get_id_range(get_random_u8(), &counter, get_random_u8()),
+		init + 2);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+/**
+ * landlock_get_id_range - Get a range of unique IDs
+ *
+ * @number_of_ids: Number of IDs to hold.  Must be greater than one.
+ *
+ * Returns: The first ID in the range.
+ */
+u64 landlock_get_id_range(size_t number_of_ids)
+{
+	return get_id_range(number_of_ids, &next_id, get_random_u8());
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static struct kunit_case __refdata test_cases[] = {
+	/* clang-format off */
+	KUNIT_CASE(test_init_min),
+	KUNIT_CASE(test_init_max),
+	KUNIT_CASE(test_init_once),
+	KUNIT_CASE(test_range1_rand0),
+	KUNIT_CASE(test_range1_rand1),
+	KUNIT_CASE(test_range1_rand16),
+	KUNIT_CASE(test_range2_rand0),
+	KUNIT_CASE(test_range2_rand1),
+	KUNIT_CASE(test_range2_rand2),
+	KUNIT_CASE(test_range2_rand16),
+	{}
+	/* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+	.name = "landlock_id",
+	.test_cases = test_cases,
+};
+
+kunit_test_init_section_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/id.h b/security/landlock/id.h
new file mode 100644
index 000000000000..99f596123c19
--- /dev/null
+++ b/security/landlock/id.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Unique identification number generator
+ *
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_ID_H
+#define _SECURITY_LANDLOCK_ID_H
+
+#ifdef CONFIG_AUDIT
+
+void __init landlock_init_id(void);
+
+u64 landlock_get_id_range(size_t number_of_ids);
+
+#else /* CONFIG_AUDIT */
+
+static inline void __init landlock_init_id(void)
+{
+}
+
+#endif /* CONFIG_AUDIT */
+
+#endif /* _SECURITY_LANDLOCK_ID_H */
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index 28519a45b11f..d297083efcb1 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -13,6 +13,7 @@
 #include "common.h"
 #include "cred.h"
 #include "fs.h"
+#include "id.h"
 #include "net.h"
 #include "setup.h"
 #include "task.h"
@@ -33,6 +34,7 @@ const struct lsm_id landlock_lsmid = {
 
 static int __init landlock_init(void)
 {
+	landlock_init_id();
 	landlock_add_cred_hooks();
 	landlock_add_task_hooks();
 	landlock_add_fs_hooks();
diff --git a/tools/testing/kunit/configs/all_tests.config b/tools/testing/kunit/configs/all_tests.config
index b3b00269a52a..ea1f824ae70f 100644
--- a/tools/testing/kunit/configs/all_tests.config
+++ b/tools/testing/kunit/configs/all_tests.config
@@ -44,6 +44,8 @@ CONFIG_DAMON_DBGFS_DEPRECATED=y
 
 CONFIG_REGMAP_BUILD=y
 
+CONFIG_AUDIT=y
+
 CONFIG_SECURITY=y
 CONFIG_SECURITY_APPARMOR=y
 CONFIG_SECURITY_LANDLOCK=y
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 05/30] landlock: Move access types
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (3 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 04/30] landlock: Add unique ID generator Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-10 11:23   ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 06/30] landlock: Simplify initially denied access rights Mickaël Salaün
                   ` (24 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Move LANDLOCK_ACCESS_FS_INITIALLY_DENIED, access_mask_t, struct
access_mask, and struct access_masks_all to a dedicated access.h file.

Rename LANDLOCK_ACCESS_FS_INITIALLY_DENIED to
_LANDLOCK_ACCESS_FS_INITIALLY_DENIED to make it clear that it's not part
of UAPI.  Add some newlines when appropriate.

This file will be extended with following commits, and it will help to
avoid dependency loops.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-6-mic@digikod.net
---

Changes since v2:
- Rebased on the (now merged) masks improvement patches.
- Move ACCESS_FS_OPTIONAL to a following patch introducing deny_masks_t,
  spotted by Francis Laniel.
- Move and rename LANDLOCK_ACCESS_FS_INITIALLY_DENIED to
  _LANDLOCK_ACCESS_FS_INITIALLY_DENIED.

Changes since v1:
- New patch
---
 security/landlock/access.h  | 62 +++++++++++++++++++++++++++++++++++++
 security/landlock/fs.c      |  3 +-
 security/landlock/fs.h      |  1 +
 security/landlock/ruleset.c |  1 +
 security/landlock/ruleset.h | 47 ++--------------------------
 5 files changed, 68 insertions(+), 46 deletions(-)
 create mode 100644 security/landlock/access.h

diff --git a/security/landlock/access.h b/security/landlock/access.h
new file mode 100644
index 000000000000..9ee4b30a87e6
--- /dev/null
+++ b/security/landlock/access.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Access types and helpers
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_ACCESS_H
+#define _SECURITY_LANDLOCK_ACCESS_H
+
+#include <linux/bitops.h>
+#include <linux/build_bug.h>
+#include <linux/kernel.h>
+#include <uapi/linux/landlock.h>
+
+#include "limits.h"
+
+/*
+ * All access rights that are denied by default whether they are handled or not
+ * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
+ * entries when we need to get the absolute handled access masks.
+ */
+/* clang-format off */
+#define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
+	LANDLOCK_ACCESS_FS_REFER)
+/* clang-format on */
+
+typedef u16 access_mask_t;
+
+/* Makes sure all filesystem access rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
+/* Makes sure all network access rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
+/* Makes sure all scoped rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
+/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
+static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
+
+/* Ruleset access masks. */
+struct access_masks {
+	access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
+	access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
+	access_mask_t scope : LANDLOCK_NUM_SCOPE;
+};
+
+union access_masks_all {
+	struct access_masks masks;
+	u32 all;
+};
+
+/* Makes sure all fields are covered. */
+static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
+	      sizeof(typeof_member(union access_masks_all, all)));
+
+typedef u16 layer_mask_t;
+
+/* Makes sure all layers can be checked. */
+static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
+
+#endif /* _SECURITY_LANDLOCK_ACCESS_H */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index d911c924843f..3da5f1945158 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -36,6 +36,7 @@
 #include <uapi/linux/fiemap.h>
 #include <uapi/linux/landlock.h>
 
+#include "access.h"
 #include "common.h"
 #include "cred.h"
 #include "fs.h"
@@ -393,7 +394,7 @@ get_handled_fs_accesses(const struct landlock_ruleset *const domain)
 {
 	/* Handles all initially denied by default access rights. */
 	return landlock_union_access_masks(domain).fs |
-	       LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+	       _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
 }
 
 static const struct access_masks any_fs = {
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index 1487e1f023a1..d445f411c26a 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -13,6 +13,7 @@
 #include <linux/init.h>
 #include <linux/rcupdate.h>
 
+#include "access.h"
 #include "ruleset.h"
 #include "setup.h"
 
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index a93bdbf52fff..cae69f2f01d9 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -20,6 +20,7 @@
 #include <linux/spinlock.h>
 #include <linux/workqueue.h>
 
+#include "access.h"
 #include "limits.h"
 #include "object.h"
 #include "ruleset.h"
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 631e24d4ffe9..2f29b9f40392 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -9,58 +9,15 @@
 #ifndef _SECURITY_LANDLOCK_RULESET_H
 #define _SECURITY_LANDLOCK_RULESET_H
 
-#include <linux/bitops.h>
-#include <linux/build_bug.h>
-#include <linux/kernel.h>
 #include <linux/mutex.h>
 #include <linux/rbtree.h>
 #include <linux/refcount.h>
 #include <linux/workqueue.h>
-#include <uapi/linux/landlock.h>
 
+#include "access.h"
 #include "limits.h"
 #include "object.h"
 
-/*
- * All access rights that are denied by default whether they are handled or not
- * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
- * entries when we need to get the absolute handled access masks.
- */
-/* clang-format off */
-#define LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
-	LANDLOCK_ACCESS_FS_REFER)
-/* clang-format on */
-
-typedef u16 access_mask_t;
-/* Makes sure all filesystem access rights can be stored. */
-static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
-/* Makes sure all network access rights can be stored. */
-static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
-/* Makes sure all scoped rights can be stored. */
-static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
-/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
-static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
-
-/* Ruleset access masks. */
-struct access_masks {
-	access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
-	access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
-	access_mask_t scope : LANDLOCK_NUM_SCOPE;
-};
-
-union access_masks_all {
-	struct access_masks masks;
-	u32 all;
-};
-
-/* Makes sure all fields are covered. */
-static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
-	      sizeof(typeof_member(union access_masks_all, all)));
-
-typedef u16 layer_mask_t;
-/* Makes sure all layers can be checked. */
-static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
-
 /**
  * struct landlock_layer - Access rights for a given layer
  */
@@ -366,7 +323,7 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
 {
 	/* Handles all initially denied by default access rights. */
 	return ruleset->access_masks[layer_level].fs |
-	       LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+	       _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
 }
 
 static inline access_mask_t
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 06/30] landlock: Simplify initially denied access rights
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (4 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 05/30] landlock: Move access types Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-10 11:24   ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 07/30] landlock: Move domain hierarchy management and export helpers Mickaël Salaün
                   ` (23 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Upgrade domain's handled access masks when creating a domain from a
ruleset, instead of converting them at runtime.  This is more consistent
and helps with audit support.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-7-mic@digikod.net
---

Changes since v2:
- New patch.
---
 security/landlock/access.h  | 17 ++++++++++++++++-
 security/landlock/fs.c      | 10 +---------
 security/landlock/ruleset.c |  3 ++-
 3 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/security/landlock/access.h b/security/landlock/access.h
index 9ee4b30a87e6..74fd8f399fbd 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -20,7 +20,8 @@
 /*
  * All access rights that are denied by default whether they are handled or not
  * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
- * entries when we need to get the absolute handled access masks.
+ * entries when we need to get the absolute handled access masks, see
+ * landlock_upgrade_handled_access_masks().
  */
 /* clang-format off */
 #define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
@@ -59,4 +60,18 @@ typedef u16 layer_mask_t;
 /* Makes sure all layers can be checked. */
 static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
 
+/* Upgrades with all initially denied by default access rights. */
+static inline struct access_masks
+landlock_upgrade_handled_access_masks(struct access_masks access_masks)
+{
+	/*
+	 * All access rights that are denied by default whether they are
+	 * explicitly handled or not.
+	 */
+	if (access_masks.fs)
+		access_masks.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+
+	return access_masks;
+}
+
 #endif /* _SECURITY_LANDLOCK_ACCESS_H */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 3da5f1945158..9779170d9199 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -389,14 +389,6 @@ static bool is_nouser_or_private(const struct dentry *dentry)
 		unlikely(IS_PRIVATE(d_backing_inode(dentry))));
 }
 
-static access_mask_t
-get_handled_fs_accesses(const struct landlock_ruleset *const domain)
-{
-	/* Handles all initially denied by default access rights. */
-	return landlock_union_access_masks(domain).fs |
-	       _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
-}
-
 static const struct access_masks any_fs = {
 	.fs = ~0,
 };
@@ -788,7 +780,7 @@ static bool is_access_to_paths_allowed(
 		 * a superset of the meaningful requested accesses).
 		 */
 		access_masked_parent1 = access_masked_parent2 =
-			get_handled_fs_accesses(domain);
+			landlock_union_access_masks(domain).fs;
 		is_dom_check = true;
 	} else {
 		if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index cae69f2f01d9..dbc528f5f3b7 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -385,7 +385,8 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 		err = -EINVAL;
 		goto out_unlock;
 	}
-	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
+	dst->access_masks[dst->num_layers - 1] =
+		landlock_upgrade_handled_access_masks(src->access_masks[0]);
 
 	/* Merges the @src inode tree. */
 	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 07/30] landlock: Move domain hierarchy management and export helpers
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (5 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 06/30] landlock: Simplify initially denied access rights Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 08/30] landlock: Add AUDIT_LANDLOCK_DENY and log ptrace denials Mickaël Salaün
                   ` (22 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Create a new domain.h file containing the struct landlock_hierarchy
definition and helpers.  This type will grow with audit support.  This
also prepares for a new domain type.

Export landlock_get_hierarchy() and landlock_put_hierarchy() that will
be used by audit in a following commit.

Clean up Makefile entries.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-8-mic@digikod.net
---

Changes since v3:
- Export landlock_get_hierarchy() and landlock_put_hierarchy().
- Clean up Makefile entries.

Changes since v1:
- New patch.
---
 MAINTAINERS                 |  1 +
 include/linux/landlock.h    | 31 +++++++++++++++++++++++++++++++
 security/landlock/Makefile  | 11 +++++++++--
 security/landlock/domain.c  | 29 +++++++++++++++++++++++++++++
 security/landlock/domain.h  | 31 +++++++++++++++++++++++++++++++
 security/landlock/ruleset.c | 22 ++++------------------
 security/landlock/ruleset.h | 17 +----------------
 security/landlock/task.c    |  1 +
 8 files changed, 107 insertions(+), 36 deletions(-)
 create mode 100644 include/linux/landlock.h
 create mode 100644 security/landlock/domain.c
 create mode 100644 security/landlock/domain.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 30cbc3d44cd5..425676b25a4f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12961,6 +12961,7 @@ T:	git https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git
 F:	Documentation/security/landlock.rst
 F:	Documentation/userspace-api/landlock.rst
 F:	fs/ioctl.c
+F:	include/linux/landlock.h
 F:	include/uapi/linux/landlock.h
 F:	samples/landlock/
 F:	security/landlock/
diff --git a/include/linux/landlock.h b/include/linux/landlock.h
new file mode 100644
index 000000000000..8491142658a1
--- /dev/null
+++ b/include/linux/landlock.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Landlock - Kernel API
+ *
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#ifndef _LINUX_LANDLOCK_H
+#define _LINUX_LANDLOCK_H
+
+struct landlock_hierarchy;
+
+#ifdef CONFIG_SECURITY_LANDLOCK
+
+void landlock_get_hierarchy(struct landlock_hierarchy *hierarchy);
+
+void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy);
+
+#else /* CONFIG_SECURITY_LANDLOCK */
+
+static inline void landlock_get_hierarchy(struct landlock_hierarchy *hierarchy)
+{
+}
+
+static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
+{
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK */
+
+#endif /* _LINUX_LANDLOCK_H */
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index e1777abbc413..51815908a464 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,7 +1,14 @@
 obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
 
-landlock-y := setup.o syscalls.o object.o ruleset.o \
-	cred.o task.o fs.o
+landlock-y := \
+	setup.o \
+	syscalls.o \
+	object.o \
+	ruleset.o \
+	cred.o \
+	task.o \
+	fs.o \
+	domain.o
 
 landlock-$(CONFIG_INET) += net.o
 
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
new file mode 100644
index 000000000000..df58638ffc50
--- /dev/null
+++ b/security/landlock/domain.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Domain management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#include <linux/landlock.h>
+#include <linux/mm.h>
+
+#include "domain.h"
+
+void landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy)
+{
+	if (hierarchy)
+		refcount_inc(&hierarchy->usage);
+}
+
+void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
+{
+	while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) {
+		const struct landlock_hierarchy *const freeme = hierarchy;
+
+		hierarchy = hierarchy->parent;
+		kfree(freeme);
+	}
+}
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
new file mode 100644
index 000000000000..2e612ef754e3
--- /dev/null
+++ b/security/landlock/domain.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Domain management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_DOMAIN_H
+#define _SECURITY_LANDLOCK_DOMAIN_H
+
+#include <linux/landlock.h>
+#include <linux/refcount.h>
+
+/**
+ * struct landlock_hierarchy - Node in a domain hierarchy
+ */
+struct landlock_hierarchy {
+	/**
+	 * @parent: Pointer to the parent node, or NULL if it is a root
+	 * Landlock domain.
+	 */
+	struct landlock_hierarchy *parent;
+	/**
+	 * @usage: Number of potential children domains plus their parent
+	 * domain.
+	 */
+	refcount_t usage;
+};
+
+#endif /* _SECURITY_LANDLOCK_DOMAIN_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index dbc528f5f3b7..8b47af69af3e 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -12,6 +12,7 @@
 #include <linux/err.h>
 #include <linux/errno.h>
 #include <linux/kernel.h>
+#include <linux/landlock.h>
 #include <linux/lockdep.h>
 #include <linux/overflow.h>
 #include <linux/rbtree.h>
@@ -21,6 +22,7 @@
 #include <linux/workqueue.h>
 
 #include "access.h"
+#include "domain.h"
 #include "limits.h"
 #include "object.h"
 #include "ruleset.h"
@@ -305,22 +307,6 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
 	return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers));
 }
 
-static void get_hierarchy(struct landlock_hierarchy *const hierarchy)
-{
-	if (hierarchy)
-		refcount_inc(&hierarchy->usage);
-}
-
-static void put_hierarchy(struct landlock_hierarchy *hierarchy)
-{
-	while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) {
-		const struct landlock_hierarchy *const freeme = hierarchy;
-
-		hierarchy = hierarchy->parent;
-		kfree(freeme);
-	}
-}
-
 static int merge_tree(struct landlock_ruleset *const dst,
 		      struct landlock_ruleset *const src,
 		      const enum landlock_key_type key_type)
@@ -475,7 +461,7 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
 		err = -EINVAL;
 		goto out_unlock;
 	}
-	get_hierarchy(parent->hierarchy);
+	landlock_get_hierarchy(parent->hierarchy);
 	child->hierarchy->parent = parent->hierarchy;
 
 out_unlock:
@@ -499,7 +485,7 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
 		free_rule(freeme, LANDLOCK_KEY_NET_PORT);
 #endif /* IS_ENABLED(CONFIG_INET) */
 
-	put_hierarchy(ruleset->hierarchy);
+	landlock_put_hierarchy(ruleset->hierarchy);
 	kfree(ruleset);
 }
 
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 2f29b9f40392..39169b6860e3 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -15,6 +15,7 @@
 #include <linux/workqueue.h>
 
 #include "access.h"
+#include "domain.h"
 #include "limits.h"
 #include "object.h"
 
@@ -106,22 +107,6 @@ struct landlock_rule {
 	struct landlock_layer layers[] __counted_by(num_layers);
 };
 
-/**
- * struct landlock_hierarchy - Node in a ruleset hierarchy
- */
-struct landlock_hierarchy {
-	/**
-	 * @parent: Pointer to the parent node, or NULL if it is a root
-	 * Landlock domain.
-	 */
-	struct landlock_hierarchy *parent;
-	/**
-	 * @usage: Number of potential children domains plus their parent
-	 * domain.
-	 */
-	refcount_t usage;
-};
-
 /**
  * struct landlock_ruleset - Landlock ruleset
  *
diff --git a/security/landlock/task.c b/security/landlock/task.c
index dc7dab78392e..98894ad1abc7 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -18,6 +18,7 @@
 
 #include "common.h"
 #include "cred.h"
+#include "domain.h"
 #include "fs.h"
 #include "ruleset.h"
 #include "setup.h"
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 08/30] landlock: Add AUDIT_LANDLOCK_DENY and log ptrace denials
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (6 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 07/30] landlock: Move domain hierarchy management and export helpers Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-15 23:53   ` [PATCH v4 8/30] " Paul Moore
  2025-01-08 15:43 ` [PATCH v4 09/30] landlock: Add AUDIT_LANDLOCK_DOM_{INFO,DROP} and log domain properties Mickaël Salaün
                   ` (21 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add a new AUDIT_LANDLOCK_DENY record type dedicated to any Landlock
denials.  This encodes access verdict into the type to be able to filter
such denied requests with audit rules.  Moreover, it would not make
sense for Landlock to log allowed requests (by default).
AUDIT_LANDLOCK_DENY indicates that something unexpected happened.  A
following commit will allow to filter such denials according to the task
that created the related security policy.

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

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

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

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

Audit event sample:

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

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

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

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

Changes since v3:
- Extend commit message.

Changes since v2:
- Log domain IDs as hexadecimal number: this is a more compact notation
  (i.e. at least one less digit), it improves alignment in logs, and it
  makes most IDs start with 1 as leading digit (because of the 2^32
  minimal value).  Do not use the "0x" prefix that would add useless
  data to logs.
- Constify function arguments.
- Clean up Makefile entries.

Changes since v1:
- Move most audit code to this patch.
- Rebase on the TCP patch series.
- Don't log missing access right: simplify and make it generic for rule
  types.
- Don't log errno and then don't wrap the error with
  landlock_log_request(), as suggested by Jeff.
- Add a WARN_ON_ONCE() check to never dereference null pointers.
- Only log when audit is enabled.
- Don't log task's PID/TID with log_task() because it would be redundant
  with the SYSCALL record.
- Move the "op" in front and rename "domain" to "denying_domain" to make
  it more consistent with other entries.
- Don't update the request with the domain ID but add an helper to get
  it from the layer masks (and in a following commit with a struct
  file).
- Revamp get_domain_id_from_layer_masks() into
  get_level_from_layer_masks().
- For ptrace_traceme, log the parent domain instead of the current one.
- Add documentation.
- Rename AUDIT_LANDLOCK_DENIAL to AUDIT_LANDLOCK_DENY.
- Only log the domain ID and the target task.
- Log "blockers", which are either implicit restrictions (e.g. ptrace)
  or explicit access rights (e.g. filesystem), or scopes (e.g. signal).
- Don't log LSM hook names/operations.
- Pick an audit event ID folling the IPE ones.
- Add KUnit tests.
---
 include/uapi/linux/audit.h  |   3 +-
 security/landlock/Makefile  |   4 +-
 security/landlock/audit.c   | 137 ++++++++++++++++++++++++++++++++++++
 security/landlock/audit.h   |  52 ++++++++++++++
 security/landlock/domain.c  |  18 +++++
 security/landlock/domain.h  |  22 ++++++
 security/landlock/ruleset.c |   6 ++
 security/landlock/task.c    |  93 ++++++++++++++++++------
 8 files changed, 310 insertions(+), 25 deletions(-)
 create mode 100644 security/landlock/audit.c
 create mode 100644 security/landlock/audit.h

diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index 75e21a135483..60c909c396c0 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -33,7 +33,7 @@
  * 1100 - 1199 user space trusted application messages
  * 1200 - 1299 messages internal to the audit daemon
  * 1300 - 1399 audit event messages
- * 1400 - 1499 SE Linux use
+ * 1400 - 1499 access control messages
  * 1500 - 1599 kernel LSPP events
  * 1600 - 1699 kernel crypto events
  * 1700 - 1799 kernel anomaly records
@@ -146,6 +146,7 @@
 #define AUDIT_IPE_ACCESS	1420	/* IPE denial or grant */
 #define AUDIT_IPE_CONFIG_CHANGE	1421	/* IPE config change */
 #define AUDIT_IPE_POLICY_LOAD	1422	/* IPE policy load */
+#define AUDIT_LANDLOCK_DENY	1423	/* Landlock denial */
 
 #define AUDIT_FIRST_KERN_ANOM_MSG   1700
 #define AUDIT_LAST_KERN_ANOM_MSG    1799
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index 51815908a464..64e9fad7de6e 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -12,4 +12,6 @@ landlock-y := \
 
 landlock-$(CONFIG_INET) += net.o
 
-landlock-$(CONFIG_AUDIT) += id.o
+landlock-$(CONFIG_AUDIT) += \
+	id.o \
+	audit.o
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
new file mode 100644
index 000000000000..d90680a5026a
--- /dev/null
+++ b/security/landlock/audit.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Audit helpers
+ *
+ * Copyright © 2023-2025 Microsoft Corporation
+ */
+
+#include <kunit/test.h>
+#include <linux/audit.h>
+#include <linux/lsm_audit.h>
+
+#include "audit.h"
+#include "domain.h"
+#include "ruleset.h"
+
+static const char *get_blocker(const enum landlock_request_type type)
+{
+	switch (type) {
+	case LANDLOCK_REQUEST_PTRACE:
+		return "ptrace";
+	}
+
+	WARN_ON_ONCE(1);
+	return "unknown";
+}
+
+static void log_blockers(struct audit_buffer *const ab,
+			 const enum landlock_request_type type)
+{
+	audit_log_format(ab, "%s", get_blocker(type));
+}
+
+static struct landlock_hierarchy *
+get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
+{
+	struct landlock_hierarchy *node = domain->hierarchy;
+	ssize_t i;
+
+	if (WARN_ON_ONCE(layer >= domain->num_layers))
+		return node;
+
+	for (i = domain->num_layers - 1; i > layer; i--) {
+		if (WARN_ON_ONCE(!node->parent))
+			break;
+
+		node = node->parent;
+	}
+
+	return node;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_hierarchy(struct kunit *const test)
+{
+	struct landlock_hierarchy dom0_node = {
+		.id = 10,
+	};
+	struct landlock_hierarchy dom1_node = {
+		.parent = &dom0_node,
+		.id = 20,
+	};
+	struct landlock_hierarchy dom2_node = {
+		.parent = &dom1_node,
+		.id = 30,
+	};
+	struct landlock_ruleset dom2 = {
+		.hierarchy = &dom2_node,
+		.num_layers = 3,
+	};
+
+	KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
+	KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
+	KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
+	KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+static bool is_valid_request(const struct landlock_request *const request)
+{
+	if (WARN_ON_ONCE(!request->layer_plus_one))
+		return false;
+
+	return true;
+}
+
+/**
+ * landlock_log_denial - Create audit records related to a denial
+ *
+ * @domain: The domain denying an action.
+ * @request: Detail of the user space request.
+ */
+void landlock_log_denial(const struct landlock_ruleset *const domain,
+			 const struct landlock_request *const request)
+{
+	struct audit_buffer *ab;
+	struct landlock_hierarchy *youngest_denied;
+
+	if (WARN_ON_ONCE(!domain || !domain->hierarchy || !request))
+		return;
+
+	if (!is_valid_request(request))
+		return;
+
+	if (!unlikely(audit_context() && audit_enabled))
+		return;
+
+	ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
+			     AUDIT_LANDLOCK_DENY);
+	if (!ab)
+		return;
+
+	youngest_denied = get_hierarchy(domain, request->layer_plus_one - 1);
+	audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
+	log_blockers(ab, request->type);
+	audit_log_lsm_data(ab, &request->audit);
+	audit_log_end(ab);
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static struct kunit_case test_cases[] = {
+	/* clang-format off */
+	KUNIT_CASE(test_get_hierarchy),
+	{}
+	/* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+	.name = "landlock_audit",
+	.test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
new file mode 100644
index 000000000000..4a635d8bc1af
--- /dev/null
+++ b/security/landlock/audit.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Audit helpers
+ *
+ * Copyright © 2023-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_AUDIT_H
+#define _SECURITY_LANDLOCK_AUDIT_H
+
+#include <linux/audit.h>
+#include <linux/lsm_audit.h>
+
+#include "ruleset.h"
+
+enum landlock_request_type {
+	LANDLOCK_REQUEST_PTRACE = 1,
+};
+
+/*
+ * We should be careful to only use a variable of this type for
+ * landlock_log_denial().  This way, the compiler can remove it entirely if
+ * CONFIG_AUDIT is not set.
+ */
+struct landlock_request {
+	/* Mandatory fields. */
+	enum landlock_request_type type;
+	struct common_audit_data audit;
+
+	/**
+	 * layer_plus_one: First layer level that denies the request + 1.  The
+	 * extra one is useful to detect uninitialized field.
+	 */
+	size_t layer_plus_one;
+};
+
+#ifdef CONFIG_AUDIT
+
+void landlock_log_denial(const struct landlock_ruleset *const domain,
+			 const struct landlock_request *const request);
+
+#else /* CONFIG_AUDIT */
+
+static inline void
+landlock_log_denial(const struct landlock_ruleset *const domain,
+		    const struct landlock_request *const request)
+{
+}
+
+#endif /* CONFIG_AUDIT */
+
+#endif /* _SECURITY_LANDLOCK_AUDIT_H */
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index df58638ffc50..895874285fe2 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -11,6 +11,7 @@
 #include <linux/mm.h>
 
 #include "domain.h"
+#include "id.h"
 
 void landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy)
 {
@@ -27,3 +28,20 @@ void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
 		kfree(freeme);
 	}
 }
+
+#ifdef CONFIG_AUDIT
+
+/**
+ * landlock_init_current_hierarchy - Partially initialize landlock_hierarchy
+ *
+ * @hierarchy: The hierarchy to initialize.
+ *
+ * @hierarchy->parent and @hierarchy->usage should already be set.
+ */
+int landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
+{
+	hierarchy->id = landlock_get_id_range(1);
+	return 0;
+}
+
+#endif /* CONFIG_AUDIT */
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 2e612ef754e3..514281f64b20 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -4,6 +4,7 @@
  *
  * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
  * Copyright © 2018-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
  */
 
 #ifndef _SECURITY_LANDLOCK_DOMAIN_H
@@ -26,6 +27,27 @@ struct landlock_hierarchy {
 	 * domain.
 	 */
 	refcount_t usage;
+
+#ifdef CONFIG_AUDIT
+	/**
+	 * @id: Landlock domain ID, sets once at domain creation time.
+	 */
+	u64 id;
+#endif /* CONFIG_AUDIT */
 };
 
+#ifdef CONFIG_AUDIT
+
+int landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy);
+
+#else /* CONFIG_AUDIT */
+
+static inline int
+landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
+{
+	return 0;
+}
+
+#endif /* CONFIG_AUDIT */
+
 #endif /* _SECURITY_LANDLOCK_DOMAIN_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 8b47af69af3e..3e4deb2d0aa3 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -22,6 +22,7 @@
 #include <linux/workqueue.h>
 
 #include "access.h"
+#include "audit.h"
 #include "domain.h"
 #include "limits.h"
 #include "object.h"
@@ -504,6 +505,7 @@ static void free_ruleset_work(struct work_struct *const work)
 	free_ruleset(ruleset);
 }
 
+/* Only called by hook_cred_free(). */
 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
 {
 	if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
@@ -563,6 +565,10 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
 	if (err)
 		goto out_put_dom;
 
+	err = landlock_init_current_hierarchy(new_dom->hierarchy);
+	if (err)
+		goto out_put_dom;
+
 	return new_dom;
 
 out_put_dom:
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 98894ad1abc7..4fef963274fd 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -10,12 +10,14 @@
 #include <linux/cred.h>
 #include <linux/errno.h>
 #include <linux/kernel.h>
+#include <linux/lsm_audit.h>
 #include <linux/lsm_hooks.h>
 #include <linux/rcupdate.h>
 #include <linux/sched.h>
 #include <net/af_unix.h>
 #include <net/sock.h>
 
+#include "audit.h"
 #include "common.h"
 #include "cred.h"
 #include "domain.h"
@@ -38,41 +40,29 @@ static bool domain_scope_le(const struct landlock_ruleset *const parent,
 {
 	const struct landlock_hierarchy *walker;
 
+	/* Quick return for non-landlocked tasks. */
 	if (!parent)
 		return true;
+
 	if (!child)
 		return false;
+
 	for (walker = child->hierarchy; walker; walker = walker->parent) {
 		if (walker == parent->hierarchy)
 			/* @parent is in the scoped hierarchy of @child. */
 			return true;
 	}
+
 	/* There is no relationship between @parent and @child. */
 	return false;
 }
 
-static bool task_is_scoped(const struct task_struct *const parent,
-			   const struct task_struct *const child)
-{
-	bool is_scoped;
-	const struct landlock_ruleset *dom_parent, *dom_child;
-
-	rcu_read_lock();
-	dom_parent = landlock_get_task_domain(parent);
-	dom_child = landlock_get_task_domain(child);
-	is_scoped = domain_scope_le(dom_parent, dom_child);
-	rcu_read_unlock();
-	return is_scoped;
-}
-
-static int task_ptrace(const struct task_struct *const parent,
-		       const struct task_struct *const child)
+static int domain_ptrace(const struct landlock_ruleset *const parent,
+			 const struct landlock_ruleset *const child)
 {
-	/* Quick return for non-landlocked tasks. */
-	if (!landlocked(parent))
-		return 0;
-	if (task_is_scoped(parent, child))
+	if (domain_scope_le(parent, child))
 		return 0;
+
 	return -EPERM;
 }
 
@@ -92,7 +82,36 @@ static int task_ptrace(const struct task_struct *const parent,
 static int hook_ptrace_access_check(struct task_struct *const child,
 				    const unsigned int mode)
 {
-	return task_ptrace(current, child);
+	const struct landlock_ruleset *parent_dom, *child_dom;
+	struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_PTRACE,
+		.audit = {
+			.type = LSM_AUDIT_DATA_TASK,
+			.u.tsk = child,
+		},
+	};
+	int err;
+
+	/* Quick return for non-landlocked tasks. */
+	parent_dom = landlock_get_current_domain();
+	if (!parent_dom)
+		return 0;
+
+	rcu_read_lock();
+	child_dom = landlock_get_task_domain(child);
+	err = domain_ptrace(parent_dom, child_dom);
+	rcu_read_unlock();
+
+	/*
+	 * For the ptrace_access_check case, we log the current/parent domain
+	 * and the child task.
+	 */
+	if (err && !(mode & PTRACE_MODE_NOAUDIT)) {
+		request.layer_plus_one = parent_dom->num_layers;
+		landlock_log_denial(parent_dom, &request);
+	}
+
+	return err;
 }
 
 /**
@@ -109,7 +128,35 @@ static int hook_ptrace_access_check(struct task_struct *const child,
  */
 static int hook_ptrace_traceme(struct task_struct *const parent)
 {
-	return task_ptrace(parent, current);
+	const struct landlock_ruleset *parent_dom, *child_dom;
+	struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_PTRACE,
+		.audit = {
+			.type = LSM_AUDIT_DATA_TASK,
+			.u.tsk = parent,
+		},
+	};
+	int err;
+
+	child_dom = landlock_get_current_domain();
+	rcu_read_lock();
+	parent_dom = landlock_get_task_domain(parent);
+	err = domain_ptrace(parent_dom, child_dom);
+
+	/*
+	 * For the ptrace_traceme case, we log the domain which is the cause of
+	 * the denial, which means the parent domain instead of the current
+	 * domain.  This may look weird because the ptrace_traceme action is a
+	 * request to be traced, but the semantic is consistent with
+	 * hook_ptrace_access_check().
+	 */
+	if (err) {
+		request.layer_plus_one = parent_dom->num_layers;
+		landlock_log_denial(parent_dom, &request);
+	}
+
+	rcu_read_unlock();
+	return err;
 }
 
 /**
@@ -128,7 +175,7 @@ static bool domain_is_scoped(const struct landlock_ruleset *const client,
 			     access_mask_t scope)
 {
 	int client_layer, server_layer;
-	struct landlock_hierarchy *client_walker, *server_walker;
+	const struct landlock_hierarchy *client_walker, *server_walker;
 
 	/* Quick return if client has no domain */
 	if (WARN_ON_ONCE(!client))
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 09/30] landlock: Add AUDIT_LANDLOCK_DOM_{INFO,DROP} and log domain properties
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (7 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 08/30] landlock: Add AUDIT_LANDLOCK_DENY and log ptrace denials Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-15 23:53   ` [PATCH v4 9/30] " Paul Moore
  2025-01-08 15:43 ` [PATCH v4 10/30] landlock: Log mount-related denials Mickaël Salaün
                   ` (20 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Asynchronously log domain information when it first denies an access.
This minimize the amount of generated logs, which makes it possible to
always log denials since they should not happen (except with the new
LANDLOCK_RESTRICT_SELF_QUIET flag).  These records are identified with
the new AUDIT_LANDLOCK_DOM_INFO type.

The AUDIT_LANDLOCK_DOM_INFO message contains:
- the "domain" ID which is described,
- the "creation" time of this domain,
- a minimal set of properties to easily identify the task that loaded
  the domain's policy with landlock_restrict_self(2): "pid", "uid",
  executable path ("exe"), and command line ("comm").

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

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

Audit event sample for a first denial:

  type=LANDLOCK_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
  type=LANDLOCK_DOM_INFO msg=audit(1732186800.349:44): domain=195ba459b creation=1732186800.345 pid=300 uid=0 exe="/root/sandboxer" comm="sandboxer"
  type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0

Audit event sample for a following denial:

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

Log domain deletion with the new AUDIT_LANDLOCK_DOM_DROP record type
when a domain was previously logged.  This makes it possible for log
parsers to free potential resources when a domain ID will never show
again.

The AUDIT_LANDLOCK_DOM_DROP message contains:
- the "domain" ID which is being freed,
- the number of "denials" accounted to this domain, which is at least 1.

The number of denied access requests is useful to easily check how many
access requests a domain blocked and potentially if some of them are
missing in logs because of audit rate limiting or audit rules.  Rate
limiting could also drop this record though.

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

  type=LANDLOCK_DOM_DROP msg=audit(1732186800.393:46): domain=195ba459b denials=2

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

Questions about AUDIT_LANDLOCK_DOM_INFO messages (keeping in mind that
each logged metadata may need to be stored for the lifetime of each
domain):
- Should we also log the initially restricted task's loginuid?
- Should we also log the initially restricted task's sessionid?

Changes since v3:
- Log number of denied access requests with AUDIT_LANDLOCK_DOM_DROP
  records, suggested by Tyler.
- Do not store a struct path pointer but the resolved string instead.
  This enables us to not block unmount of the initially restricted task
  executable's mount point.  See the new get_current_info() and
  get_current_exe().  A following patch add tests for this case.
- Create and allocate a new struct landlock_details for initially
  restricted task's information.
- Remove audit_get_ctime() call, as requested by Paul.  We now always
  have a standalone timestamp per Landlock domain creations.
- Fix docstring.

Changes since v2:
- Fix docstring.
- Fix log_status check in log_hierarchy() to also log
  LANDLOCK_LOG_DISABLED.
- Add audit's creation time to domain's properties.
- Use hexadecimal notation for domain IDs.
- Remove domain's parent records: parent domains are not really useful
  in the logs.  They will be available with the upcoming introspection
  feature though.
- Extend commit message with audit's timestamp explanation.

Changes since v1:
- Add a ruleset's version for atomic logs.
- Rebased on the TCP patch series.
- Rename operation using "_" instead of "-".
- Rename AUDIT_LANDLOCK to AUDIT_LANDLOCK_RULESET.
- Only log when audit is enabled, but always set domain IDs.
- Don't log task's PID/TID with log_task() because it would be redundant
  with the SYSCALL record.
- Remove race condition when logging ruleset creation and logging
  ruleset modification while the related file descriptor was already
  registered but the ruleset creation not logged yet.
- Fix domain drop logs.
- Move the domain drop record from the previous patch into this one.
- Do not log domain creation but log first domain use instead.
- Save task's properties that sandbox themselves.
---
 include/uapi/linux/audit.h  |   2 +
 security/landlock/audit.c   |  88 ++++++++++++++++++++++++++++-
 security/landlock/audit.h   |   7 +++
 security/landlock/domain.c  | 109 ++++++++++++++++++++++++++++++++++++
 security/landlock/domain.h  |  63 +++++++++++++++++++++
 security/landlock/ruleset.c |   6 ++
 6 files changed, 274 insertions(+), 1 deletion(-)

diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index 60c909c396c0..a72f7b3403be 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -147,6 +147,8 @@
 #define AUDIT_IPE_CONFIG_CHANGE	1421	/* IPE config change */
 #define AUDIT_IPE_POLICY_LOAD	1422	/* IPE policy load */
 #define AUDIT_LANDLOCK_DENY	1423	/* Landlock denial */
+#define AUDIT_LANDLOCK_DOM_INFO	1424	/* Landlock domain properties */
+#define AUDIT_LANDLOCK_DOM_DROP	1425	/* Landlock domain release */
 
 #define AUDIT_FIRST_KERN_ANOM_MSG   1700
 #define AUDIT_LAST_KERN_ANOM_MSG    1799
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index d90680a5026a..ccc591146f8a 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -8,6 +8,8 @@
 #include <kunit/test.h>
 #include <linux/audit.h>
 #include <linux/lsm_audit.h>
+#include <linux/pid.h>
+#include <linux/uidgid.h>
 
 #include "audit.h"
 #include "domain.h"
@@ -30,6 +32,43 @@ static void log_blockers(struct audit_buffer *const ab,
 	audit_log_format(ab, "%s", get_blocker(type));
 }
 
+static void log_node(struct landlock_hierarchy *const node)
+{
+	struct audit_buffer *ab;
+
+	if (WARN_ON_ONCE(!node))
+		return;
+
+	/* Ignores already logged domains.  */
+	if (READ_ONCE(node->log_status) == LANDLOCK_LOG_RECORDED)
+		return;
+
+	ab = audit_log_start(audit_context(), GFP_ATOMIC,
+			     AUDIT_LANDLOCK_DOM_INFO);
+	if (!ab)
+		return;
+
+	WARN_ON_ONCE(node->id == 0);
+	audit_log_format(
+		ab,
+		"domain=%llx creation=%llu.%03lu pid=%d uid=%u exe=", node->id,
+		/* See audit_log_start() */
+		(unsigned long long)node->details->creation.tv_sec,
+		node->details->creation.tv_nsec / 1000000,
+		pid_nr(node->details->pid),
+		from_kuid(&init_user_ns, node->details->cred->uid));
+	audit_log_untrustedstring(ab, node->details->exe_path);
+	audit_log_format(ab, " comm=");
+	audit_log_untrustedstring(ab, node->details->comm);
+	audit_log_end(ab);
+
+	/*
+	 * There may be race condition leading to logging of the same domain
+	 * several times but that is OK.
+	 */
+	WRITE_ONCE(node->log_status, LANDLOCK_LOG_RECORDED);
+}
+
 static struct landlock_hierarchy *
 get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
 {
@@ -103,6 +142,15 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
 	if (!is_valid_request(request))
 		return;
 
+	youngest_denied = get_hierarchy(domain, request->layer_plus_one - 1);
+
+	/*
+	 * Consistently keeps track of the number of denied access requests
+	 * even if audit is currently disabled or if audit rules currently
+	 * exclude this record type.
+	 */
+	atomic64_inc(&youngest_denied->num_denials);
+
 	if (!unlikely(audit_context() && audit_enabled))
 		return;
 
@@ -111,11 +159,49 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
 	if (!ab)
 		return;
 
-	youngest_denied = get_hierarchy(domain, request->layer_plus_one - 1);
 	audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
 	log_blockers(ab, request->type);
 	audit_log_lsm_data(ab, &request->audit);
 	audit_log_end(ab);
+
+	/* Logs this domain if it is the first time. */
+	log_node(youngest_denied);
+}
+
+/**
+ * landlock_log_drop_domain - Create an audit record when a domain is deleted
+ *
+ * @domain: The domain being deleted.
+ *
+ * Only domains which previously appeared in the audit logs are logged again.
+ * This is useful to know when a domain will never show again in the audit log.
+ *
+ * This record is not directly tied to a syscall entry.
+ *
+ * Called by the cred_free() hook, in an uninterruptible context.
+ */
+void landlock_log_drop_domain(const struct landlock_ruleset *const domain)
+{
+	struct audit_buffer *ab;
+
+	if (WARN_ON_ONCE(!domain->hierarchy))
+		return;
+
+	if (!audit_enabled)
+		return;
+
+	/* Ignores domains that were not logged.  */
+	if (READ_ONCE(domain->hierarchy->log_status) != LANDLOCK_LOG_RECORDED)
+		return;
+
+	ab = audit_log_start(audit_context(), GFP_ATOMIC,
+			     AUDIT_LANDLOCK_DOM_DROP);
+	if (!ab)
+		return;
+
+	audit_log_format(ab, "domain=%llx denials=%llu", domain->hierarchy->id,
+			 atomic64_read(&domain->hierarchy->num_denials));
+	audit_log_end(ab);
 }
 
 #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 4a635d8bc1af..7bc67ca2e470 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -36,11 +36,18 @@ struct landlock_request {
 
 #ifdef CONFIG_AUDIT
 
+void landlock_log_drop_domain(const struct landlock_ruleset *const domain);
+
 void landlock_log_denial(const struct landlock_ruleset *const domain,
 			 const struct landlock_request *const request);
 
 #else /* CONFIG_AUDIT */
 
+static inline void
+landlock_log_drop_domain(const struct landlock_ruleset *const domain)
+{
+}
+
 static inline void
 landlock_log_denial(const struct landlock_ruleset *const domain,
 		    const struct landlock_request *const request)
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 895874285fe2..d0cb9f8683b4 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -7,10 +7,17 @@
  * Copyright © 2024-2025 Microsoft Corporation
  */
 
+#include <linux/cred.h>
+#include <linux/file.h>
 #include <linux/landlock.h>
 #include <linux/mm.h>
+#include <linux/path.h>
+#include <linux/pid.h>
+#include <linux/sched.h>
+#include <linux/timekeeping.h>
 
 #include "domain.h"
+#include "fs.h"
 #include "id.h"
 
 void landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy)
@@ -24,6 +31,12 @@ void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
 	while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) {
 		const struct landlock_hierarchy *const freeme = hierarchy;
 
+#ifdef CONFIG_AUDIT
+		put_cred(hierarchy->details->cred);
+		put_pid(hierarchy->details->pid);
+		kfree(hierarchy->details);
+#endif /* CONFIG_AUDIT */
+
 		hierarchy = hierarchy->parent;
 		kfree(freeme);
 	}
@@ -31,16 +44,112 @@ void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
 
 #ifdef CONFIG_AUDIT
 
+/**
+ * get_current_exe - Get the current's executable path, if any
+ *
+ * @path_str: Returned pointer to a path string with a lifetime tied to the
+ *            returned buffer, if any.
+ * @path_size: Returned size of the @path string (including the trailing null
+ *             character), if any.
+ *
+ * Returns: A pointer to an allocated buffer where @path point to, %NULL if
+ * there is no executable path, or an error otherwise.
+ */
+static const void *get_current_exe(const char **path_str, size_t *path_size)
+{
+	struct mm_struct *mm = current->mm;
+	struct file *file __free(fput) = NULL;
+	char *buffer __free(kfree) = NULL;
+	const char *path;
+	size_t size;
+
+	/* Adds 11 extra characters for the potential " (deleted)" suffix. */
+	const size_t buffer_size = PATH_MAX + 11;
+
+	if (!mm)
+		return NULL;
+
+	file = get_mm_exe_file(mm);
+	if (!file)
+		return NULL;
+
+	buffer = kmalloc(buffer_size, GFP_KERNEL);
+	if (!buffer)
+		return ERR_PTR(-ENOMEM);
+
+	path = d_path(&file->f_path, buffer, buffer_size);
+	if (WARN_ON_ONCE(IS_ERR(path)))
+		/* Should never happen according to buffer_size. */
+		return ERR_CAST(path);
+
+	size = buffer + buffer_size - path;
+	if (WARN_ON_ONCE(size <= 0))
+		return ERR_PTR(-ENAMETOOLONG);
+
+	*path_size = size;
+	*path_str = path;
+	return no_free_ptr(buffer);
+}
+
+/*
+ * Returns: A newly allocated object describing a domain, or an error
+ * otherwise.
+ */
+static struct landlock_details *get_current_details(void)
+{
+	/* Cf. audit_log_d_path_exe() */
+	static const char null_path[] = "(null)";
+	const char *path_str = null_path;
+	size_t path_size = sizeof(null_path);
+	struct landlock_details *details;
+	const void *buffer __free(kfree) = NULL;
+
+	buffer = get_current_exe(&path_str, &path_size);
+	if (IS_ERR(buffer))
+		return ERR_CAST(buffer);
+
+	/*
+	 * Create the new details according to the path's length.  Do not
+	 * allocate with GFP_KERNEL_ACCOUNT because it is independent from the
+	 * caller.
+	 */
+	details =
+		kzalloc(struct_size(details, exe_path, path_size), GFP_KERNEL);
+	if (!details)
+		return ERR_PTR(-ENOMEM);
+
+	memcpy(details->exe_path, path_str, path_size);
+	ktime_get_coarse_real_ts64(&details->creation);
+
+	WARN_ON_ONCE(current_cred() != current_real_cred());
+	details->cred = get_current_cred();
+	details->pid = get_pid(task_pid(current));
+	get_task_comm(details->comm, current);
+	return details;
+}
+
 /**
  * landlock_init_current_hierarchy - Partially initialize landlock_hierarchy
  *
  * @hierarchy: The hierarchy to initialize.
  *
+ * The current task is referenced as the domain restrictor.  The subjective
+ * credentials must not be in an overridden state.
+ *
  * @hierarchy->parent and @hierarchy->usage should already be set.
  */
 int landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
 {
+	struct landlock_details *details;
+
+	details = get_current_details();
+	if (IS_ERR(details))
+		return PTR_ERR(details);
+
+	hierarchy->details = details;
 	hierarchy->id = landlock_get_id_range(1);
+	hierarchy->log_status = LANDLOCK_LOG_PENDING;
+	atomic64_set(&hierarchy->num_denials, 0);
 	return 0;
 }
 
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 514281f64b20..a7fda9c6a5a3 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -10,8 +10,57 @@
 #ifndef _SECURITY_LANDLOCK_DOMAIN_H
 #define _SECURITY_LANDLOCK_DOMAIN_H
 
+#include <linux/cred.h>
 #include <linux/landlock.h>
+#include <linux/path.h>
+#include <linux/pid.h>
 #include <linux/refcount.h>
+#include <linux/sched.h>
+#include <linux/time64.h>
+
+enum landlock_log_status {
+	LANDLOCK_LOG_PENDING = 0,
+	LANDLOCK_LOG_RECORDED,
+};
+
+/**
+ * struct landlock_details - Dommain's creation information
+ *
+ * Rarely accessed, mainly when logging the first domain's denial.
+ *
+ * The contained pointers are initialized at the domain creation time and never
+ * changed again.  Contrary to most other Landlock object types, this one is
+ * not allocated with GFP_KERNEL_ACCOUNT because its size may not be under the
+ * caller's control (e.g. unknown exe_path) and the data is not explicitly
+ * requested nor used by tasks.
+ */
+struct landlock_details {
+	/**
+	 * @creation: Time of the domain creation (i.e. syscall entry as used
+	 * in audit context if available).
+	 */
+	struct timespec64 creation;
+	/**
+	 * @cred: Credential of the task that initially restricted itself, at
+	 * creation time.
+	 */
+	const struct cred *cred;
+	/**
+	 * @pid: PID of the task that initially restricted itself.  It still
+	 * identifies the same task.
+	 */
+	struct pid *pid;
+	/**
+	 * @comm: Command line of the task that initially restricted itself, at
+	 * creation time.  Always NULL terminated.
+	 */
+	char comm[TASK_COMM_LEN];
+	/**
+	 * @exe_path: Executable path of the task that initially restricted
+	 * itself, at creation time.  Always NULL terminated.
+	 */
+	char exe_path[];
+};
 
 /**
  * struct landlock_hierarchy - Node in a domain hierarchy
@@ -29,10 +78,24 @@ struct landlock_hierarchy {
 	refcount_t usage;
 
 #ifdef CONFIG_AUDIT
+	/**
+	 * @log_status: Whether this domain should be logged or not.  Because
+	 * concurrent log entries may be created at the same time, it is still
+	 * possible to have several domain records of the same domain.
+	 */
+	enum landlock_log_status log_status;
+	/**
+	 * @num_denials: Number of access requests denied by this domain.
+	 */
+	atomic64_t num_denials;
 	/**
 	 * @id: Landlock domain ID, sets once at domain creation time.
 	 */
 	u64 id;
+	/**
+	 * @details: Information about the related domain.
+	 */
+	const struct landlock_details *details;
 #endif /* CONFIG_AUDIT */
 };
 
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 3e4deb2d0aa3..cd94d4660561 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -509,6 +509,9 @@ static void free_ruleset_work(struct work_struct *const work)
 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
 {
 	if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
+		/* Logs with the current context. */
+		landlock_log_drop_domain(ruleset);
+
 		INIT_WORK(&ruleset->work_free, free_ruleset_work);
 		schedule_work(&ruleset->work_free);
 	}
@@ -520,6 +523,9 @@ void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
  * @parent: Parent domain.
  * @ruleset: New ruleset to be merged.
  *
+ * The current task is requesting to be restricted.  The subjective credentials
+ * must not be in an overridden state (see landlock_init_current_hierarchy).
+ *
  * Returns the intersection of @parent and @ruleset, or returns @parent if
  * @ruleset is empty, or returns a duplicate of @ruleset if @parent is empty.
  */
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 10/30] landlock: Log mount-related denials
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (8 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 09/30] landlock: Add AUDIT_LANDLOCK_DOM_{INFO,DROP} and log domain properties Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 11/30] landlock: Align partial refer access checks with final ones Mickaël Salaün
                   ` (19 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add audit support for sb_mount, move_mount, sb_umount, sb_remount, and
sb_pivot_root hooks.

The new related blocker is "fs.change_layout".

Add and use a new landlock_match_layer_level() helper.

Audit event sample:

  type=LANDLOCK_DENY msg=audit(1729738800.349:44): domain=195ba459b blockers=fs.change_layout name="/" dev="tmpfs" ino=1

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-11-mic@digikod.net
---

Changes since v3:
- Cosmetic change to the "fs.change_layout" name.

Changes since v2:
- Log the domain that denied the action because not all layers block FS
  layout changes.
- Fix landlock_match_layer_level().

Changes since v1:
- Rebased on the TCP patch series.
- Don't log missing permissions, only domain layer, and then remove the
  permission word (suggested by Günther)
---
 security/landlock/audit.c   |  3 ++
 security/landlock/audit.h   |  1 +
 security/landlock/fs.c      | 64 ++++++++++++++++++++++++++++++++++---
 security/landlock/ruleset.h | 31 ++++++++++++++++++
 4 files changed, 94 insertions(+), 5 deletions(-)

diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index ccc591146f8a..aae93b2c994b 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -20,6 +20,9 @@ static const char *get_blocker(const enum landlock_request_type type)
 	switch (type) {
 	case LANDLOCK_REQUEST_PTRACE:
 		return "ptrace";
+
+	case LANDLOCK_REQUEST_FS_CHANGE_LAYOUT:
+		return "fs.change_layout";
 	}
 
 	WARN_ON_ONCE(1);
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 7bc67ca2e470..745dcf7b1b6d 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -15,6 +15,7 @@
 
 enum landlock_request_type {
 	LANDLOCK_REQUEST_PTRACE = 1,
+	LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
 };
 
 /*
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 9779170d9199..171012efb559 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -23,6 +23,7 @@
 #include <linux/kernel.h>
 #include <linux/limits.h>
 #include <linux/list.h>
+#include <linux/lsm_audit.h>
 #include <linux/lsm_hooks.h>
 #include <linux/mount.h>
 #include <linux/namei.h>
@@ -37,6 +38,7 @@
 #include <uapi/linux/landlock.h>
 
 #include "access.h"
+#include "audit.h"
 #include "common.h"
 #include "cred.h"
 #include "fs.h"
@@ -1301,6 +1303,38 @@ static void hook_sb_delete(struct super_block *const sb)
 		       !atomic_long_read(&landlock_superblock(sb)->inode_refs));
 }
 
+static void
+log_fs_change_layout_path(const struct landlock_ruleset *const domain,
+			  const struct path *const path)
+{
+	const struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
+		.audit = {
+			.type = LSM_AUDIT_DATA_PATH,
+			.u.path = *path,
+		},
+		.layer_plus_one = landlock_match_layer_level(domain, any_fs) + 1,
+	};
+
+	landlock_log_denial(domain, &request);
+}
+
+static void
+log_fs_change_layout_dentry(const struct landlock_ruleset *const domain,
+			    struct dentry *const dentry)
+{
+	const struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
+		.audit = {
+			.type = LSM_AUDIT_DATA_DENTRY,
+			.u.dentry = dentry,
+		},
+		.layer_plus_one = landlock_match_layer_level(domain, any_fs) + 1,
+	};
+
+	landlock_log_denial(domain, &request);
+}
+
 /*
  * Because a Landlock security policy is defined according to the filesystem
  * topology (i.e. the mount namespace), changing it may grant access to files
@@ -1323,16 +1357,24 @@ static int hook_sb_mount(const char *const dev_name,
 			 const struct path *const path, const char *const type,
 			 const unsigned long flags, void *const data)
 {
-	if (!get_current_fs_domain())
+	const struct landlock_ruleset *const dom = get_current_fs_domain();
+
+	if (!dom)
 		return 0;
+
+	log_fs_change_layout_path(dom, path);
 	return -EPERM;
 }
 
 static int hook_move_mount(const struct path *const from_path,
 			   const struct path *const to_path)
 {
-	if (!get_current_fs_domain())
+	const struct landlock_ruleset *const dom = get_current_fs_domain();
+
+	if (!dom)
 		return 0;
+
+	log_fs_change_layout_path(dom, to_path);
 	return -EPERM;
 }
 
@@ -1342,15 +1384,23 @@ static int hook_move_mount(const struct path *const from_path,
  */
 static int hook_sb_umount(struct vfsmount *const mnt, const int flags)
 {
-	if (!get_current_fs_domain())
+	const struct landlock_ruleset *const dom = get_current_fs_domain();
+
+	if (!dom)
 		return 0;
+
+	log_fs_change_layout_dentry(dom, mnt->mnt_root);
 	return -EPERM;
 }
 
 static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
 {
-	if (!get_current_fs_domain())
+	const struct landlock_ruleset *const dom = get_current_fs_domain();
+
+	if (!dom)
 		return 0;
+
+	log_fs_change_layout_dentry(dom, sb->s_root);
 	return -EPERM;
 }
 
@@ -1365,8 +1415,12 @@ static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
 static int hook_sb_pivotroot(const struct path *const old_path,
 			     const struct path *const new_path)
 {
-	if (!get_current_fs_domain())
+	const struct landlock_ruleset *const dom = get_current_fs_domain();
+
+	if (!dom)
 		return 0;
+
+	log_fs_change_layout_path(dom, new_path);
 	return -EPERM;
 }
 
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 39169b6860e3..7dba8bf960f6 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -267,6 +267,37 @@ landlock_get_applicable_domain(const struct landlock_ruleset *const domain,
 	return NULL;
 }
 
+/**
+ * landlock_match_layer_level - Return the layer level restricting @masks
+ *
+ * @domain: Landlock ruleset (used as a domain)
+ * @masks: access masks
+ *
+ * Returns: the number of the layer restricting/handling any right of @access,
+ * or return 0 (i.e. first layer) otherwise.
+ */
+static inline size_t
+landlock_match_layer_level(const struct landlock_ruleset *const domain,
+			   const struct access_masks masks)
+{
+	const union access_masks_all masks_all = {
+		.masks = masks,
+	};
+	ssize_t layer_level;
+
+	for (layer_level = domain->num_layers; layer_level >= 0;
+	     layer_level--) {
+		union access_masks_all layer = {
+			.masks = domain->access_masks[layer_level],
+		};
+
+		if (masks_all.all & layer.all)
+			return layer_level;
+	}
+
+	return 0;
+}
+
 static inline void
 landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
 			    const access_mask_t fs_access_mask,
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 11/30] landlock: Align partial refer access checks with final ones
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (9 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 10/30] landlock: Log mount-related denials Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-10 11:24   ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 12/30] selftests/landlock: Add test to check partial access in a mount tree Mickaël Salaün
                   ` (18 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Fix a logical issue that could have been visible if the source or the
destination of a rename/link action was allowed for either the source or
the destination but not both.  However, this logical bug is unreachable
because either:
- the rename/link action is allowed by the access rights tied to the
  same mount point (without relying on access rights in a parent mount
  point) and the access request is allowed (i.e. allow_parent1 and
  allow_parent2 are true in current_check_refer_path),
- or a common rule in a parent mount point updates the access check for
  the source and the destination (cf. is_access_to_paths_allowed).

See the following layout1.refer_part_mount_tree_is_allowed test that
work with and without this fix.

This fix does not impact current code but it is required for the audit
support.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-12-mic@digikod.net
---

Changes since v2:
- New patch.
---
 security/landlock/fs.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 171012efb559..ddadc465581e 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -567,6 +567,12 @@ static void test_no_more_access(struct kunit *const test)
 #undef NMA_TRUE
 #undef NMA_FALSE
 
+static bool is_layer_masks_allowed(
+	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+{
+	return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
+}
+
 /*
  * Removes @layer_masks accesses that are not requested.
  *
@@ -584,7 +590,8 @@ scope_to_request(const access_mask_t access_request,
 
 	for_each_clear_bit(access_bit, &access_req, ARRAY_SIZE(*layer_masks))
 		(*layer_masks)[access_bit] = 0;
-	return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
+
+	return is_layer_masks_allowed(layer_masks);
 }
 
 #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
@@ -773,9 +780,14 @@ static bool is_access_to_paths_allowed(
 	if (WARN_ON_ONCE(domain->num_layers < 1 || !layer_masks_parent1))
 		return false;
 
+	allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
+
 	if (unlikely(layer_masks_parent2)) {
 		if (WARN_ON_ONCE(!dentry_child1))
 			return false;
+
+		allowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);
+
 		/*
 		 * For a double request, first check for potential privilege
 		 * escalation by looking at domain handled accesses (which are
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 12/30] selftests/landlock: Add test to check partial access in a mount tree
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (10 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 11/30] landlock: Align partial refer access checks with final ones Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-10 11:24   ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 13/30] landlock: Optimize file path walks and prepare for audit support Mickaël Salaün
                   ` (17 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add layout1.refer_part_mount_tree_is_allowed to test the masked logical
issue regarding collect_domain_accesses() calls followed by the
is_access_to_paths_allowed() check in current_check_refer_path().  See
previous commit.

This test should work without the previous fix as well, but it enables
us to make sure future changes will not have impact regarding this
behavior.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-13-mic@digikod.net
---

Changes since v2:
- New patch.
---
 tools/testing/selftests/landlock/fs_test.c | 54 ++++++++++++++++++++--
 1 file changed, 50 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 6788762188fe..42ce1e79ba82 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -85,6 +85,9 @@ static const char file1_s3d1[] = TMP_DIR "/s3d1/f1";
 /* dir_s3d2 is a mount point. */
 static const char dir_s3d2[] = TMP_DIR "/s3d1/s3d2";
 static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3";
+static const char file1_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3/f1";
+static const char dir_s3d4[] = TMP_DIR "/s3d1/s3d2/s3d4";
+static const char file1_s3d4[] = TMP_DIR "/s3d1/s3d2/s3d4/f1";
 
 /*
  * layout1 hierarchy:
@@ -108,8 +111,11 @@ static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3";
  * │           └── f2
  * └── s3d1
  *     ├── f1
- *     └── s3d2
- *         └── s3d3
+ *     └── s3d2 [mount point]
+ *         ├── s3d3
+ *         │   └── f1
+ *         └── s3d4
+ *             └── f1
  */
 
 static bool fgrep(FILE *const inf, const char *const str)
@@ -358,7 +364,8 @@ static void create_layout1(struct __test_metadata *const _metadata)
 	ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s3d2));
 	clear_cap(_metadata, CAP_SYS_ADMIN);
 
-	ASSERT_EQ(0, mkdir(dir_s3d3, 0700));
+	create_file(_metadata, file1_s3d3);
+	create_file(_metadata, file1_s3d4);
 }
 
 static void remove_layout1(struct __test_metadata *const _metadata)
@@ -378,7 +385,8 @@ static void remove_layout1(struct __test_metadata *const _metadata)
 	EXPECT_EQ(0, remove_path(dir_s2d2));
 
 	EXPECT_EQ(0, remove_path(file1_s3d1));
-	EXPECT_EQ(0, remove_path(dir_s3d3));
+	EXPECT_EQ(0, remove_path(file1_s3d3));
+	EXPECT_EQ(0, remove_path(file1_s3d4));
 	set_cap(_metadata, CAP_SYS_ADMIN);
 	umount(dir_s3d2);
 	clear_cap(_metadata, CAP_SYS_ADMIN);
@@ -2444,6 +2452,44 @@ TEST_F_FORK(layout1, refer_mount_root_deny)
 	EXPECT_EQ(0, close(root_fd));
 }
 
+TEST_F_FORK(layout1, refer_part_mount_tree_is_allowed)
+{
+	const struct rule layer1[] = {
+		{
+			/* Parent mount point. */
+			.path = dir_s3d1,
+			.access = LANDLOCK_ACCESS_FS_REFER |
+				  LANDLOCK_ACCESS_FS_MAKE_REG,
+		},
+		{
+			/*
+			 * Removing the source file is allowed because its
+			 * access rights are already a superset of the
+			 * destination.
+			 */
+			.path = dir_s3d4,
+			.access = LANDLOCK_ACCESS_FS_REFER |
+				  LANDLOCK_ACCESS_FS_MAKE_REG |
+				  LANDLOCK_ACCESS_FS_REMOVE_FILE,
+		},
+		{},
+	};
+	int ruleset_fd;
+
+	ASSERT_EQ(0, unlink(file1_s3d3));
+	ruleset_fd = create_ruleset(_metadata,
+				    LANDLOCK_ACCESS_FS_REFER |
+					    LANDLOCK_ACCESS_FS_MAKE_REG |
+					    LANDLOCK_ACCESS_FS_REMOVE_FILE,
+				    layer1);
+
+	ASSERT_LE(0, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	ASSERT_EQ(0, rename(file1_s3d4, file1_s3d3));
+}
+
 TEST_F_FORK(layout1, reparent_link)
 {
 	const struct rule layer1[] = {
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 13/30] landlock: Optimize file path walks and prepare for audit support
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (11 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 12/30] selftests/landlock: Add test to check partial access in a mount tree Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-10 11:24   ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 14/30] landlock: Log file-related denials Mickaël Salaün
                   ` (16 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Always synchronize access_masked_parent* with access_request_parent*
according to allowed_parent*.  This is required for audit support to be
able to get back to the reason of denial.

In a rename/link action, instead of always checking a rule two times for
the same parent directory of the source and the destination files, only
check it when an action on a child was not already allowed.  This also
enables us to keep consistent allowed_parent* status, which is required
to get back to the reason of denial.

For internal mount points, only upgrade allowed_parent* to true but do
not wrongfully set both of them to false otherwise.  This is also
required to get back to the reason of denial.

This does not impact the current behavior but slightly optimize code and
prepare for audit support that needs to know the exact reason why an
access was denied.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-14-mic@digikod.net
---

Changes since v2:
- New patch.
---
 security/landlock/fs.c | 44 ++++++++++++++++++++++++++----------------
 1 file changed, 27 insertions(+), 17 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index ddadc465581e..01f9d5e78218 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -854,15 +854,6 @@ static bool is_access_to_paths_allowed(
 				     child1_is_directory, layer_masks_parent2,
 				     layer_masks_child2,
 				     child2_is_directory))) {
-			allowed_parent1 = scope_to_request(
-				access_request_parent1, layer_masks_parent1);
-			allowed_parent2 = scope_to_request(
-				access_request_parent2, layer_masks_parent2);
-
-			/* Stops when all accesses are granted. */
-			if (allowed_parent1 && allowed_parent2)
-				break;
-
 			/*
 			 * Now, downgrades the remaining checks from domain
 			 * handled accesses to requested accesses.
@@ -870,15 +861,32 @@ static bool is_access_to_paths_allowed(
 			is_dom_check = false;
 			access_masked_parent1 = access_request_parent1;
 			access_masked_parent2 = access_request_parent2;
+
+			allowed_parent1 =
+				allowed_parent1 ||
+				scope_to_request(access_masked_parent1,
+						 layer_masks_parent1);
+			allowed_parent2 =
+				allowed_parent2 ||
+				scope_to_request(access_masked_parent2,
+						 layer_masks_parent2);
+
+			/* Stops when all accesses are granted. */
+			if (allowed_parent1 && allowed_parent2)
+				break;
 		}
 
 		rule = find_rule(domain, walker_path.dentry);
-		allowed_parent1 = landlock_unmask_layers(
-			rule, access_masked_parent1, layer_masks_parent1,
-			ARRAY_SIZE(*layer_masks_parent1));
-		allowed_parent2 = landlock_unmask_layers(
-			rule, access_masked_parent2, layer_masks_parent2,
-			ARRAY_SIZE(*layer_masks_parent2));
+		allowed_parent1 = allowed_parent1 ||
+				  landlock_unmask_layers(
+					  rule, access_masked_parent1,
+					  layer_masks_parent1,
+					  ARRAY_SIZE(*layer_masks_parent1));
+		allowed_parent2 = allowed_parent2 ||
+				  landlock_unmask_layers(
+					  rule, access_masked_parent2,
+					  layer_masks_parent2,
+					  ARRAY_SIZE(*layer_masks_parent2));
 
 		/* Stops when a rule from each layer grants access. */
 		if (allowed_parent1 && allowed_parent2)
@@ -902,8 +910,10 @@ static bool is_access_to_paths_allowed(
 			 * access to internal filesystems (e.g. nsfs, which is
 			 * reachable through /proc/<pid>/ns/<namespace>).
 			 */
-			allowed_parent1 = allowed_parent2 =
-				!!(walker_path.mnt->mnt_flags & MNT_INTERNAL);
+			if (walker_path.mnt->mnt_flags & MNT_INTERNAL) {
+				allowed_parent1 = true;
+				allowed_parent2 = true;
+			}
 			break;
 		}
 		parent_dentry = dget_parent(walker_path.dentry);
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 14/30] landlock: Log file-related denials
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (12 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 13/30] landlock: Optimize file path walks and prepare for audit support Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 15/30] landlock: Log truncate and IOCTL denials Mickaël Salaün
                   ` (15 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add audit support for path_mkdir, path_mknod, path_symlink, path_unlink,
path_rmdir, path_truncate, path_link, path_rename, and file_open hooks.

The dedicated blockers are:
- 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
- fs.truncate
- fs.ioctl_dev

Audit event sample for a denied link action:

  type=LANDLOCK_DENY msg=audit(1729738800.349:44): domain=195ba459b blockers=fs.refer path="/usr/bin" dev="vda2" ino=351
  type=LANDLOCK_DENY msg=audit(1729738800.349:44): domain=195ba459b blockers=fs.make_reg,fs.refer path="/usr/local" dev="vda2" ino=365

We could pack blocker names (e.g. "fs:make_reg,refer") but that would
increase complexity for the kernel and log parsers.  Moreover, this
could not handle blockers of different classes (e.g. fs and net).  Make
it simple and flexible instead.

Add KUnit tests to check the identification from a layer_mask_t array of
the first layer level denying such request.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-15-mic@digikod.net
---

Changes since v3:
- Rename blockers from fs_* to fs.*
- Extend commit message.

Changes since v2:
- Replace integer with bool in log_blockers().
- Always initialize youngest_layer, spotted by Francis Laniel.
- Fix incorrect log reason by using access_masked_parent1 instead of
  access_request_parent1 (thanks to the previous fix patches).
- Clean up formatting.

Changes since v1:
- Move audit code to the ptrace patch.
- Revamp logging and support the path_link and path_rename hooks.
- Add KUnit tests.
---
 security/landlock/audit.c | 177 ++++++++++++++++++++++++++++++++++++--
 security/landlock/audit.h |   9 ++
 security/landlock/fs.c    |  65 +++++++++++---
 3 files changed, 234 insertions(+), 17 deletions(-)

diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index aae93b2c994b..987b4b15e0d7 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -7,22 +7,55 @@
 
 #include <kunit/test.h>
 #include <linux/audit.h>
+#include <linux/bitops.h>
 #include <linux/lsm_audit.h>
 #include <linux/pid.h>
 #include <linux/uidgid.h>
+#include <uapi/linux/landlock.h>
 
 #include "audit.h"
+#include "common.h"
 #include "domain.h"
 #include "ruleset.h"
 
-static const char *get_blocker(const enum landlock_request_type type)
+static const char *const fs_access_strings[] = {
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = "fs.write_file",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = "fs.read_file",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = "fs.read_dir",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = "fs.remove_dir",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_FILE)] = "fs.remove_file",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_CHAR)] = "fs.make_char",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_DIR)] = "fs.make_dir",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = "fs.make_reg",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SOCK)] = "fs.make_sock",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_FIFO)] = "fs.make_fifo",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_BLOCK)] = "fs.make_block",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SYM)] = "fs.make_sym",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate",
+	[BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev",
+};
+
+static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
+
+static __attribute_const__ const char *
+get_blocker(const enum landlock_request_type type,
+	    const unsigned long access_bit)
 {
 	switch (type) {
 	case LANDLOCK_REQUEST_PTRACE:
+		WARN_ON_ONCE(access_bit != -1);
 		return "ptrace";
 
 	case LANDLOCK_REQUEST_FS_CHANGE_LAYOUT:
+		WARN_ON_ONCE(access_bit != -1);
 		return "fs.change_layout";
+
+	case LANDLOCK_REQUEST_FS_ACCESS:
+		if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings)))
+			return "unknown";
+		return fs_access_strings[access_bit];
 	}
 
 	WARN_ON_ONCE(1);
@@ -30,9 +63,20 @@ static const char *get_blocker(const enum landlock_request_type type)
 }
 
 static void log_blockers(struct audit_buffer *const ab,
-			 const enum landlock_request_type type)
+			 const enum landlock_request_type type,
+			 const access_mask_t access)
 {
-	audit_log_format(ab, "%s", get_blocker(type));
+	const unsigned long access_mask = access;
+	unsigned long access_bit;
+	bool is_first = true;
+
+	for_each_set_bit(access_bit, &access_mask, BITS_PER_TYPE(access)) {
+		audit_log_format(ab, "%s%s", is_first ? "" : ",",
+				 get_blocker(type, access_bit));
+		is_first = false;
+	}
+	if (is_first)
+		audit_log_format(ab, "%s", get_blocker(type, -1));
 }
 
 static void log_node(struct landlock_hierarchy *const node)
@@ -119,9 +163,110 @@ static void test_get_hierarchy(struct kunit *const test)
 
 #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
 
+static size_t get_denied_layer(const struct landlock_ruleset *const domain,
+			       access_mask_t *const access_request,
+			       const layer_mask_t (*const layer_masks)[],
+			       const size_t layer_masks_size)
+{
+	const unsigned long access_req = *access_request;
+	unsigned long access_bit;
+	access_mask_t missing = 0;
+	long youngest_layer = -1;
+
+	for_each_set_bit(access_bit, &access_req, layer_masks_size) {
+		const access_mask_t mask = (*layer_masks)[access_bit];
+		long layer;
+
+		if (!mask)
+			continue;
+
+		/* __fls(1) == 0 */
+		layer = __fls(mask);
+		if (layer > youngest_layer) {
+			youngest_layer = layer;
+			missing = BIT(access_bit);
+		} else if (layer == youngest_layer) {
+			missing |= BIT(access_bit);
+		}
+	}
+
+	*access_request = missing;
+	if (youngest_layer == -1)
+		return domain->num_layers - 1;
+
+	return youngest_layer;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_denied_layer(struct kunit *const test)
+{
+	const struct landlock_ruleset dom = {
+		.num_layers = 5,
+	};
+	const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
+		[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT(0),
+		[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT(1),
+		[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = BIT(1) | BIT(0),
+		[BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = BIT(2),
+	};
+	access_mask_t access;
+
+	access = LANDLOCK_ACCESS_FS_EXECUTE;
+	KUNIT_EXPECT_EQ(test, 0,
+			get_denied_layer(&dom, &access, &layer_masks,
+					 sizeof(layer_masks)));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE);
+
+	access = LANDLOCK_ACCESS_FS_READ_FILE;
+	KUNIT_EXPECT_EQ(test, 1,
+			get_denied_layer(&dom, &access, &layer_masks,
+					 sizeof(layer_masks)));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE);
+
+	access = LANDLOCK_ACCESS_FS_READ_DIR;
+	KUNIT_EXPECT_EQ(test, 1,
+			get_denied_layer(&dom, &access, &layer_masks,
+					 sizeof(layer_masks)));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
+
+	access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
+	KUNIT_EXPECT_EQ(test, 1,
+			get_denied_layer(&dom, &access, &layer_masks,
+					 sizeof(layer_masks)));
+	KUNIT_EXPECT_EQ(test, access,
+			LANDLOCK_ACCESS_FS_READ_FILE |
+				LANDLOCK_ACCESS_FS_READ_DIR);
+
+	access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR;
+	KUNIT_EXPECT_EQ(test, 1,
+			get_denied_layer(&dom, &access, &layer_masks,
+					 sizeof(layer_masks)));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
+
+	access = LANDLOCK_ACCESS_FS_WRITE_FILE;
+	KUNIT_EXPECT_EQ(test, 4,
+			get_denied_layer(&dom, &access, &layer_masks,
+					 sizeof(layer_masks)));
+	KUNIT_EXPECT_EQ(test, access, 0);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
 static bool is_valid_request(const struct landlock_request *const request)
 {
-	if (WARN_ON_ONCE(!request->layer_plus_one))
+	if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
+		return false;
+
+	if (request->access) {
+		if (WARN_ON_ONCE(!request->layer_masks))
+			return false;
+	} else {
+		if (WARN_ON_ONCE(request->layer_masks))
+			return false;
+	}
+
+	if (WARN_ON_ONCE(!!request->layer_masks ^ !!request->layer_masks_size))
 		return false;
 
 	return true;
@@ -138,6 +283,7 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
 {
 	struct audit_buffer *ab;
 	struct landlock_hierarchy *youngest_denied;
+	access_mask_t missing;
 
 	if (WARN_ON_ONCE(!domain || !domain->hierarchy || !request))
 		return;
@@ -145,7 +291,25 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
 	if (!is_valid_request(request))
 		return;
 
-	youngest_denied = get_hierarchy(domain, request->layer_plus_one - 1);
+	missing = request->access;
+	if (missing) {
+		size_t youngest_layer;
+
+		/* Gets the nearest domain that denies the request. */
+		if (request->layer_masks) {
+			youngest_layer = get_denied_layer(
+				domain, &missing, request->layer_masks,
+				request->layer_masks_size);
+		} else {
+			/* This will change with the next commit. */
+			WARN_ON_ONCE(1);
+			youngest_layer = domain->num_layers;
+		}
+		youngest_denied = get_hierarchy(domain, youngest_layer);
+	} else {
+		youngest_denied =
+			get_hierarchy(domain, request->layer_plus_one - 1);
+	}
 
 	/*
 	 * Consistently keeps track of the number of denied access requests
@@ -163,7 +327,7 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
 		return;
 
 	audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
-	log_blockers(ab, request->type);
+	log_blockers(ab, request->type, missing);
 	audit_log_lsm_data(ab, &request->audit);
 	audit_log_end(ab);
 
@@ -212,6 +376,7 @@ void landlock_log_drop_domain(const struct landlock_ruleset *const domain)
 static struct kunit_case test_cases[] = {
 	/* clang-format off */
 	KUNIT_CASE(test_get_hierarchy),
+	KUNIT_CASE(test_get_denied_layer),
 	{}
 	/* clang-format on */
 };
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 745dcf7b1b6d..093791929580 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -11,11 +11,13 @@
 #include <linux/audit.h>
 #include <linux/lsm_audit.h>
 
+#include "access.h"
 #include "ruleset.h"
 
 enum landlock_request_type {
 	LANDLOCK_REQUEST_PTRACE = 1,
 	LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
+	LANDLOCK_REQUEST_FS_ACCESS,
 };
 
 /*
@@ -33,6 +35,13 @@ struct landlock_request {
 	 * extra one is useful to detect uninitialized field.
 	 */
 	size_t layer_plus_one;
+
+	/* Required field for configurable access control. */
+	access_mask_t access;
+
+	/* Required fields for requests with layer masks. */
+	const layer_mask_t (*layer_masks)[];
+	size_t layer_masks_size;
 };
 
 #ifdef CONFIG_AUDIT
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 01f9d5e78218..f38c7a60b9bf 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -730,6 +730,7 @@ static void test_is_eacces_with_write(struct kunit *const test)
  *     those identified by @access_request_parent1).  This matrix can
  *     initially refer to domain layer masks and, when the accesses for the
  *     destination and source are the same, to requested layer masks.
+ * @log_request_parent1: Audit request to fill if the related access is denied.
  * @dentry_child1: Dentry to the initial child of the parent1 path.  This
  *     pointer must be NULL for non-refer actions (i.e. not link nor rename).
  * @access_request_parent2: Similar to @access_request_parent1 but for a
@@ -738,6 +739,7 @@ static void test_is_eacces_with_write(struct kunit *const test)
  *     the source.  Must be set to 0 when using a simple path request.
  * @layer_masks_parent2: Similar to @layer_masks_parent1 but for a refer
  *     action.  This must be NULL otherwise.
+ * @log_request_parent2: Audit request to fill if the related access is denied.
  * @dentry_child2: Dentry to the initial child of the parent2 path.  This
  *     pointer is only set for RENAME_EXCHANGE actions and must be NULL
  *     otherwise.
@@ -757,10 +759,12 @@ static bool is_access_to_paths_allowed(
 	const struct path *const path,
 	const access_mask_t access_request_parent1,
 	layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS],
-	const struct dentry *const dentry_child1,
+	struct landlock_request *const log_request_parent1,
+	struct dentry *const dentry_child1,
 	const access_mask_t access_request_parent2,
 	layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS],
-	const struct dentry *const dentry_child2)
+	struct landlock_request *const log_request_parent2,
+	struct dentry *const dentry_child2)
 {
 	bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
 	     child1_is_directory = true, child2_is_directory = true;
@@ -922,6 +926,25 @@ static bool is_access_to_paths_allowed(
 	}
 	path_put(&walker_path);
 
+	if (!allowed_parent1) {
+		log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS,
+		log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH,
+		log_request_parent1->audit.u.path = *path;
+		log_request_parent1->access = access_masked_parent1;
+		log_request_parent1->layer_masks = layer_masks_parent1;
+		log_request_parent1->layer_masks_size =
+			ARRAY_SIZE(*layer_masks_parent1);
+	}
+
+	if (!allowed_parent2) {
+		log_request_parent2->type = LANDLOCK_REQUEST_FS_ACCESS,
+		log_request_parent2->audit.type = LSM_AUDIT_DATA_PATH,
+		log_request_parent2->audit.u.path = *path;
+		log_request_parent2->access = access_masked_parent2;
+		log_request_parent2->layer_masks = layer_masks_parent2;
+		log_request_parent2->layer_masks_size =
+			ARRAY_SIZE(*layer_masks_parent2);
+	}
 	return allowed_parent1 && allowed_parent2;
 }
 
@@ -930,6 +953,7 @@ static int current_check_access_path(const struct path *const path,
 {
 	const struct landlock_ruleset *const dom = get_current_fs_domain();
 	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
+	struct landlock_request request = {};
 
 	if (!dom)
 		return 0;
@@ -937,9 +961,10 @@ static int current_check_access_path(const struct path *const path,
 	access_request = landlock_init_layer_masks(
 		dom, access_request, &layer_masks, LANDLOCK_KEY_INODE);
 	if (is_access_to_paths_allowed(dom, path, access_request, &layer_masks,
-				       NULL, 0, NULL, NULL))
+				       &request, NULL, 0, NULL, NULL, NULL))
 		return 0;
 
+	landlock_log_denial(dom, &request);
 	return -EACCES;
 }
 
@@ -1108,6 +1133,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
 	struct dentry *old_parent;
 	layer_mask_t layer_masks_parent1[LANDLOCK_NUM_ACCESS_FS] = {},
 		     layer_masks_parent2[LANDLOCK_NUM_ACCESS_FS] = {};
+	struct landlock_request request1 = {}, request2 = {};
 
 	if (!dom)
 		return 0;
@@ -1139,10 +1165,13 @@ static int current_check_refer_path(struct dentry *const old_dentry,
 		access_request_parent1 = landlock_init_layer_masks(
 			dom, access_request_parent1 | access_request_parent2,
 			&layer_masks_parent1, LANDLOCK_KEY_INODE);
-		if (is_access_to_paths_allowed(
-			    dom, new_dir, access_request_parent1,
-			    &layer_masks_parent1, NULL, 0, NULL, NULL))
+		if (is_access_to_paths_allowed(dom, new_dir,
+					       access_request_parent1,
+					       &layer_masks_parent1, &request1,
+					       NULL, 0, NULL, NULL, NULL))
 			return 0;
+
+		landlock_log_denial(dom, &request1);
 		return -EACCES;
 	}
 
@@ -1177,12 +1206,22 @@ static int current_check_refer_path(struct dentry *const old_dentry,
 	 * parent access rights.  This will be useful to compare with the
 	 * destination parent access rights.
 	 */
-	if (is_access_to_paths_allowed(
-		    dom, &mnt_dir, access_request_parent1, &layer_masks_parent1,
-		    old_dentry, access_request_parent2, &layer_masks_parent2,
-		    exchange ? new_dentry : NULL))
+	if (is_access_to_paths_allowed(dom, &mnt_dir, access_request_parent1,
+				       &layer_masks_parent1, &request1,
+				       old_dentry, access_request_parent2,
+				       &layer_masks_parent2, &request2,
+				       exchange ? new_dentry : NULL))
 		return 0;
 
+	if (request1.access) {
+		request1.audit.u.path.dentry = old_parent;
+		landlock_log_denial(dom, &request1);
+	}
+	if (request2.access) {
+		request2.audit.u.path.dentry = new_dir->dentry;
+		landlock_log_denial(dom, &request2);
+	}
+
 	/*
 	 * This prioritizes EACCES over EXDEV for all actions, including
 	 * renames with RENAME_EXCHANGE.
@@ -1562,6 +1601,7 @@ static int hook_file_open(struct file *const file)
 	const struct landlock_ruleset *const dom =
 		landlock_get_applicable_domain(
 			landlock_cred(file->f_cred)->domain, any_fs);
+	struct landlock_request request = {};
 
 	if (!dom)
 		return 0;
@@ -1587,7 +1627,7 @@ static int hook_file_open(struct file *const file)
 		    dom, &file->f_path,
 		    landlock_init_layer_masks(dom, full_access_request,
 					      &layer_masks, LANDLOCK_KEY_INODE),
-		    &layer_masks, NULL, 0, NULL, NULL)) {
+		    &layer_masks, &request, NULL, 0, NULL, NULL, NULL)) {
 		allowed_access = full_access_request;
 	} else {
 		unsigned long access_bit;
@@ -1617,6 +1657,9 @@ static int hook_file_open(struct file *const file)
 	if ((open_access_request & allowed_access) == open_access_request)
 		return 0;
 
+	/* Sets access to reflect the actual request. */
+	request.access = open_access_request;
+	landlock_log_denial(dom, &request);
 	return -EACCES;
 }
 
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 15/30] landlock: Log truncate and IOCTL denials
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (13 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 14/30] landlock: Log file-related denials Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 16/30] landlock: Log TCP bind and connect denials Mickaël Salaün
                   ` (14 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add audit support to the file_truncate and file_ioctl hooks.

Add a deny_masks_t type and related helpers to store the domain's layer
level per optional access rights (i.e. LANDLOCK_ACCESS_FS_TRUNCATE and
LANDLOCK_ACCESS_FS_IOCTL_DEV) when opening a file, which cannot be
inferred later.  In practice, the landlock_file_security blob size is
unchanged because this new one-byte deny_masks field follows the
existing two-bytes allowed_access field.

Implementing deny_masks_t with a bitfield instead of a struct enables a
generic implementation to store and extract layer levels.

Add KUnit tests to check the identification of a layer level from a
deny_masks_t, and the computation of a deny_masks_t from an access right
with its layer level or a layer_mask_t array.

Audit event sample:

  type=LANDLOCK_DENY msg=audit(1729738800.349:44): domain=195ba459b blockers=fs.ioctl_dev path="/dev/tty" dev="devtmpfs" ino=9

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-16-mic@digikod.net
---

Changes since v3:
- Rename get_layer_from_deny_masks().

Changes since v2:
- Fix !CONFIG_AUDIT build warning.
- Rename ACCESS_FS_OPTIONAL to _LANDLOCK_ACCESS_FS_OPTIONAL.
---
 security/landlock/access.h |  23 +++++++
 security/landlock/audit.c  | 102 ++++++++++++++++++++++++++--
 security/landlock/audit.h  |   4 ++
 security/landlock/domain.c | 133 +++++++++++++++++++++++++++++++++++++
 security/landlock/domain.h |   8 +++
 security/landlock/fs.c     |  51 ++++++++++++++
 security/landlock/fs.h     |   9 +++
 7 files changed, 325 insertions(+), 5 deletions(-)

diff --git a/security/landlock/access.h b/security/landlock/access.h
index 74fd8f399fbd..1eaaafa63178 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -28,6 +28,12 @@
 	LANDLOCK_ACCESS_FS_REFER)
 /* clang-format on */
 
+/* clang-format off */
+#define _LANDLOCK_ACCESS_FS_OPTIONAL ( \
+	LANDLOCK_ACCESS_FS_TRUNCATE | \
+	LANDLOCK_ACCESS_FS_IOCTL_DEV)
+/* clang-format on */
+
 typedef u16 access_mask_t;
 
 /* Makes sure all filesystem access rights can be stored. */
@@ -60,6 +66,23 @@ typedef u16 layer_mask_t;
 /* Makes sure all layers can be checked. */
 static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
 
+/*
+ * Tracks domains responsible of a denied access.  This is required to avoid
+ * storing in each object the full layer_masks[] required by update_request().
+ */
+typedef u8 deny_masks_t;
+
+/*
+ * Makes sure all optional access rights can be tied to a layer index (cf.
+ * get_deny_mask).
+ */
+static_assert(BITS_PER_TYPE(deny_masks_t) >=
+	      (HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1) *
+	       HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL)));
+
+/* LANDLOCK_MAX_NUM_LAYERS must be a power of two (cf. deny_masks_t assert). */
+static_assert(HWEIGHT(LANDLOCK_MAX_NUM_LAYERS) == 1);
+
 /* Upgrades with all initially denied by default access rights. */
 static inline struct access_masks
 landlock_upgrade_handled_access_masks(struct access_masks access_masks)
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 987b4b15e0d7..89446e913d9f 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -13,9 +13,11 @@
 #include <linux/uidgid.h>
 #include <uapi/linux/landlock.h>
 
+#include "access.h"
 #include "audit.h"
 #include "common.h"
 #include "domain.h"
+#include "fs.h"
 #include "ruleset.h"
 
 static const char *const fs_access_strings[] = {
@@ -253,22 +255,111 @@ static void test_get_denied_layer(struct kunit *const test)
 
 #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_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 unsigned long access_opt = all_existing_optional_access;
+	const unsigned long access_req = *access_request;
+	access_mask_t missing = 0;
+	size_t youngest_layer = 0;
+	size_t access_index = 0;
+	unsigned long access_bit;
+
+	/* 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)) {
+		if (access_req & BIT(access_bit)) {
+			const size_t layer =
+				(deny_masks >> (access_index * 4)) &
+				(LANDLOCK_MAX_NUM_LAYERS - 1);
+
+			if (layer > youngest_layer) {
+				youngest_layer = layer;
+				missing = BIT(access_bit);
+			} else if (layer == youngest_layer) {
+				missing |= BIT(access_bit);
+			}
+		}
+		access_index++;
+	}
+
+	*access_request = missing;
+	return youngest_layer;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_layer_from_deny_masks(struct kunit *const test)
+{
+	deny_masks_t deny_mask;
+	access_mask_t access;
+
+	/* truncate:0 ioctl_dev:2 */
+	deny_mask = 0x20;
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE;
+	KUNIT_EXPECT_EQ(test, 0,
+			get_layer_from_deny_masks(&access,
+						  _LANDLOCK_ACCESS_FS_OPTIONAL,
+						  deny_mask));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+
+	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));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+
+	/* truncate:15 ioctl_dev:15 */
+	deny_mask = 0xff;
+
+	access = LANDLOCK_ACCESS_FS_TRUNCATE;
+	KUNIT_EXPECT_EQ(test, 15,
+			get_layer_from_deny_masks(&access,
+						  _LANDLOCK_ACCESS_FS_OPTIONAL,
+						  deny_mask));
+	KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+
+	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));
+	KUNIT_EXPECT_EQ(test, access,
+			LANDLOCK_ACCESS_FS_TRUNCATE |
+				LANDLOCK_ACCESS_FS_IOCTL_DEV);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
 static bool is_valid_request(const struct landlock_request *const request)
 {
 	if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
 		return false;
 
 	if (request->access) {
-		if (WARN_ON_ONCE(!request->layer_masks))
+		if (WARN_ON_ONCE(!(!!request->layer_masks ^
+				   !!request->all_existing_optional_access)))
 			return false;
 	} else {
-		if (WARN_ON_ONCE(request->layer_masks))
+		if (WARN_ON_ONCE(request->layer_masks ||
+				 request->all_existing_optional_access))
 			return false;
 	}
 
 	if (WARN_ON_ONCE(!!request->layer_masks ^ !!request->layer_masks_size))
 		return false;
 
+	if (request->deny_masks) {
+		if (WARN_ON_ONCE(!request->all_existing_optional_access))
+			return false;
+	}
+
 	return true;
 }
 
@@ -301,9 +392,9 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
 				domain, &missing, request->layer_masks,
 				request->layer_masks_size);
 		} else {
-			/* This will change with the next commit. */
-			WARN_ON_ONCE(1);
-			youngest_layer = domain->num_layers;
+			youngest_layer = get_layer_from_deny_masks(
+				&missing, request->all_existing_optional_access,
+				request->deny_masks);
 		}
 		youngest_denied = get_hierarchy(domain, youngest_layer);
 	} else {
@@ -377,6 +468,7 @@ static struct kunit_case test_cases[] = {
 	/* clang-format off */
 	KUNIT_CASE(test_get_hierarchy),
 	KUNIT_CASE(test_get_denied_layer),
+	KUNIT_CASE(test_get_layer_from_deny_masks),
 	{}
 	/* clang-format on */
 };
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 093791929580..762cdbd10b3a 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -42,6 +42,10 @@ struct landlock_request {
 	/* Required fields for requests with layer masks. */
 	const layer_mask_t (*layer_masks)[];
 	size_t layer_masks_size;
+
+	/* Required fields for requests with deny masks. */
+	const access_mask_t all_existing_optional_access;
+	deny_masks_t deny_masks;
 };
 
 #ifdef CONFIG_AUDIT
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index d0cb9f8683b4..f1a0d1b9af7c 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -7,6 +7,9 @@
  * Copyright © 2024-2025 Microsoft Corporation
  */
 
+#include <kunit/test.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
 #include <linux/cred.h>
 #include <linux/file.h>
 #include <linux/landlock.h>
@@ -16,6 +19,8 @@
 #include <linux/sched.h>
 #include <linux/timekeeping.h>
 
+#include "access.h"
+#include "common.h"
 #include "domain.h"
 #include "fs.h"
 #include "id.h"
@@ -153,4 +158,132 @@ int landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
 	return 0;
 }
 
+static deny_masks_t
+get_layer_deny_mask(const access_mask_t all_existing_optional_access,
+		    const unsigned long access_bit, const size_t layer)
+{
+	unsigned long access_weight;
+
+	/* This may require change with new object types. */
+	WARN_ON_ONCE(all_existing_optional_access !=
+		     _LANDLOCK_ACCESS_FS_OPTIONAL);
+
+	if (WARN_ON_ONCE(layer >= LANDLOCK_MAX_NUM_LAYERS))
+		return 0;
+
+	access_weight = hweight_long(all_existing_optional_access &
+				     GENMASK(access_bit, 0));
+	if (WARN_ON_ONCE(access_weight < 1))
+		return 0;
+
+	return layer
+	       << ((access_weight - 1) * HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1));
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_layer_deny_mask(struct kunit *const test)
+{
+	const unsigned long truncate = BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE);
+	const unsigned long ioctl_dev = BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV);
+
+	KUNIT_EXPECT_EQ(test, 0,
+			get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+					    truncate, 0));
+	KUNIT_EXPECT_EQ(test, 0x3,
+			get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+					    truncate, 3));
+
+	KUNIT_EXPECT_EQ(test, 0,
+			get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+					    ioctl_dev, 0));
+	KUNIT_EXPECT_EQ(test, 0xf0,
+			get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+					    ioctl_dev, 15));
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+deny_masks_t
+landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
+			const access_mask_t optional_access,
+			const layer_mask_t (*const layer_masks)[],
+			const size_t layer_masks_size)
+{
+	const unsigned long access_opt = optional_access;
+	unsigned long access_bit;
+	deny_masks_t deny_masks = 0;
+
+	/* This may require change with new object types. */
+	WARN_ON_ONCE(access_opt !=
+		     (optional_access & all_existing_optional_access));
+
+	if (WARN_ON_ONCE(!layer_masks))
+		return 0;
+
+	if (WARN_ON_ONCE(!access_opt))
+		return 0;
+
+	for_each_set_bit(access_bit, &access_opt, layer_masks_size) {
+		const layer_mask_t mask = (*layer_masks)[access_bit];
+
+		if (!mask)
+			continue;
+
+		/* __fls(1) == 0 */
+		deny_masks |= get_layer_deny_mask(all_existing_optional_access,
+						  access_bit, __fls(mask));
+	}
+	return deny_masks;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_landlock_get_deny_masks(struct kunit *const test)
+{
+	const layer_mask_t layers1[BITS_PER_TYPE(access_mask_t)] = {
+		[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0) |
+							  BIT_ULL(9),
+		[BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = BIT_ULL(1),
+		[BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = BIT_ULL(2) |
+							    BIT_ULL(0),
+	};
+
+	KUNIT_EXPECT_EQ(test, 0x1,
+			landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
+						LANDLOCK_ACCESS_FS_TRUNCATE,
+						&layers1, ARRAY_SIZE(layers1)));
+	KUNIT_EXPECT_EQ(test, 0x20,
+			landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
+						LANDLOCK_ACCESS_FS_IOCTL_DEV,
+						&layers1, ARRAY_SIZE(layers1)));
+	KUNIT_EXPECT_EQ(
+		test, 0x21,
+		landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
+					LANDLOCK_ACCESS_FS_TRUNCATE |
+						LANDLOCK_ACCESS_FS_IOCTL_DEV,
+					&layers1, ARRAY_SIZE(layers1)));
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static struct kunit_case test_cases[] = {
+	/* clang-format off */
+	KUNIT_CASE(test_get_layer_deny_mask),
+	KUNIT_CASE(test_landlock_get_deny_masks),
+	{}
+	/* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+	.name = "landlock_domain",
+	.test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
 #endif /* CONFIG_AUDIT */
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index a7fda9c6a5a3..e1129ab09a1b 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -18,6 +18,8 @@
 #include <linux/sched.h>
 #include <linux/time64.h>
 
+#include "access.h"
+
 enum landlock_log_status {
 	LANDLOCK_LOG_PENDING = 0,
 	LANDLOCK_LOG_RECORDED,
@@ -103,6 +105,12 @@ struct landlock_hierarchy {
 
 int landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy);
 
+deny_masks_t
+landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
+			const access_mask_t optional_access,
+			const layer_mask_t (*const layer_masks)[],
+			size_t layer_masks_size);
+
 #else /* CONFIG_AUDIT */
 
 static inline int
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index f38c7a60b9bf..6404961ecbc7 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -246,6 +246,17 @@ is_masked_device_ioctl_compat(const unsigned int cmd)
 	}
 }
 
+static void
+update_request(struct landlock_request *const request,
+	       const struct landlock_file_security *const file_security,
+	       const access_mask_t access)
+{
+	request->access = access;
+#ifdef CONFIG_AUDIT
+	request->deny_masks = file_security->deny_masks;
+#endif /* CONFIG_AUDIT */
+}
+
 /* Ruleset management */
 
 static struct landlock_object *get_inode_object(struct inode *const inode)
@@ -1653,6 +1664,11 @@ static int hook_file_open(struct file *const file)
 	 * file access rights in the opened struct file.
 	 */
 	landlock_file(file)->allowed_access = allowed_access;
+#ifdef CONFIG_AUDIT
+	landlock_file(file)->deny_masks = landlock_get_deny_masks(
+		_LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks,
+		ARRAY_SIZE(layer_masks));
+#endif /* CONFIG_AUDIT */
 
 	if ((open_access_request & allowed_access) == open_access_request)
 		return 0;
@@ -1665,6 +1681,15 @@ static int hook_file_open(struct file *const file)
 
 static int hook_file_truncate(struct file *const file)
 {
+	struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_FS_ACCESS,
+		.audit = {
+			.type = LSM_AUDIT_DATA_FILE,
+			.u.file = file,
+		},
+		.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
+	};
+
 	/*
 	 * Allows truncation if the truncate right was available at the time of
 	 * opening the file, to get a consistent access check as for read, write
@@ -1677,12 +1702,24 @@ static int hook_file_truncate(struct file *const file)
 	 */
 	if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE)
 		return 0;
+
+	update_request(&request, landlock_file(file),
+		       LANDLOCK_ACCESS_FS_TRUNCATE);
+	landlock_log_denial(landlock_cred(file->f_cred)->domain, &request);
 	return -EACCES;
 }
 
 static int hook_file_ioctl(struct file *file, unsigned int cmd,
 			   unsigned long arg)
 {
+	struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_FS_ACCESS,
+		.audit = {
+			.type = LSM_AUDIT_DATA_FILE,
+			.u.file = file,
+		},
+		.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
+	};
 	access_mask_t allowed_access = landlock_file(file)->allowed_access;
 
 	/*
@@ -1700,12 +1737,23 @@ static int hook_file_ioctl(struct file *file, unsigned int cmd,
 	if (is_masked_device_ioctl(cmd))
 		return 0;
 
+	update_request(&request, landlock_file(file),
+		       LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	landlock_log_denial(landlock_cred(file->f_cred)->domain, &request);
 	return -EACCES;
 }
 
 static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
 				  unsigned long arg)
 {
+	struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_FS_ACCESS,
+		.audit = {
+			.type = LSM_AUDIT_DATA_FILE,
+			.u.file = file,
+		},
+		.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
+	};
 	access_mask_t allowed_access = landlock_file(file)->allowed_access;
 
 	/*
@@ -1723,6 +1771,9 @@ static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
 	if (is_masked_device_ioctl_compat(cmd))
 		return 0;
 
+	update_request(&request, landlock_file(file),
+		       LANDLOCK_ACCESS_FS_IOCTL_DEV);
+	landlock_log_denial(landlock_cred(file->f_cred)->domain, &request);
 	return -EACCES;
 }
 
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index d445f411c26a..9f52c9b37898 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -53,6 +53,15 @@ struct landlock_file_security {
 	 * needed to authorize later operations on the open file.
 	 */
 	access_mask_t allowed_access;
+
+#ifdef CONFIG_AUDIT
+	/**
+	 * @deny_masks: Domain layer levels that deny an optional access (see
+	 * _LANDLOCK_ACCESS_FS_OPTIONAL).
+	 */
+	deny_masks_t deny_masks;
+#endif /* CONFIG_AUDIT */
+
 	/**
 	 * @fown_domain: Domain of the task that set the PID that may receive a
 	 * signal e.g., SIGURG when writing MSG_OOB to the related socket.
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 16/30] landlock: Log TCP bind and connect denials
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (14 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 15/30] landlock: Log truncate and IOCTL denials Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 17/30] landlock: Log scoped denials Mickaël Salaün
                   ` (13 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add audit support to socket_bind and socket_connect hooks.

The related blockers are:
- net.bind_tcp
- net.connect_tcp

Audit event sample:

  type=LANDLOCK_DENY msg=audit(1729738800.349:44): domain=195ba459b blockers=net.connect_tcp daddr=127.0.0.1 dest=80

Cc: Günther Noack <gnoack@google.com>
Cc: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-17-mic@digikod.net
---

Changes since v3:
- Rename blockers from net_* to net.*

Changes since v2:
- Remove potentially superfluous IPv6 saddr log, spotted by Francis
  Laniel.
- Cosmetic improvements.
---
 security/landlock/audit.c | 12 +++++++++
 security/landlock/audit.h |  1 +
 security/landlock/net.c   | 51 ++++++++++++++++++++++++++++++++++++---
 3 files changed, 60 insertions(+), 4 deletions(-)

diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 89446e913d9f..2744e3d4fe73 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -41,6 +41,13 @@ static const char *const fs_access_strings[] = {
 
 static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
 
+static const char *const net_access_strings[] = {
+	[BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp",
+	[BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp",
+};
+
+static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);
+
 static __attribute_const__ const char *
 get_blocker(const enum landlock_request_type type,
 	    const unsigned long access_bit)
@@ -58,6 +65,11 @@ get_blocker(const enum landlock_request_type type,
 		if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings)))
 			return "unknown";
 		return fs_access_strings[access_bit];
+
+	case LANDLOCK_REQUEST_NET_ACCESS:
+		if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(net_access_strings)))
+			return "unknown";
+		return net_access_strings[access_bit];
 	}
 
 	WARN_ON_ONCE(1);
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 762cdbd10b3a..9a3697b901b5 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -18,6 +18,7 @@ enum landlock_request_type {
 	LANDLOCK_REQUEST_PTRACE = 1,
 	LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
 	LANDLOCK_REQUEST_FS_ACCESS,
+	LANDLOCK_REQUEST_NET_ACCESS,
 };
 
 /*
diff --git a/security/landlock/net.c b/security/landlock/net.c
index d5dcc4407a19..45c80fa417c4 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -7,10 +7,12 @@
  */
 
 #include <linux/in.h>
+#include <linux/lsm_audit.h>
 #include <linux/net.h>
 #include <linux/socket.h>
 #include <net/ipv6.h>
 
+#include "audit.h"
 #include "common.h"
 #include "cred.h"
 #include "limits.h"
@@ -57,6 +59,10 @@ static int current_check_access_socket(struct socket *const sock,
 	const struct landlock_ruleset *const dom =
 		landlock_get_applicable_domain(landlock_get_current_domain(),
 					       any_net);
+	struct lsm_network_audit audit_net = {};
+	struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_NET_ACCESS,
+	};
 
 	if (!dom)
 		return 0;
@@ -73,18 +79,48 @@ static int current_check_access_socket(struct socket *const sock,
 
 	switch (address->sa_family) {
 	case AF_UNSPEC:
-	case AF_INET:
+	case AF_INET: {
+		const struct sockaddr_in *addr4;
+
 		if (addrlen < sizeof(struct sockaddr_in))
 			return -EINVAL;
-		port = ((struct sockaddr_in *)address)->sin_port;
+
+		addr4 = (struct sockaddr_in *)address;
+		port = addr4->sin_port;
+
+		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+			audit_net.dport = port;
+			audit_net.v4info.daddr = addr4->sin_addr.s_addr;
+		} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+			audit_net.sport = port;
+			audit_net.v4info.saddr = addr4->sin_addr.s_addr;
+		} else {
+			WARN_ON_ONCE(1);
+		}
 		break;
+	}
 
 #if IS_ENABLED(CONFIG_IPV6)
-	case AF_INET6:
+	case AF_INET6: {
+		const struct sockaddr_in6 *addr6;
+
 		if (addrlen < SIN6_LEN_RFC2133)
 			return -EINVAL;
-		port = ((struct sockaddr_in6 *)address)->sin6_port;
+
+		addr6 = (struct sockaddr_in6 *)address;
+		port = addr6->sin6_port;
+
+		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+			audit_net.dport = port;
+			audit_net.v6info.daddr = addr6->sin6_addr;
+		} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+			audit_net.sport = port;
+			audit_net.v6info.saddr = addr6->sin6_addr;
+		} else {
+			WARN_ON_ONCE(1);
+		}
 		break;
+	}
 #endif /* IS_ENABLED(CONFIG_IPV6) */
 
 	default:
@@ -153,6 +189,13 @@ static int current_check_access_socket(struct socket *const sock,
 				   ARRAY_SIZE(layer_masks)))
 		return 0;
 
+	audit_net.family = address->sa_family;
+	request.audit.type = LSM_AUDIT_DATA_NET;
+	request.audit.u.net = &audit_net;
+	request.access = access_request;
+	request.layer_masks = &layer_masks;
+	request.layer_masks_size = ARRAY_SIZE(layer_masks);
+	landlock_log_denial(dom, &request);
 	return -EACCES;
 }
 
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 17/30] landlock: Log scoped denials
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (15 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 16/30] landlock: Log TCP bind and connect denials Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 18/30] landlock: Control log events with LANDLOCK_RESTRICT_SELF_QUIET Mickaël Salaün
                   ` (12 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add audit support for unix_stream_connect, unix_may_send, task_kill, and
file_send_sigiotask hooks.

The related blockers are:
- scope.abstract_unix_socket
- scope.signal

Audit event sample for abstract unix socket:

  type=LANDLOCK_DENY msg=audit(1729738800.268:30): domain=195ba459b blockers=scope.abstract_unix_socket path=00666F6F

Audit event sample for signal:

  type=LANDLOCK_DENY msg=audit(1729738800.291:31): domain=195ba459b blockers=scope.signal opid=1 ocomm="systemd"

Cc: Günther Noack <gnoack@google.com>
Cc: Tahera Fahimi <fahimitahera@gmail.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-18-mic@digikod.net
---

Changes since v3:
- Cosmetic change to the "scope.*" blocker names.
- Extend commit message.

Changes since v1:
- New patch.
---
 security/landlock/audit.c |  8 ++++++
 security/landlock/audit.h |  2 ++
 security/landlock/task.c  | 58 ++++++++++++++++++++++++++++++++++++---
 3 files changed, 64 insertions(+), 4 deletions(-)

diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 2744e3d4fe73..7a2183ac9add 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -70,6 +70,14 @@ get_blocker(const enum landlock_request_type type,
 		if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(net_access_strings)))
 			return "unknown";
 		return net_access_strings[access_bit];
+
+	case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
+		WARN_ON_ONCE(access_bit != -1);
+		return "scope.abstract_unix_socket";
+
+	case LANDLOCK_REQUEST_SCOPE_SIGNAL:
+		WARN_ON_ONCE(access_bit != -1);
+		return "scope.signal";
 	}
 
 	WARN_ON_ONCE(1);
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 9a3697b901b5..dd5deaf7dc4d 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -19,6 +19,8 @@ enum landlock_request_type {
 	LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
 	LANDLOCK_REQUEST_FS_ACCESS,
 	LANDLOCK_REQUEST_NET_ACCESS,
+	LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
+	LANDLOCK_REQUEST_SCOPE_SIGNAL,
 };
 
 /*
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 4fef963274fd..1b3d5de89e14 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -263,13 +263,27 @@ static int hook_unix_stream_connect(struct sock *const sock,
 	const struct landlock_ruleset *const dom =
 		landlock_get_applicable_domain(landlock_get_current_domain(),
 					       unix_scope);
+	struct lsm_network_audit audit_net = {
+		.sk = other,
+	};
+	struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
+		.audit = {
+			.type = LSM_AUDIT_DATA_NET,
+			.u.net = &audit_net,
+		},
+	};
 
 	/* Quick return for non-landlocked tasks. */
 	if (!dom)
 		return 0;
 
-	if (is_abstract_socket(other) && sock_is_scoped(other, dom))
+	if (is_abstract_socket(other) && sock_is_scoped(other, dom)) {
+		request.layer_plus_one =
+			landlock_match_layer_level(dom, unix_scope) + 1;
+		landlock_log_denial(dom, &request);
 		return -EPERM;
+	}
 
 	return 0;
 }
@@ -280,6 +294,16 @@ static int hook_unix_may_send(struct socket *const sock,
 	const struct landlock_ruleset *const dom =
 		landlock_get_applicable_domain(landlock_get_current_domain(),
 					       unix_scope);
+	struct lsm_network_audit audit_net = {
+		.sk = other->sk,
+	};
+	struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
+		.audit = {
+			.type = LSM_AUDIT_DATA_NET,
+			.u.net = &audit_net,
+		},
+	};
 
 	if (!dom)
 		return 0;
@@ -291,8 +315,12 @@ static int hook_unix_may_send(struct socket *const sock,
 	if (unix_peer(sock->sk) == other->sk)
 		return 0;
 
-	if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom))
+	if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom)) {
+		request.layer_plus_one =
+			landlock_match_layer_level(dom, unix_scope) + 1;
+		landlock_log_denial(dom, &request);
 		return -EPERM;
+	}
 
 	return 0;
 }
@@ -307,6 +335,13 @@ static int hook_task_kill(struct task_struct *const p,
 {
 	bool is_scoped;
 	const struct landlock_ruleset *dom;
+	struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_SCOPE_SIGNAL,
+		.audit = {
+			.type = LSM_AUDIT_DATA_TASK,
+			.u.tsk = p,
+		},
+	};
 
 	if (cred) {
 		/* Dealing with USB IO. */
@@ -324,8 +359,12 @@ static int hook_task_kill(struct task_struct *const p,
 	is_scoped = domain_is_scoped(dom, landlock_get_task_domain(p),
 				     LANDLOCK_SCOPE_SIGNAL);
 	rcu_read_unlock();
-	if (is_scoped)
+	if (is_scoped) {
+		request.layer_plus_one =
+			landlock_match_layer_level(dom, signal_scope) + 1;
+		landlock_log_denial(dom, &request);
 		return -EPERM;
+	}
 
 	return 0;
 }
@@ -334,6 +373,13 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
 				    struct fown_struct *fown, int signum)
 {
 	const struct landlock_ruleset *dom;
+	struct landlock_request request = {
+		.type = LANDLOCK_REQUEST_SCOPE_SIGNAL,
+		.audit = {
+			.type = LSM_AUDIT_DATA_TASK,
+			.u.tsk = tsk,
+		},
+	};
 	bool is_scoped = false;
 
 	/* Lock already held by send_sigio() and send_sigurg(). */
@@ -349,8 +395,12 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
 	is_scoped = domain_is_scoped(dom, landlock_get_task_domain(tsk),
 				     LANDLOCK_SCOPE_SIGNAL);
 	rcu_read_unlock();
-	if (is_scoped)
+	if (is_scoped) {
+		request.layer_plus_one =
+			landlock_match_layer_level(dom, signal_scope) + 1;
+		landlock_log_denial(dom, &request);
 		return -EPERM;
+	}
 
 	return 0;
 }
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 18/30] landlock: Control log events with LANDLOCK_RESTRICT_SELF_QUIET
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (16 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 17/30] landlock: Log scoped denials Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 19/30] samples/landlock: Do not log denials from the sandboxer by default Mickaël Salaün
                   ` (11 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Most of the time we want to log denied access because they should not
happen and such information helps diagnose issues.  However, when
sandboxing processes that we know will try to access denied resources
(e.g. unknown, bogus, or malicious binary), we might want to not log
related access requests that might fill up logs.

To disable any log for a specific Landlock domain, add a
LANDLOCK_RESTRICT_SELF_QUIET optional flag to the
landlock_restrict_self() system call.

Because this flag is set for a specific Landlock domain, it makes it
possible to selectively mask some access requests that would be logged
by a parent domain, which might be handy for unprivileged processes to
limit logs.  However, system administrators should still use the audit
filtering mechanism.

There is intentionally no audit nor sysctl configuration to re-enable
these quiet domains.  This is delegated to the user space program.

Increment the Landlock ABI version to reflect this interface change.

Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Closes: https://github.com/landlock-lsm/linux/issues/3
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-19-mic@digikod.net
---

Changes since v3:
- Rename LANDLOCK_RESTRICT_SELF_LOGLESS to LANDLOCK_RESTRICT_SELF_QUIET.
  "quiet" is already used by kernel's cmdline to disable most log
  messages, so this name makes sense for Landlock.
- Improve the LANDLOCK_ABI_VERSION comment.

Changes since v2:
- Update ABI version test.
---
 Documentation/userspace-api/landlock.rst     |  2 +-
 include/uapi/linux/landlock.h                | 14 ++++++++++
 security/landlock/audit.c                    |  3 +++
 security/landlock/domain.h                   |  1 +
 security/landlock/syscalls.c                 | 28 ++++++++++++++++----
 tools/testing/selftests/landlock/base_test.c |  2 +-
 6 files changed, 43 insertions(+), 7 deletions(-)

diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index d639c61cb472..a7c1ebef2c79 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -8,7 +8,7 @@ Landlock: unprivileged access control
 =====================================
 
 :Author: Mickaël Salaün
-:Date: October 2024
+:Date: January 2025
 
 The goal of Landlock is to enable restriction of ambient rights (e.g. global
 filesystem or network access) for a set of processes.  Because Landlock
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 33745642f787..b7f78abd6ddd 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -62,6 +62,20 @@ struct landlock_ruleset_attr {
 #define LANDLOCK_CREATE_RULESET_VERSION			(1U << 0)
 /* clang-format on */
 
+/*
+ * sys_landlock_restrict_self() flags:
+ *
+ * - %LANDLOCK_RESTRICT_SELF_QUIET: Do not create any log related to the
+ *   enforced restrictions.  This should only be set by tools launching unknown
+ *   or untrusted programs (e.g. a sandbox tool, container runtime, system
+ *   service manager).  Because programs sandboxing themselves should fix any
+ *   denied access, they should not set this flag to be aware of potential
+ *   issues reported by system's logs (i.e. audit).
+ */
+/* clang-format off */
+#define LANDLOCK_RESTRICT_SELF_QUIET			(1U << 0)
+/* clang-format on */
+
 /**
  * enum landlock_rule_type - Landlock rule type
  *
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 7a2183ac9add..cc01a0d663f3 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -422,6 +422,9 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
 			get_hierarchy(domain, request->layer_plus_one - 1);
 	}
 
+	if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
+		return;
+
 	/*
 	 * Consistently keeps track of the number of denied access requests
 	 * even if audit is currently disabled or if audit rules currently
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index e1129ab09a1b..7176043bd0ff 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -23,6 +23,7 @@
 enum landlock_log_status {
 	LANDLOCK_LOG_PENDING = 0,
 	LANDLOCK_LOG_RECORDED,
+	LANDLOCK_LOG_DISABLED,
 };
 
 /**
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 4ed8e70c25ed..8bc14a561e51 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -27,6 +27,7 @@
 #include <uapi/linux/landlock.h>
 
 #include "cred.h"
+#include "domain.h"
 #include "fs.h"
 #include "limits.h"
 #include "net.h"
@@ -150,7 +151,14 @@ static const struct file_operations ruleset_fops = {
 	.write = fop_dummy_write,
 };
 
-#define LANDLOCK_ABI_VERSION 6
+/*
+ * The Landlock ABI version should be incremented for each new Landlock-related
+ * user space visible change (e.g. Landlock syscalls).  This version should
+ * only be incremented once per Linux release, and the date in
+ * Documentation/userspace-api/landlock.rst should be updated to reflect the
+ * UAPI change.
+ */
+#define LANDLOCK_ABI_VERSION 7
 
 /**
  * sys_landlock_create_ruleset - Create a new ruleset
@@ -434,7 +442,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
  * sys_landlock_restrict_self - Enforce a ruleset on the calling thread
  *
  * @ruleset_fd: File descriptor tied to the ruleset to merge with the target.
- * @flags: Must be 0.
+ * @flags: Supported value: %LANDLOCK_RESTRICT_SELF_QUIET.
  *
  * This system call enables to enforce a Landlock ruleset on the current
  * thread.  Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
@@ -460,6 +468,7 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 	struct cred *new_cred;
 	struct landlock_cred_security *new_llcred;
 	int err;
+	bool is_quiet = false;
 
 	if (!is_initialized())
 		return -EOPNOTSUPP;
@@ -472,9 +481,12 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
 		return -EPERM;
 
-	/* No flag for now. */
-	if (flags)
-		return -EINVAL;
+	if (flags) {
+		if (flags == LANDLOCK_RESTRICT_SELF_QUIET)
+			is_quiet = true;
+		else
+			return -EINVAL;
+	}
 
 	/* Gets and checks the ruleset. */
 	ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ);
@@ -499,6 +511,12 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 		goto out_put_creds;
 	}
 
+	if (is_quiet) {
+#ifdef CONFIG_AUDIT
+		new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
+#endif /* CONFIG_AUDIT */
+	}
+
 	/* Replaces the old (prepared) domain. */
 	landlock_put_ruleset(new_llcred->domain);
 	new_llcred->domain = new_dom;
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 1bc16fde2e8a..fbd687691b3c 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,7 +76,7 @@ TEST(abi_version)
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
 	};
-	ASSERT_EQ(6, landlock_create_ruleset(NULL, 0,
+	ASSERT_EQ(7, landlock_create_ruleset(NULL, 0,
 					     LANDLOCK_CREATE_RULESET_VERSION));
 
 	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 19/30] samples/landlock: Do not log denials from the sandboxer by default
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (17 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 18/30] landlock: Control log events with LANDLOCK_RESTRICT_SELF_QUIET Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 20/30] selftests/landlock: Fix error message Mickaël Salaün
                   ` (10 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Do not pollute audit logs because of unknown sandboxed programs.
Indeed, the sandboxer's security policy might not be fitted to the set
of sandboxed processes that could be spawned (e.g. from a shell).

The LANDLOCK_RESTRICT_SELF_QUIET flag should be used for all similar
sandboxer tools by default.  Only natively-sandboxed programs should not
use this flag.

For test purpose, parse the LL_FORCE_LOG environment variable to still
log denials.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-20-mic@digikod.net
---

Changes since v3:
- Extend error message, suggested by Francis Laniel.

Changes since v2:
- New patch.
---
 samples/landlock/sandboxer.c | 35 ++++++++++++++++++++++++++++++++---
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 57565dfd74a2..f132869f55f5 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -58,6 +58,7 @@ static inline int landlock_restrict_self(const int ruleset_fd,
 #define ENV_TCP_BIND_NAME "LL_TCP_BIND"
 #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
 #define ENV_SCOPED_NAME "LL_SCOPED"
+#define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
 #define ENV_DELIMITER ":"
 
 static int str2num(const char *numstr, __u64 *num_dst)
@@ -288,7 +289,7 @@ static bool check_ruleset_scope(const char *const env_var,
 
 /* clang-format on */
 
-#define LANDLOCK_ABI_LAST 6
+#define LANDLOCK_ABI_LAST 7
 
 #define XSTR(s) #s
 #define STR(s) XSTR(s)
@@ -315,6 +316,9 @@ static const char help[] =
 	"  - \"a\" to restrict opening abstract unix sockets\n"
 	"  - \"s\" to restrict sending signals\n"
 	"\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"
+	"\n"
 	"Example:\n"
 	ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
 	ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
@@ -333,7 +337,7 @@ int main(const int argc, char *const argv[], char *const *const envp)
 	const char *cmd_path;
 	char *const *cmd_argv;
 	int ruleset_fd, abi;
-	char *env_port_name;
+	char *env_port_name, *env_force_log;
 	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
 	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
 
@@ -344,6 +348,8 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
 			  LANDLOCK_SCOPE_SIGNAL,
 	};
+	/* Do not pollute audit logs because of unknown sandboxed programs. */
+	int restrict_flags = LANDLOCK_RESTRICT_SELF_QUIET;
 
 	if (argc < 2) {
 		fprintf(stderr, help, argv[0]);
@@ -415,6 +421,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		/* Removes LANDLOCK_SCOPE_* for ABI < 6 */
 		ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
 					 LANDLOCK_SCOPE_SIGNAL);
+		__attribute__((fallthrough));
+	case 6:
+		/* Removes LANDLOCK_RESTRICT_SELF_QUIET for ABI < 7 */
+		restrict_flags &= ~LANDLOCK_RESTRICT_SELF_QUIET;
+
+		/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
 		fprintf(stderr,
 			"Hint: You should update the running kernel "
 			"to leverage Landlock features "
@@ -449,6 +461,23 @@ int main(const int argc, char *const argv[], char *const *const envp)
 	if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
 		return 1;
 
+	/* Enables optional logs. */
+	env_force_log = getenv(ENV_FORCE_LOG_NAME);
+	if (env_force_log) {
+		if (strcmp(env_force_log, "1") != 0) {
+			fprintf(stderr, "Unknown value for " ENV_FORCE_LOG_NAME
+					" (only \"1\" is handled)\n");
+			return 1;
+		}
+		if (!(restrict_flags & LANDLOCK_RESTRICT_SELF_QUIET)) {
+			fprintf(stderr,
+				"Audit logs not supported by current kernel\n");
+			return 1;
+		}
+		restrict_flags &= ~LANDLOCK_RESTRICT_SELF_QUIET;
+		unsetenv(ENV_FORCE_LOG_NAME);
+	}
+
 	ruleset_fd =
 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
 	if (ruleset_fd < 0) {
@@ -476,7 +505,7 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		perror("Failed to restrict privileges");
 		goto err_close_ruleset;
 	}
-	if (landlock_restrict_self(ruleset_fd, 0)) {
+	if (landlock_restrict_self(ruleset_fd, restrict_flags)) {
 		perror("Failed to enforce ruleset");
 		goto err_close_ruleset;
 	}
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 20/30] selftests/landlock: Fix error message
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (18 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 19/30] samples/landlock: Do not log denials from the sandboxer by default Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-10 11:24   ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 21/30] selftests/landlock: Add wrappers.h Mickaël Salaün
                   ` (9 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

The global variable errno may not be set in test_execute().  Do not use
it in related error message.

Cc: Günther Noack <gnoack@google.com>
Fixes: e1199815b47b ("selftests/landlock: Add user space tests")
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-21-mic@digikod.net
---

Changes since v3:
- New patch.
---
 tools/testing/selftests/landlock/fs_test.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 42ce1e79ba82..a359c0d3107f 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -2011,8 +2011,7 @@ static void test_execute(struct __test_metadata *const _metadata, const int err,
 	ASSERT_EQ(1, WIFEXITED(status));
 	ASSERT_EQ(err ? 2 : 0, WEXITSTATUS(status))
 	{
-		TH_LOG("Unexpected return code for \"%s\": %s", path,
-		       strerror(errno));
+		TH_LOG("Unexpected return code for \"%s\"", path);
 	};
 }
 
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 21/30] selftests/landlock: Add wrappers.h
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (19 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 20/30] selftests/landlock: Fix error message Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-10 11:24   ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 22/30] selftests/landlock: Add layout1.umount_sandboxer tests Mickaël Salaün
                   ` (8 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Extract syscall wrappers to make them usable by standalone binaries (see
next commit).

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-22-mic@digikod.net
---

Changes since v3:
- New patch.
---
 tools/testing/selftests/landlock/common.h   | 37 +---------------
 tools/testing/selftests/landlock/wrappers.h | 47 +++++++++++++++++++++
 2 files changed, 48 insertions(+), 36 deletions(-)
 create mode 100644 tools/testing/selftests/landlock/wrappers.h

diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 61056fa074bb..8391ab574f64 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -9,17 +9,15 @@
 
 #include <arpa/inet.h>
 #include <errno.h>
-#include <linux/landlock.h>
 #include <linux/securebits.h>
 #include <sys/capability.h>
 #include <sys/socket.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
 #include <sys/un.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
 #include "../kselftest_harness.h"
+#include "wrappers.h"
 
 #define TMP_DIR "tmp"
 
@@ -30,34 +28,6 @@
 /* TEST_F_FORK() should not be used for new tests. */
 #define TEST_F_FORK(fixture_name, test_name) TEST_F(fixture_name, test_name)
 
-#ifndef landlock_create_ruleset
-static inline int
-landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
-			const size_t size, const __u32 flags)
-{
-	return syscall(__NR_landlock_create_ruleset, attr, size, flags);
-}
-#endif
-
-#ifndef landlock_add_rule
-static inline int landlock_add_rule(const int ruleset_fd,
-				    const enum landlock_rule_type rule_type,
-				    const void *const rule_attr,
-				    const __u32 flags)
-{
-	return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
-		       flags);
-}
-#endif
-
-#ifndef landlock_restrict_self
-static inline int landlock_restrict_self(const int ruleset_fd,
-					 const __u32 flags)
-{
-	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
-}
-#endif
-
 static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
 {
 	cap_t cap_p;
@@ -250,11 +220,6 @@ struct service_fixture {
 	};
 };
 
-static pid_t __maybe_unused sys_gettid(void)
-{
-	return syscall(__NR_gettid);
-}
-
 static void __maybe_unused set_unix_address(struct service_fixture *const srv,
 					    const unsigned short index)
 {
diff --git a/tools/testing/selftests/landlock/wrappers.h b/tools/testing/selftests/landlock/wrappers.h
new file mode 100644
index 000000000000..32963a44876b
--- /dev/null
+++ b/tools/testing/selftests/landlock/wrappers.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Landlock helpers
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2019-2020 ANSSI
+ * Copyright © 2021-2024 Microsoft Corporation
+ */
+
+#define _GNU_SOURCE
+#include <linux/landlock.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifndef landlock_create_ruleset
+static inline int
+landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
+			const size_t size, const __u32 flags)
+{
+	return syscall(__NR_landlock_create_ruleset, attr, size, flags);
+}
+#endif
+
+#ifndef landlock_add_rule
+static inline int landlock_add_rule(const int ruleset_fd,
+				    const enum landlock_rule_type rule_type,
+				    const void *const rule_attr,
+				    const __u32 flags)
+{
+	return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
+		       flags);
+}
+#endif
+
+#ifndef landlock_restrict_self
+static inline int landlock_restrict_self(const int ruleset_fd,
+					 const __u32 flags)
+{
+	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
+}
+#endif
+
+static inline pid_t sys_gettid(void)
+{
+	return syscall(__NR_gettid);
+}
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 22/30] selftests/landlock: Add layout1.umount_sandboxer tests
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (20 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 21/30] selftests/landlock: Add wrappers.h Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-10 11:25   ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 23/30] selftests/landlock: Extend tests for landlock_restrict_self()'s flags Mickaël Salaün
                   ` (7 subsequent siblings)
  29 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Check that a domain is not tied to the executable file that created it.
For instance, that could happen if a Landlock domain took a reference to
a struct path.

Move global path names to common.h and replace copy_binary() with a more
generic copy_file() helper.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-23-mic@digikod.net
---

Changes since v3:
- New patch to check issue from v2.
---
 tools/testing/selftests/landlock/Makefile     |  2 +-
 tools/testing/selftests/landlock/common.h     |  3 +
 tools/testing/selftests/landlock/fs_test.c    | 94 +++++++++++++++++--
 .../selftests/landlock/sandbox-and-launch.c   | 82 ++++++++++++++++
 tools/testing/selftests/landlock/wait-pipe.c  | 42 +++++++++
 5 files changed, 213 insertions(+), 10 deletions(-)
 create mode 100644 tools/testing/selftests/landlock/sandbox-and-launch.c
 create mode 100644 tools/testing/selftests/landlock/wait-pipe.c

diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile
index 348e2dbdb4e0..b1445c8bee50 100644
--- a/tools/testing/selftests/landlock/Makefile
+++ b/tools/testing/selftests/landlock/Makefile
@@ -10,7 +10,7 @@ src_test := $(wildcard *_test.c)
 
 TEST_GEN_PROGS := $(src_test:.c=)
 
-TEST_GEN_PROGS_EXTENDED := true
+TEST_GEN_PROGS_EXTENDED := true sandbox-and-launch wait-pipe
 
 # Short targets:
 $(TEST_GEN_PROGS): LDLIBS += -lcap
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 8391ab574f64..a604ea5d8297 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -28,6 +28,9 @@
 /* TEST_F_FORK() should not be used for new tests. */
 #define TEST_F_FORK(fixture_name, test_name) TEST_F(fixture_name, test_name)
 
+static const char bin_sandbox_and_launch[] = "./sandbox-and-launch";
+static const char bin_wait_pipe[] = "./wait-pipe";
+
 static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
 {
 	cap_t cap_p;
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index a359c0d3107f..8ac9aaf38eaa 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -59,7 +59,7 @@ int open_tree(int dfd, const char *filename, unsigned int flags)
 #define RENAME_EXCHANGE (1 << 1)
 #endif
 
-#define BINARY_PATH "./true"
+static const char bin_true[] = "./true";
 
 /* Paths (sibling number and depth) */
 static const char dir_s1d1[] = TMP_DIR "/s1d1";
@@ -1965,8 +1965,8 @@ TEST_F_FORK(layout1, relative_chroot_chdir)
 	test_relative_path(_metadata, REL_CHROOT_CHDIR);
 }
 
-static void copy_binary(struct __test_metadata *const _metadata,
-			const char *const dst_path)
+static void copy_file(struct __test_metadata *const _metadata,
+		      const char *const src_path, const char *const dst_path)
 {
 	int dst_fd, src_fd;
 	struct stat statbuf;
@@ -1976,11 +1976,10 @@ static void copy_binary(struct __test_metadata *const _metadata,
 	{
 		TH_LOG("Failed to open \"%s\": %s", dst_path, strerror(errno));
 	}
-	src_fd = open(BINARY_PATH, O_RDONLY | O_CLOEXEC);
+	src_fd = open(src_path, O_RDONLY | O_CLOEXEC);
 	ASSERT_LE(0, src_fd)
 	{
-		TH_LOG("Failed to open \"" BINARY_PATH "\": %s",
-		       strerror(errno));
+		TH_LOG("Failed to open \"%s\": %s", src_path, strerror(errno));
 	}
 	ASSERT_EQ(0, fstat(src_fd, &statbuf));
 	ASSERT_EQ(statbuf.st_size,
@@ -2028,9 +2027,9 @@ TEST_F_FORK(layout1, execute)
 		create_ruleset(_metadata, rules[0].access, rules);
 
 	ASSERT_LE(0, ruleset_fd);
-	copy_binary(_metadata, file1_s1d1);
-	copy_binary(_metadata, file1_s1d2);
-	copy_binary(_metadata, file1_s1d3);
+	copy_file(_metadata, bin_true, file1_s1d1);
+	copy_file(_metadata, bin_true, file1_s1d2);
+	copy_file(_metadata, bin_true, file1_s1d3);
 
 	enforce_ruleset(_metadata, ruleset_fd);
 	ASSERT_EQ(0, close(ruleset_fd));
@@ -2048,6 +2047,83 @@ TEST_F_FORK(layout1, execute)
 	test_execute(_metadata, 0, file1_s1d3);
 }
 
+TEST_F_FORK(layout1, umount_sandboxer)
+{
+	int pipe_child[2], pipe_parent[2];
+	char buf_parent;
+	pid_t child;
+	int status;
+
+	copy_file(_metadata, bin_sandbox_and_launch, file1_s3d3);
+	ASSERT_EQ(0, pipe2(pipe_child, 0));
+	ASSERT_EQ(0, pipe2(pipe_parent, 0));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		char pipe_child_str[12], pipe_parent_str[12];
+		char *const argv[] = { (char *)file1_s3d3,
+				       (char *)bin_wait_pipe, pipe_child_str,
+				       pipe_parent_str, NULL };
+
+		/* Passes the pipe FDs to the executed binary and its child. */
+		EXPECT_EQ(0, close(pipe_child[0]));
+		EXPECT_EQ(0, close(pipe_parent[1]));
+		snprintf(pipe_child_str, sizeof(pipe_child_str), "%d",
+			 pipe_child[1]);
+		snprintf(pipe_parent_str, sizeof(pipe_parent_str), "%d",
+			 pipe_parent[0]);
+
+		/*
+		 * We need bin_sandbox_and_launch (copied inside the mount as
+		 * file1_s3d3) to execute bin_wait_pipe (outside the mount) to
+		 * make sure the mount point will not be EBUSY because of
+		 * file1_s3d3 being in use.  This avoids a potential race
+		 * condition between the following read() and umount() calls.
+		 */
+		ASSERT_EQ(0, execve(argv[0], argv, NULL))
+		{
+			TH_LOG("Failed to execute \"%s\": %s", argv[0],
+			       strerror(errno));
+		};
+		_exit(1);
+		return;
+	}
+
+	EXPECT_EQ(0, close(pipe_child[1]));
+	EXPECT_EQ(0, close(pipe_parent[0]));
+
+	/* Waits for the child to sandbox itself. */
+	EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1));
+
+	/* Tests that the sandboxer is tied to its mount point. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, umount(dir_s3d2));
+	EXPECT_EQ(EBUSY, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* Signals the child to launch a grandchild. */
+	EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
+
+	/* Waits for the grandchild. */
+	EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1));
+
+	/* Tests that the domain's sandboxer is not tied to its mount point. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, umount(dir_s3d2))
+	{
+		TH_LOG("Failed to umount \"%s\": %s", dir_s3d2,
+		       strerror(errno));
+	};
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* Signals the grandchild to terminate. */
+	EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(0, WEXITSTATUS(status));
+}
+
 TEST_F_FORK(layout1, link)
 {
 	const struct rule layer1[] = {
diff --git a/tools/testing/selftests/landlock/sandbox-and-launch.c b/tools/testing/selftests/landlock/sandbox-and-launch.c
new file mode 100644
index 000000000000..1ef49f349429
--- /dev/null
+++ b/tools/testing/selftests/landlock/sandbox-and-launch.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sandbox itself and execute another program (in a different mount point).
+ *
+ * Used by layout1.umount_sandboxer from fs_test.c
+ *
+ * Copyright © 2024 Microsoft Corporation
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "wrappers.h"
+
+int main(int argc, char *argv[])
+{
+	struct landlock_ruleset_attr ruleset_attr = {
+		.scoped = LANDLOCK_SCOPE_SIGNAL,
+	};
+	int pipe_child, pipe_parent, ruleset_fd;
+	char buf;
+
+	/*
+	 * The first argument must be the file descriptor number of a pipe.
+	 * The second argument must be the program to execute.
+	 */
+	if (argc != 4) {
+		fprintf(stderr, "Wrong number of arguments (not three)\n");
+		return 1;
+	}
+
+	pipe_child = atoi(argv[2]);
+	pipe_parent = atoi(argv[3]);
+
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	if (ruleset_fd < 0) {
+		perror("Failed to create ruleset");
+		return 1;
+	}
+
+	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+		perror("Failed to call prctl()");
+		return 1;
+	}
+
+	if (landlock_restrict_self(ruleset_fd, 0)) {
+		perror("Failed to restrict self");
+		return 1;
+	}
+
+	if (close(ruleset_fd)) {
+		perror("Failed to close ruleset");
+		return 1;
+	}
+
+	/* Signals that we are sandboxed. */
+	errno = 0;
+	if (write(pipe_child, ".", 1) != 1) {
+		perror("Failed to write to the second argument");
+		return 1;
+	}
+
+	/* Waits for the parent to try to umount. */
+	if (read(pipe_parent, &buf, 1) != 1) {
+		perror("Failed to write to the third argument");
+		return 1;
+	}
+
+	/* Shifts arguments. */
+	argv[0] = argv[1];
+	argv[1] = argv[2];
+	argv[2] = argv[3];
+	argv[3] = NULL;
+	execve(argv[0], argv, NULL);
+	perror("Failed to execute the provided binary");
+	return 1;
+}
diff --git a/tools/testing/selftests/landlock/wait-pipe.c b/tools/testing/selftests/landlock/wait-pipe.c
new file mode 100644
index 000000000000..0dbcd260a0fa
--- /dev/null
+++ b/tools/testing/selftests/landlock/wait-pipe.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Write in a pipe and wait.
+ *
+ * Used by layout1.umount_sandboxer from fs_test.c
+ *
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int main(int argc, char *argv[])
+{
+	int pipe_child, pipe_parent;
+	char buf;
+
+	/* The first argument must be the file descriptor number of a pipe. */
+	if (argc != 3) {
+		fprintf(stderr, "Wrong number of arguments (not two)\n");
+		return 1;
+	}
+
+	pipe_child = atoi(argv[1]);
+	pipe_parent = atoi(argv[2]);
+
+	/* Signals that we are waiting. */
+	if (write(pipe_child, ".", 1) != 1) {
+		perror("Failed to write to first argument");
+		return 1;
+	}
+
+	/* Waits for the parent do its test. */
+	if (read(pipe_parent, &buf, 1) != 1) {
+		perror("Failed to write to the second argument");
+		return 1;
+	}
+
+	return 0;
+}
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 23/30] selftests/landlock: Extend tests for landlock_restrict_self()'s flags
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (21 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 22/30] selftests/landlock: Add layout1.umount_sandboxer tests Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 24/30] selftests/landlock: Add tests for audit and LANDLOCK_RESTRICT_SELF_QUIET Mickaël Salaün
                   ` (6 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add the restrict_self_flags test suite to check that
LANDLOCK_RESTRICT_SELF_QUIET is valid but not the next bit.  Some checks
are similar to restrict_self_checks_ordering's ones.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-24-mic@digikod.net
---

Changes since v3:
- Use a last_flag variable.

Changes since v2:
- New patch.
---
 tools/testing/selftests/landlock/base_test.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index fbd687691b3c..1a1603ff178b 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -233,6 +233,23 @@ TEST(restrict_self_checks_ordering)
 	ASSERT_EQ(0, close(ruleset_fd));
 }
 
+TEST(restrict_self_flags)
+{
+	const __u32 last_flag = LANDLOCK_RESTRICT_SELF_QUIET;
+
+	ASSERT_EQ(-1, landlock_restrict_self(-1, 0));
+	ASSERT_EQ(EBADF, errno);
+
+	ASSERT_EQ(-1, landlock_restrict_self(-1, LANDLOCK_RESTRICT_SELF_QUIET));
+	ASSERT_EQ(EBADF, errno);
+
+	ASSERT_EQ(-1, landlock_restrict_self(-1, last_flag << 1));
+	ASSERT_EQ(EINVAL, errno);
+
+	ASSERT_EQ(-1, landlock_restrict_self(-1, -1));
+	ASSERT_EQ(EINVAL, errno);
+}
+
 TEST(ruleset_fd_io)
 {
 	struct landlock_ruleset_attr ruleset_attr = {
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 24/30] selftests/landlock: Add tests for audit and LANDLOCK_RESTRICT_SELF_QUIET
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (22 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 23/30] selftests/landlock: Extend tests for landlock_restrict_self()'s flags Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 25/30] selftests/landlock: Add audit tests for ptrace Mickaël Salaün
                   ` (5 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add audit_test.c to check with and without LANDLOCK_RESTRICT_SELF_QUIET
against the three Landlock audit record types: AUDIT_LANDLOCK_DENY,
AUDIT_LANDLOCK_DOM_INFO, and AUDIT_LANDLOCK_DOM_DROP.

Tests are run with audit filters to ensure the audit records come from
the running tests.  Moreover, because there can only be one audit
process, tests would failed if run in parallel.  Because of audit
limitations, tests can only be run in the initial namespace.

The audit test helpers were inspired by libaudit and
tools/testing/selftests/net/netfilter/audit_logread.c

Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Phil Sutter <phil@nwl.cc>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-25-mic@digikod.net
---

Changes since v3:
- Improve audit_request() to check Netlink errors and handle multiple
  replies.
- Make audit_filter_exe() more generic to handle several audit rule
  lists.
- Merge audit_init_state() into audit_init() and create
  audit_init_with_exe_filter() to handle AUDIT_EXE_LANDLOCK_DENY with an
  arbitrary path.
- Add matches_log_dom_info().

Changes since v2:
- New patch.
---
 tools/testing/selftests/landlock/audit.h      | 370 ++++++++++++++++++
 tools/testing/selftests/landlock/audit_test.c | 158 ++++++++
 tools/testing/selftests/landlock/common.h     |   2 +
 tools/testing/selftests/landlock/config       |   1 +
 4 files changed, 531 insertions(+)
 create mode 100644 tools/testing/selftests/landlock/audit.h
 create mode 100644 tools/testing/selftests/landlock/audit_test.c

diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h
new file mode 100644
index 000000000000..37979a62478c
--- /dev/null
+++ b/tools/testing/selftests/landlock/audit.h
@@ -0,0 +1,370 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Landlock audit helpers
+ *
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <linux/audit.h>
+#include <linux/limits.h>
+#include <linux/netlink.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#define REGEX_LANDLOCK_PREFIX "^audit([0-9.:]\\+): domain=[0-9a-f]\\+"
+
+struct audit_filter {
+	__u32 record_type;
+	size_t exe_len;
+	char exe[PATH_MAX];
+};
+
+struct audit_message {
+	struct nlmsghdr header;
+	union {
+		struct audit_status status;
+		struct audit_features features;
+		struct audit_rule_data rule;
+		struct nlmsgerr err;
+		char data[PATH_MAX + 200];
+	};
+};
+
+static int audit_send(const int fd, const struct audit_message *const msg)
+{
+	struct sockaddr_nl addr = {
+		.nl_family = AF_NETLINK,
+	};
+	int ret;
+
+	do {
+		ret = sendto(fd, msg, msg->header.nlmsg_len, 0,
+			     (struct sockaddr *)&addr, sizeof(addr));
+	} while (ret < 0 && errno == EINTR);
+
+	if (ret < 0)
+		return -errno;
+
+	if (ret != msg->header.nlmsg_len)
+		return -E2BIG;
+
+	return 0;
+}
+
+static int audit_recv(const int fd, struct audit_message *msg)
+{
+	struct sockaddr_nl addr;
+	socklen_t addrlen = sizeof(addr);
+	struct audit_message msg_tmp;
+	int err;
+
+	if (!msg)
+		msg = &msg_tmp;
+
+	do {
+		err = recvfrom(fd, msg, sizeof(*msg), 0,
+			       (struct sockaddr *)&addr, &addrlen);
+	} while (err < 0 && errno == EINTR);
+
+	if (err < 0)
+		return -errno;
+
+	if (addrlen != sizeof(addr) || addr.nl_pid != 0)
+		return -EINVAL;
+
+	/* Checks Netlink error or end of messages. */
+	if (msg->header.nlmsg_type == NLMSG_ERROR)
+		return msg->err.error;
+
+	return 0;
+}
+
+static int audit_request(const int fd,
+			 const struct audit_message *const request,
+			 struct audit_message *reply)
+{
+	struct audit_message msg_tmp;
+	bool first_reply = true;
+	int err;
+
+	err = audit_send(fd, request);
+	if (err)
+		return err;
+
+	if (!reply)
+		reply = &msg_tmp;
+
+	do {
+		if (first_reply)
+			first_reply = false;
+		else
+			reply = &msg_tmp;
+
+		err = audit_recv(fd, reply);
+		if (err)
+			return err;
+	} while (reply->header.nlmsg_type != NLMSG_ERROR &&
+		 reply->err.msg.nlmsg_type != request->header.nlmsg_type);
+
+	return reply->err.error;
+}
+
+static int audit_filter_exe(const int audit_fd,
+			    const struct audit_filter *const filter,
+			    const __u16 type, const __u32 flags)
+{
+	struct audit_message msg = {
+		.header = {
+			.nlmsg_len = NLMSG_SPACE(sizeof(msg.rule)) +
+				     NLMSG_ALIGN(filter->exe_len),
+			.nlmsg_type = type,
+			.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+		},
+		.rule = {
+			.flags = flags,
+			.action = AUDIT_NEVER,
+			.field_count = 1,
+			.fields[0] = filter->record_type,
+			.fieldflags[0] = AUDIT_NOT_EQUAL,
+			.values[0] = filter->exe_len,
+			.buflen = filter->exe_len,
+		}
+	};
+
+	switch (filter->record_type) {
+	case AUDIT_EXE:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	memcpy(msg.rule.buf, filter->exe, filter->exe_len);
+	return audit_request(audit_fd, &msg, NULL);
+}
+
+static int audit_filter_drop(const int audit_fd, const __u16 type)
+{
+	struct audit_message msg = {
+		.header = {
+			.nlmsg_len = NLMSG_SPACE(sizeof(msg.rule)),
+			.nlmsg_type = type,
+			.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+		},
+		.rule = {
+			.flags = AUDIT_FILTER_EXCLUDE,
+			.action = AUDIT_NEVER,
+			.field_count = 1,
+			.fields[0] = AUDIT_MSGTYPE,
+			.fieldflags[0] = AUDIT_NOT_EQUAL,
+			.values[0] = AUDIT_LANDLOCK_DOM_DROP,
+		}
+	};
+
+	return audit_request(audit_fd, &msg, NULL);
+}
+
+static int audit_set_status(int fd, __u32 key, __u32 val)
+{
+	const struct audit_message msg = {
+		.header = {
+			.nlmsg_len = NLMSG_SPACE(sizeof(msg.status)),
+			.nlmsg_type = AUDIT_SET,
+			.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+		},
+		.status = {
+			.mask = key,
+			.enabled = key == AUDIT_STATUS_ENABLED ? val : 0,
+			.pid = key == AUDIT_STATUS_PID ? val : 0,
+		}
+	};
+
+	return audit_request(fd, &msg, NULL);
+}
+
+static int audit_match_record(int audit_fd, const __u16 type,
+			      const char *const pattern)
+{
+	struct audit_message msg;
+	int ret, err = 0;
+	bool matches_record = !type;
+	regex_t regex;
+
+	ret = regcomp(&regex, pattern, 0);
+	if (ret)
+		return -EINVAL;
+
+	do {
+		memset(&msg, 0, sizeof(msg));
+		err = audit_recv(audit_fd, &msg);
+		if (err)
+			goto out;
+
+		if (msg.header.nlmsg_type == type)
+			matches_record = true;
+	} while (!matches_record);
+
+	ret = regexec(&regex, msg.data, 0, NULL, 0);
+	if (ret) {
+		printf("DATA: %s\n", msg.data);
+		printf("ERROR: no match for pattern: %s\n", pattern);
+		err = -ENOENT;
+	}
+
+out:
+	regfree(&regex);
+	return err;
+}
+
+struct audit_records {
+	size_t deny;
+	size_t info;
+	size_t drop;
+};
+
+static void audit_count_records(int audit_fd, struct audit_records *records)
+{
+	struct audit_message msg;
+
+	records->deny = 0;
+	records->info = 0;
+	records->drop = 0;
+
+	do {
+		memset(&msg, 0, sizeof(msg));
+		if (audit_recv(audit_fd, &msg))
+			return;
+
+		switch (msg.header.nlmsg_type) {
+		case AUDIT_LANDLOCK_DENY:
+			records->deny++;
+			break;
+		case AUDIT_LANDLOCK_DOM_INFO:
+			records->info++;
+			break;
+		case AUDIT_LANDLOCK_DOM_DROP:
+			records->drop++;
+			break;
+		}
+	} while (true);
+}
+
+static int audit_init(void)
+{
+	const struct timeval tv = {
+		.tv_usec = 1,
+	};
+	int fd, err;
+
+	fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
+	if (fd < 0)
+		return -errno;
+
+	err = audit_set_status(fd, AUDIT_STATUS_ENABLED, 1);
+	if (err)
+		return err;
+
+	err = audit_set_status(fd, AUDIT_STATUS_PID, getpid());
+	if (err)
+		return err;
+
+	/* Sets a timeout for negative tests. */
+	err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+	if (err)
+		return -errno;
+
+	return fd;
+}
+
+static int audit_init_filter_exe(const __u32 record_type,
+				 struct audit_filter *filter, const char *path)
+{
+	char *absolute_path = NULL;
+
+	// TODO: Make sure there is no rule already filtering.
+
+	filter->record_type = record_type;
+
+	if (!path) {
+		filter->exe_len = readlink("/proc/self/exe", filter->exe,
+					   sizeof(filter->exe) - 1);
+		if (filter->exe_len < 0)
+			return -errno;
+
+		return 0;
+	}
+
+	absolute_path = realpath(path, NULL);
+	if (!absolute_path)
+		return -errno;
+
+	/* No need for the terminating NULL byte. */
+	filter->exe_len = strlen(absolute_path);
+	if (filter->exe_len > sizeof(filter->exe))
+		return -E2BIG;
+
+	memcpy(filter->exe, absolute_path, filter->exe_len);
+	free(absolute_path);
+	return 0;
+}
+
+static int audit_cleanup(int audit_fd, struct audit_filter *filter)
+{
+	struct audit_filter new_filter;
+
+	if (audit_fd < 0 || !filter) {
+		int err;
+
+		/*
+		 * Simulates audit_init_with_exe_filter() when called from
+		 * FIXTURE_TEARDOWN_PARENT().
+		 */
+		audit_fd = audit_init();
+		if (audit_fd < 0)
+			return audit_fd;
+
+		filter = &new_filter;
+		err = audit_init_filter_exe(AUDIT_EXE, filter, NULL);
+		if (err)
+			return err;
+	}
+
+	/* Filters might not be in place. */
+	audit_filter_exe(audit_fd, filter, AUDIT_DEL_RULE,
+			 AUDIT_FILTER_EXCLUDE);
+	audit_filter_drop(audit_fd, AUDIT_DEL_RULE);
+
+	/*
+	 * Because audit_cleanup() might not be called by the test auditd
+	 * process, it might not be possible to explicitly set it.  Anyway,
+	 * AUDIT_STATUS_ENABLED will implicitly be set to 0 when the auditd
+	 * process will exit.
+	 */
+	return close(audit_fd);
+}
+
+static int audit_init_with_exe_filter(struct audit_filter *filter)
+{
+	int fd, err;
+
+	fd = audit_init();
+	if (fd < 0)
+		return fd;
+
+	err = audit_init_filter_exe(AUDIT_EXE, filter, NULL);
+	if (err)
+		return err;
+
+	err = audit_filter_exe(fd, filter, AUDIT_ADD_RULE,
+			       AUDIT_FILTER_EXCLUDE);
+	if (err)
+		return err;
+
+	return fd;
+}
diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
new file mode 100644
index 000000000000..d5330e843395
--- /dev/null
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - Audit
+ *
+ * Copyright © 2024 Microsoft Corporation
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <linux/landlock.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "audit.h"
+#include "common.h"
+
+static int matches_log_dom_info(struct __test_metadata *const _metadata,
+				int audit_fd)
+{
+	return audit_match_record(
+		audit_fd, AUDIT_LANDLOCK_DOM_INFO,
+		REGEX_LANDLOCK_PREFIX
+		" creation=[0-9.]\\+ pid=[0-9]\\+ uid=[0-9]\\+ exe=\"[^\"]\\+\" comm=\"audit_test\"$");
+}
+
+static int matches_log_umount(struct __test_metadata *const _metadata,
+			      int audit_fd)
+{
+	return audit_match_record(audit_fd, AUDIT_LANDLOCK_DENY,
+				  REGEX_LANDLOCK_PREFIX " blockers=.*");
+}
+
+FIXTURE(audit)
+{
+	struct audit_filter audit_filter;
+	int audit_fd;
+};
+
+FIXTURE_VARIANT(audit)
+{
+	const int restrict_flags;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit, default) {};
+/* clang-format on */
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit, quiet) {
+	/* clang-format on */
+	.restrict_flags = LANDLOCK_RESTRICT_SELF_QUIET,
+};
+
+FIXTURE_SETUP(audit)
+{
+	disable_caps(_metadata);
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
+	EXPECT_LE(0, self->audit_fd)
+	{
+		const char *error_msg;
+
+		/* kill "$(auditctl -s | sed -ne 's/^pid \([0-9]\+\)$/\1/p')" */
+		if (self->audit_fd == -EEXIST)
+			error_msg = "socket already in use (e.g. auditd)";
+		else
+			error_msg = strerror(-self->audit_fd);
+		TH_LOG("Failed to initialize audit: %s", error_msg);
+	}
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+FIXTURE_TEARDOWN(audit)
+{
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	EXPECT_EQ(0, audit_cleanup(self->audit_fd, &self->audit_filter));
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+TEST_F(audit, fs_deny)
+{
+	int status;
+	pid_t child;
+	struct audit_records records;
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		const struct landlock_ruleset_attr ruleset_attr = {
+			.handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
+		};
+		int ruleset_fd;
+
+		/* Add filesystem restrictions. */
+		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+						     sizeof(ruleset_attr), 0);
+		ASSERT_LE(0, ruleset_fd);
+		EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+		ASSERT_EQ(0, landlock_restrict_self(ruleset_fd,
+						    variant->restrict_flags));
+		EXPECT_EQ(0, close(ruleset_fd));
+
+		/* First umount checks to test log entries. */
+		set_cap(_metadata, CAP_SYS_ADMIN);
+		EXPECT_EQ(-1, umount("/"));
+		EXPECT_EQ(EPERM, errno);
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+
+		if (variant->restrict_flags & LANDLOCK_RESTRICT_SELF_QUIET) {
+			EXPECT_EQ(-EAGAIN, matches_log_umount(_metadata,
+							      self->audit_fd));
+		} else {
+			EXPECT_EQ(0, matches_log_umount(_metadata,
+							self->audit_fd));
+
+			/* Checks domain information records. */
+			EXPECT_EQ(0, matches_log_dom_info(_metadata,
+							  self->audit_fd));
+		}
+
+		/* Second umount checks to test audit_count_records(). */
+		set_cap(_metadata, CAP_SYS_ADMIN);
+		EXPECT_EQ(-1, umount("/"));
+		EXPECT_EQ(EPERM, errno);
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+
+		/* Makes sure there is no superfluous logged records. */
+		audit_count_records(self->audit_fd, &records);
+		if (variant->restrict_flags & LANDLOCK_RESTRICT_SELF_QUIET) {
+			EXPECT_EQ(0, records.deny);
+		} else {
+			EXPECT_EQ(1, records.deny);
+		}
+		EXPECT_EQ(0, records.info);
+		EXPECT_EQ(0, records.drop);
+
+		/* Updates filter rules to match the drop record. */
+		set_cap(_metadata, CAP_AUDIT_CONTROL);
+		EXPECT_EQ(0, audit_filter_drop(self->audit_fd, AUDIT_ADD_RULE));
+		EXPECT_EQ(0, audit_filter_exe(
+				     self->audit_fd, &self->audit_filter,
+				     AUDIT_DEL_RULE, AUDIT_FILTER_EXCLUDE));
+		clear_cap(_metadata, CAP_AUDIT_CONTROL);
+
+		_exit(_metadata->exit_code);
+		return;
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) != EXIT_SUCCESS)
+		_metadata->exit_code = KSFT_FAIL;
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index a604ea5d8297..ea7c4f9638b0 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -11,6 +11,7 @@
 #include <errno.h>
 #include <linux/securebits.h>
 #include <sys/capability.h>
+#include <sys/prctl.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <sys/wait.h>
@@ -37,6 +38,7 @@ static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
 	/* Only these three capabilities are useful for the tests. */
 	const cap_value_t caps[] = {
 		/* clang-format off */
+		CAP_AUDIT_CONTROL,
 		CAP_DAC_OVERRIDE,
 		CAP_MKNOD,
 		CAP_NET_ADMIN,
diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
index 29af19c4e9f9..3d4eb994d62b 100644
--- a/tools/testing/selftests/landlock/config
+++ b/tools/testing/selftests/landlock/config
@@ -1,3 +1,4 @@
+CONFIG_AUDIT=y
 CONFIG_CGROUPS=y
 CONFIG_CGROUP_SCHED=y
 CONFIG_INET=y
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 25/30] selftests/landlock: Add audit tests for ptrace
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (23 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 24/30] selftests/landlock: Add tests for audit and LANDLOCK_RESTRICT_SELF_QUIET Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 26/30] landlock: Export and rename landlock_get_inode_object() Mickaël Salaün
                   ` (4 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add tests for all ptrace actions.  This improves all the ptrace tests by
making sure that the restrictions comes from Landlock, and with the
expected objects.  These extended tests are like enhanced errno checks
that make sure Landlock enforcement is consistent.

Test coverage for security/landlock is 93.4% of 1619 lines according to
gcc/gcov-14.

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

Changes since v3:
- Update test coverage.

Changes since v2:
- New patch.
---
 .../testing/selftests/landlock/ptrace_test.c  | 67 +++++++++++++++++--
 1 file changed, 63 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c
index a19db4d0b3bd..20a1ca7801aa 100644
--- a/tools/testing/selftests/landlock/ptrace_test.c
+++ b/tools/testing/selftests/landlock/ptrace_test.c
@@ -4,6 +4,7 @@
  *
  * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
  * Copyright © 2019-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
  */
 
 #define _GNU_SOURCE
@@ -17,6 +18,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include "audit.h"
 #include "common.h"
 
 /* Copied from security/yama/yama_lsm.c */
@@ -85,9 +87,27 @@ static int get_yama_ptrace_scope(void)
 	return ret;
 }
 
-/* clang-format off */
-FIXTURE(hierarchy) {};
-/* clang-format on */
+static int matches_log_ptrace(struct __test_metadata *const _metadata,
+			      int audit_fd, const pid_t opid)
+{
+	static const char log_template[] = REGEX_LANDLOCK_PREFIX
+		" blockers=ptrace opid=%d ocomm=\"ptrace_test\"$";
+	char log_match[sizeof(log_template) + 10];
+	int log_match_len;
+
+	log_match_len =
+		snprintf(log_match, sizeof(log_match), log_template, opid);
+	if (log_match_len > sizeof(log_match))
+		return -E2BIG;
+
+	return audit_match_record(audit_fd, AUDIT_LANDLOCK_DENY, log_match);
+}
+
+FIXTURE(hierarchy)
+{
+	struct audit_filter audit_filter;
+	int audit_fd;
+};
 
 FIXTURE_VARIANT(hierarchy)
 {
@@ -245,10 +265,16 @@ FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) {
 
 FIXTURE_SETUP(hierarchy)
 {
+	disable_caps(_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);
 }
 
-FIXTURE_TEARDOWN(hierarchy)
+FIXTURE_TEARDOWN_PARENT(hierarchy)
 {
+	EXPECT_EQ(0, audit_cleanup(-1, NULL));
 }
 
 /* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
@@ -261,6 +287,7 @@ TEST_F(hierarchy, trace)
 	char buf_parent;
 	long ret;
 	bool can_read_child, can_trace_child, can_read_parent, can_trace_parent;
+	struct audit_records records;
 
 	yama_ptrace_scope = get_yama_ptrace_scope();
 	ASSERT_LE(0, yama_ptrace_scope);
@@ -336,17 +363,29 @@ TEST_F(hierarchy, trace)
 		err_proc_read = test_ptrace_read(parent);
 		if (can_read_parent) {
 			EXPECT_EQ(0, err_proc_read);
+			EXPECT_EQ(-EAGAIN,
+				  matches_log_ptrace(_metadata, self->audit_fd,
+						     parent));
 		} else {
 			EXPECT_EQ(EACCES, err_proc_read);
+			EXPECT_EQ(0,
+				  matches_log_ptrace(_metadata, self->audit_fd,
+						     parent));
 		}
 
 		/* Tests PTRACE_ATTACH on the parent. */
 		ret = ptrace(PTRACE_ATTACH, parent, NULL, 0);
 		if (can_trace_parent) {
 			EXPECT_EQ(0, ret);
+			EXPECT_EQ(-EAGAIN,
+				  matches_log_ptrace(_metadata, self->audit_fd,
+						     parent));
 		} else {
 			EXPECT_EQ(-1, ret);
 			EXPECT_EQ(EPERM, errno);
+			EXPECT_EQ(can_read_parent ? -EAGAIN : 0,
+				  matches_log_ptrace(_metadata, self->audit_fd,
+						     parent));
 		}
 		if (ret == 0) {
 			ASSERT_EQ(parent, waitpid(parent, &status, 0));
@@ -358,9 +397,16 @@ TEST_F(hierarchy, trace)
 		ret = ptrace(PTRACE_TRACEME);
 		if (can_trace_child) {
 			EXPECT_EQ(0, ret);
+			EXPECT_EQ(-EAGAIN,
+				  matches_log_ptrace(_metadata, self->audit_fd,
+						     parent));
 		} else {
 			EXPECT_EQ(-1, ret);
 			EXPECT_EQ(EPERM, errno);
+			/* We should indeed see the parent process. */
+			EXPECT_EQ(can_read_child ? -EAGAIN : 0,
+				  matches_log_ptrace(_metadata, self->audit_fd,
+						     parent));
 		}
 
 		/*
@@ -408,17 +454,25 @@ TEST_F(hierarchy, trace)
 	err_proc_read = test_ptrace_read(child);
 	if (can_read_child) {
 		EXPECT_EQ(0, err_proc_read);
+		EXPECT_EQ(-EAGAIN,
+			  matches_log_ptrace(_metadata, self->audit_fd, child));
 	} else {
 		EXPECT_EQ(EACCES, err_proc_read);
+		EXPECT_EQ(0,
+			  matches_log_ptrace(_metadata, self->audit_fd, child));
 	}
 
 	/* Tests PTRACE_ATTACH on the child. */
 	ret = ptrace(PTRACE_ATTACH, child, NULL, 0);
 	if (can_trace_child) {
 		EXPECT_EQ(0, ret);
+		EXPECT_EQ(-EAGAIN,
+			  matches_log_ptrace(_metadata, self->audit_fd, child));
 	} else {
 		EXPECT_EQ(-1, ret);
 		EXPECT_EQ(EPERM, errno);
+		EXPECT_EQ(can_read_child ? -EAGAIN : 0,
+			  matches_log_ptrace(_metadata, self->audit_fd, child));
 	}
 
 	if (ret == 0) {
@@ -434,6 +488,11 @@ TEST_F(hierarchy, trace)
 	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
 	    WEXITSTATUS(status) != EXIT_SUCCESS)
 		_metadata->exit_code = KSFT_FAIL;
+
+	/* Makes sure there is no superfluous logged records. */
+	audit_count_records(self->audit_fd, &records);
+	EXPECT_EQ(0, records.deny);
+	EXPECT_EQ(0, records.info);
 }
 
 TEST_HARNESS_MAIN
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 26/30] landlock: Export and rename landlock_get_inode_object()
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (24 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 25/30] selftests/landlock: Add audit tests for ptrace Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 27/30] fs: Add iput() cleanup helper Mickaël Salaün
                   ` (3 subsequent siblings)
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

This will be used by security/landlock/audit.c in a following commit.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-27-mic@digikod.net
---

Changes since v3:
- New patch.
---
 security/landlock/fs.c | 22 ++++++++++++----------
 security/landlock/fs.h |  2 ++
 2 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 6404961ecbc7..4b718b669ebe 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -74,13 +74,14 @@ static void release_inode(struct landlock_object *const object)
 	spin_unlock(&object->lock);
 	/*
 	 * Because object->underobj was not NULL, hook_sb_delete() and
-	 * get_inode_object() guarantee that it is safe to reset
+	 * landlock_get_inode_object() guarantee that it is safe to reset
 	 * landlock_inode(inode)->object while it is not NULL.  It is therefore
 	 * not necessary to lock inode->i_lock.
 	 */
 	rcu_assign_pointer(landlock_inode(inode)->object, NULL);
 	/*
-	 * Now, new rules can safely be tied to @inode with get_inode_object().
+	 * Now, new rules can safely be tied to @inode with
+	 * landlock_get_inode_object().
 	 */
 
 	iput(inode);
@@ -259,7 +260,7 @@ update_request(struct landlock_request *const request,
 
 /* Ruleset management */
 
-static struct landlock_object *get_inode_object(struct inode *const inode)
+struct landlock_object *landlock_get_inode_object(struct inode *const inode)
 {
 	struct landlock_object *object, *new_object;
 	struct landlock_inode_security *inode_sec = landlock_inode(inode);
@@ -291,7 +292,7 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
 		return new_object;
 
 	/*
-	 * Protects against concurrent calls to get_inode_object() or
+	 * Protects against concurrent calls to landlock_get_inode_object() or
 	 * hook_sb_delete().
 	 */
 	spin_lock(&inode->i_lock);
@@ -347,7 +348,8 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
 	/* Transforms relative access rights to absolute ones. */
 	access_rights |= LANDLOCK_MASK_ACCESS_FS &
 			 ~landlock_get_fs_access_mask(ruleset, 0);
-	id.key.object = get_inode_object(d_backing_inode(path->dentry));
+	id.key.object =
+		landlock_get_inode_object(d_backing_inode(path->dentry));
 	if (IS_ERR(id.key.object))
 		return PTR_ERR(id.key.object);
 	mutex_lock(&ruleset->lock);
@@ -1288,7 +1290,7 @@ static void hook_sb_delete(struct super_block *const sb)
 
 		/*
 		 * Protects against concurrent modification of inode (e.g.
-		 * from get_inode_object()).
+		 * from landlock_get_inode_object()).
 		 */
 		spin_lock(&inode->i_lock);
 		/*
@@ -1327,16 +1329,16 @@ static void hook_sb_delete(struct super_block *const sb)
 
 			/*
 			 * Because object->underobj was not NULL,
-			 * release_inode() and get_inode_object() guarantee
-			 * that it is safe to reset
+			 * release_inode() and landlock_get_inode_object()
+			 * guarantee that it is safe to reset
 			 * landlock_inode(inode)->object while it is not NULL.
 			 * It is therefore not necessary to lock inode->i_lock.
 			 */
 			rcu_assign_pointer(landlock_inode(inode)->object, NULL);
 			/*
 			 * At this point, we own the ihold() reference that was
-			 * originally set up by get_inode_object() and the
-			 * __iget() reference that we just set in this loop
+			 * originally set up by landlock_get_inode_object() and
+			 * the __iget() reference that we just set in this loop
 			 * walk.  Therefore the following call to iput() will
 			 * not sleep nor drop the inode because there is now at
 			 * least two references to it.
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index 9f52c9b37898..3e428fa51cec 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -109,4 +109,6 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
 			    const struct path *const path,
 			    access_mask_t access_hierarchy);
 
+struct landlock_object *landlock_get_inode_object(struct inode *const inode);
+
 #endif /* _SECURITY_LANDLOCK_FS_H */
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 27/30] fs: Add iput() cleanup helper
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (25 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 26/30] landlock: Export and rename landlock_get_inode_object() Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-13 11:15   ` Mickaël Salaün
                     ` (2 more replies)
  2025-01-08 15:43 ` [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type Mickaël Salaün
                   ` (2 subsequent siblings)
  29 siblings, 3 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module, Al Viro, Christian Brauner, Jeff Layton,
	Josef Bacik

Add a simple scope-based helper to put an inode reference, similar to
the fput() helper.

This is used in a following commit.

Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Jeff Layton <jlayton@kernel.org>
Cc: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250108154338.1129069-28-mic@digikod.net
---

Changes since v3:
- New patch.
---
 include/linux/fs.h | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/include/linux/fs.h b/include/linux/fs.h
index 7e29433c5ecc..bd5a28b0871f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -47,6 +47,8 @@
 #include <linux/rw_hint.h>
 #include <linux/file_ref.h>
 #include <linux/unicode.h>
+#include <linux/cleanup.h>
+#include <linux/err.h>
 
 #include <asm/byteorder.h>
 #include <uapi/linux/fs.h>
@@ -2698,6 +2700,8 @@ extern void iput(struct inode *);
 int inode_update_timestamps(struct inode *inode, int flags);
 int generic_update_time(struct inode *, int);
 
+DEFINE_FREE(iput, struct inode *, if (!IS_ERR_OR_NULL(_T)) iput(_T))
+
 /* /sys/fs */
 extern struct kobject *fs_kobj;
 
@@ -3108,8 +3112,6 @@ static inline bool is_dot_dotdot(const char *name, size_t len)
 		(len == 1 || (len == 2 && name[1] == '.'));
 }
 
-#include <linux/err.h>
-
 /* needed for stackable file system support */
 extern loff_t default_llseek(struct file *file, loff_t offset, int whence);
 
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (26 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 27/30] fs: Add iput() cleanup helper Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-13 14:55   ` Jann Horn
  2025-01-15 23:53   ` Paul Moore
  2025-01-08 15:43 ` [PATCH v4 29/30] selftests/landlock: Test audit rule with AUDIT_EXE_LANDLOCK_DOM Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 30/30] selftests/landlock: Test compatibility with audit rule lists Mickaël Salaün
  29 siblings, 2 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Landlock manages a set of standalone security policies, which can be
loaded by any process.  Because a sandbox policy may contain errors and
can lead to log spam, we need a way to exclude some of them.  It is
simple and it makes sense to identify Landlock domains (i.e. security
policies) per binary path that loaded such policy.

Add a new AUDIT_EXE_LANDLOCK_DENY rule type to enables system
administrator to filter logs according to the origin or the security
policy responsible for a denial.

AUDIT_EXE identifies a property of the task calling the kernel, whereas
AUDIT_EXE_LANDLOCK_DENY identifies a property of a task that restricted
the task calling the kernel.  AUDIT_EXE_LANDLOCK_DENY leverages most of
AUDIT_EXE's code to track files and compare them.

AUDIT_EXE_LANDLOCK_DENY is only handled by these audit rule lists:
- AUDIT_FILTER_EXCLUDE
- AUDIT_FILTER_EXIT
- AUDIT_FILTER_URING_EXIT

Add a new audit_set_landlock_hierarchy() helper to enrich the audit
context with the Landlock domain's creator which is the origin of the
current denial (if any).

Pass the current audit context to audit_filter() to be able to filter
according to the Landlock domain creator that denied the current action.

Add a new landlock_read_domain_exe() helper for audit to compare a
Landlock domain creator's inode and device numbers with a rule.

If scripts are not directly executed but passed to an interpreter, like
with AUDIT_EXE and /proc/self/exe, only this interpreter's path will
show in the logs.  Scripts enforcing a security policy should then be
directly executed to differentiate between different scripts.

It does not make sense to add dedicated LSM hooks because it would not
make sense to treat all current and future LSM policies the same, and
there is currently only Landlock that handles different standalone and
unprivileged security policies.  Indeed, AUDIT_EXE_LANDLOCK_DENY has a
clear semantic: it identifies the source of a Landlock denial.

In the future, we might want to extend this filtering capability with
other properties of tasks that restrict themselves with Landlock (e.g.
UID, loginuid, sessionid).  This could be useful on systems where users
can bring their own executable code (which can already spam logs).  For
now, AUDIT_EXE_LANDLOCK_DENY is enough to exclude buggy sandboxed
applications that may spam logs.

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

We could have something like this to filter Landlock domains:
  -a never,exclude -F exe_landlock_deny=/usr/bin/buggy-sandboxed-app

Changes since v3:
- New patch.
---
 include/linux/audit.h      | 11 ++++++++
 include/linux/landlock.h   | 10 +++++++
 include/uapi/linux/audit.h |  1 +
 kernel/audit.c             |  4 +--
 kernel/audit.h             |  5 +++-
 kernel/auditfilter.c       | 30 ++++++++++++++++++++-
 kernel/auditsc.c           | 31 ++++++++++++++++++++++
 security/landlock/audit.c  |  4 +++
 security/landlock/domain.c | 54 ++++++++++++++++++++++++++++++++++++--
 security/landlock/domain.h | 20 ++++++++++++++
 security/landlock/fs.c     |  4 +--
 security/landlock/object.h |  4 ++-
 12 files changed, 169 insertions(+), 9 deletions(-)

diff --git a/include/linux/audit.h b/include/linux/audit.h
index 0050ef288ab3..6397bc01c0c0 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -12,6 +12,7 @@
 #include <linux/sched.h>
 #include <linux/ptrace.h>
 #include <linux/audit_arch.h>
+#include <linux/landlock.h>
 #include <uapi/linux/audit.h>
 #include <uapi/linux/netfilter/nf_tables.h>
 #include <uapi/linux/fanotify.h>
@@ -305,6 +306,7 @@ extern void audit_seccomp(unsigned long syscall, long signr, int code);
 extern void audit_seccomp_actions_logged(const char *names,
 					 const char *old_names, int res);
 extern void __audit_ptrace(struct task_struct *t);
+extern void __audit_set_landlock_hierarchy(struct landlock_hierarchy *hierarchy);
 
 static inline void audit_set_context(struct task_struct *task, struct audit_context *ctx)
 {
@@ -357,6 +359,12 @@ static inline void audit_syscall_exit(void *pt_regs)
 		__audit_syscall_exit(success, return_code);
 	}
 }
+static inline void
+audit_set_landlock_hierarchy(struct landlock_hierarchy *hierarchy)
+{
+	if (unlikely(audit_context() && audit_enabled))
+		__audit_set_landlock_hierarchy(hierarchy);
+}
 static inline struct filename *audit_reusename(const __user char *name)
 {
 	if (unlikely(!audit_dummy_context()))
@@ -591,6 +599,9 @@ static inline void audit_syscall_entry(int major, unsigned long a0,
 { }
 static inline void audit_syscall_exit(void *pt_regs)
 { }
+static inline void
+audit_set_landlock_hierarchy(struct landlock_hierarchy *hierarchy)
+{ }
 static inline bool audit_dummy_context(void)
 {
 	return true;
diff --git a/include/linux/landlock.h b/include/linux/landlock.h
index 8491142658a1..1b6dae8d5631 100644
--- a/include/linux/landlock.h
+++ b/include/linux/landlock.h
@@ -16,6 +16,9 @@ void landlock_get_hierarchy(struct landlock_hierarchy *hierarchy);
 
 void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy);
 
+bool landlock_read_domain_exe(const struct landlock_hierarchy *const hierarchy,
+			      ino_t *ino, dev_t *dev);
+
 #else /* CONFIG_SECURITY_LANDLOCK */
 
 static inline void landlock_get_hierarchy(struct landlock_hierarchy *hierarchy)
@@ -26,6 +29,13 @@ static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
 {
 }
 
+static inline bool
+landlock_read_domain_exe(const struct landlock_hierarchy *const hierarchy,
+			 ino_t *ino, dev_t *dev)
+{
+	return false;
+}
+
 #endif /* CONFIG_SECURITY_LANDLOCK */
 
 #endif /* _LINUX_LANDLOCK_H */
diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index a72f7b3403be..07b69d4ef076 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -296,6 +296,7 @@
 #define AUDIT_FIELD_COMPARE	111
 #define AUDIT_EXE	112
 #define AUDIT_SADDR_FAM	113
+#define AUDIT_EXE_LANDLOCK_DENY	114
 
 #define AUDIT_ARG0      200
 #define AUDIT_ARG1      (AUDIT_ARG0+1)
diff --git a/kernel/audit.c b/kernel/audit.c
index 6a95a6077953..edde6f5ebdc7 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -1381,7 +1381,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
 		if (data_len < 2)
 			return -EINVAL;
 
-		err = audit_filter(msg_type, AUDIT_FILTER_USER);
+		err = audit_filter(msg_type, AUDIT_FILTER_USER, NULL);
 		if (err == 1) { /* match or error */
 			char *str = data;
 
@@ -1866,7 +1866,7 @@ struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask,
 	if (audit_initialized != AUDIT_INITIALIZED)
 		return NULL;
 
-	if (unlikely(!audit_filter(type, AUDIT_FILTER_EXCLUDE)))
+	if (unlikely(!audit_filter(type, AUDIT_FILTER_EXCLUDE, ctx)))
 		return NULL;
 
 	/* NOTE: don't ever fail/sleep on these two conditions:
diff --git a/kernel/audit.h b/kernel/audit.h
index 0211cb307d30..4f20574462b4 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -13,6 +13,7 @@
 #include <linux/audit.h>
 #include <linux/security.h>
 #include <linux/skbuff.h>
+#include <linux/landlock.h>
 #include <uapi/linux/mqueue.h>
 #include <linux/tty.h>
 #include <uapi/linux/openat2.h> // struct open_how
@@ -209,6 +210,7 @@ struct audit_context {
 	};
 	int fds[2];
 	struct audit_proctitle proctitle;
+	struct landlock_hierarchy *landlock_hierarchy;
 };
 
 extern bool audit_ever_enabled;
@@ -340,7 +342,8 @@ static inline int audit_signal_info_syscall(struct task_struct *t)
 
 extern char *audit_unpack_string(void **bufp, size_t *remain, size_t len);
 
-extern int audit_filter(int msgtype, unsigned int listtype);
+extern int audit_filter(int msgtype, unsigned int listtype,
+			const struct audit_context *ctx);
 
 extern void audit_ctl_lock(void);
 extern void audit_ctl_unlock(void);
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index bceb9f58a09e..36d258229437 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -13,6 +13,7 @@
 #include <linux/kthread.h>
 #include <linux/mutex.h>
 #include <linux/fs.h>
+#include <linux/landlock.h>
 #include <linux/namei.h>
 #include <linux/netlink.h>
 #include <linux/sched.h>
@@ -340,6 +341,12 @@ static int audit_field_valid(struct audit_entry *entry, struct audit_field *f)
 		if (entry->rule.listnr == AUDIT_FILTER_URING_EXIT)
 			return -EINVAL;
 		break;
+	case AUDIT_EXE_LANDLOCK_DENY:
+		if (entry->rule.listnr != AUDIT_FILTER_EXCLUDE &&
+		    entry->rule.listnr != AUDIT_FILTER_EXIT &&
+		    entry->rule.listnr != AUDIT_FILTER_URING_EXIT)
+			return -EINVAL;
+		break;
 	}
 
 	switch (entry->rule.listnr) {
@@ -407,6 +414,7 @@ static int audit_field_valid(struct audit_entry *entry, struct audit_field *f)
 	case AUDIT_FILETYPE:
 	case AUDIT_FIELD_COMPARE:
 	case AUDIT_EXE:
+	case AUDIT_EXE_LANDLOCK_DENY:
 		/* only equal and not equal valid ops */
 		if (f->op != Audit_not_equal && f->op != Audit_equal)
 			return -EINVAL;
@@ -583,6 +591,7 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
 			entry->rule.filterkey = str;
 			break;
 		case AUDIT_EXE:
+		case AUDIT_EXE_LANDLOCK_DENY:
 			if (entry->rule.exe || f_val > PATH_MAX)
 				goto exit_free;
 			str = audit_unpack_string(&bufp, &remain, f_val);
@@ -681,6 +690,7 @@ static struct audit_rule_data *audit_krule_to_data(struct audit_krule *krule)
 				audit_pack_string(&bufp, krule->filterkey);
 			break;
 		case AUDIT_EXE:
+		case AUDIT_EXE_LANDLOCK_DENY:
 			data->buflen += data->values[i] =
 				audit_pack_string(&bufp, audit_mark_path(krule->exe));
 			break;
@@ -749,6 +759,7 @@ static int audit_compare_rule(struct audit_krule *a, struct audit_krule *b)
 				return 1;
 			break;
 		case AUDIT_EXE:
+		case AUDIT_EXE_LANDLOCK_DENY:
 			/* both paths exist based on above type compare */
 			if (strcmp(audit_mark_path(a->exe),
 				   audit_mark_path(b->exe)))
@@ -877,6 +888,7 @@ struct audit_entry *audit_dupe_rule(struct audit_krule *old)
 				new->filterkey = fk;
 			break;
 		case AUDIT_EXE:
+		case AUDIT_EXE_LANDLOCK_DENY:
 			err = audit_dupe_exe(new, old);
 			break;
 		}
@@ -1328,7 +1340,8 @@ int audit_compare_dname_path(const struct qstr *dname, const char *path, int par
 	return strncmp(p, dname->name, dlen);
 }
 
-int audit_filter(int msgtype, unsigned int listtype)
+int audit_filter(int msgtype, unsigned int listtype,
+		 const struct audit_context *ctx)
 {
 	struct audit_entry *e;
 	int ret = 1; /* Audit by default */
@@ -1381,6 +1394,21 @@ int audit_filter(int msgtype, unsigned int listtype)
 				if (f->op == Audit_not_equal)
 					result = !result;
 				break;
+			case AUDIT_EXE_LANDLOCK_DENY:
+				if (ctx && ctx->landlock_hierarchy) {
+					ino_t ino = 0;
+					dev_t dev = 0;
+
+					result =
+						landlock_read_domain_exe(
+							ctx->landlock_hierarchy,
+							&ino, &dev) &&
+						audit_mark_compare(e->rule.exe,
+								   ino, dev);
+					if (f->op == Audit_not_equal)
+						result = !result;
+				}
+				break;
 			default:
 				goto unlock_and_return;
 			}
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 561d96affe9f..0be7542852de 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -61,6 +61,7 @@
 #include <linux/string.h>
 #include <linux/uaccess.h>
 #include <linux/fsnotify_backend.h>
+#include <linux/landlock.h>
 #include <uapi/linux/limits.h>
 #include <uapi/linux/netfilter/nf_tables.h>
 #include <uapi/linux/openat2.h> // struct open_how
@@ -501,6 +502,20 @@ static int audit_filter_rules(struct task_struct *tsk,
 			if (f->op == Audit_not_equal)
 				result = !result;
 			break;
+		case AUDIT_EXE_LANDLOCK_DENY:
+			if (ctx && ctx->landlock_hierarchy) {
+				ino_t ino = 0;
+				dev_t dev = 0;
+
+				result =
+					landlock_read_domain_exe(
+						ctx->landlock_hierarchy, &ino,
+						&dev) &&
+					audit_mark_compare(rule->exe, ino, dev);
+				if (f->op == Audit_not_equal)
+					result = !result;
+			}
+			break;
 		case AUDIT_UID:
 			result = audit_uid_comparator(cred->uid, f->op, f->uid);
 			break;
@@ -1025,6 +1040,10 @@ static void audit_reset_context(struct audit_context *ctx)
 	WARN_ON(!list_empty(&ctx->killed_trees));
 	audit_free_module(ctx);
 	ctx->fds[0] = -1;
+	if (ctx->landlock_hierarchy) {
+		landlock_put_hierarchy(ctx->landlock_hierarchy);
+		ctx->landlock_hierarchy = NULL;
+	}
 	ctx->type = 0; /* reset last for audit_free_*() */
 }
 
@@ -2081,6 +2100,18 @@ void __audit_syscall_exit(int success, long return_code)
 	audit_reset_context(context);
 }
 
+/**
+ * __audit_set_landlock_hierarchy - record Landlock domain denying the syscall
+ * @hierarchy: Landlock domain's hierarchy
+ */
+void __audit_set_landlock_hierarchy(struct landlock_hierarchy *hierarchy)
+{
+	struct audit_context *context = audit_context();
+
+	landlock_get_hierarchy(hierarchy);
+	context->landlock_hierarchy = hierarchy;
+}
+
 static inline void handle_one(const struct inode *inode)
 {
 	struct audit_context *context;
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index cc01a0d663f3..a2d344c24f10 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -8,6 +8,7 @@
 #include <kunit/test.h>
 #include <linux/audit.h>
 #include <linux/bitops.h>
+#include <linux/landlock.h>
 #include <linux/lsm_audit.h>
 #include <linux/pid.h>
 #include <linux/uidgid.h>
@@ -422,6 +423,7 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
 			get_hierarchy(domain, request->layer_plus_one - 1);
 	}
 
+	/* Static filtering. */
 	if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
 		return;
 
@@ -435,6 +437,8 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
 	if (!unlikely(audit_context() && audit_enabled))
 		return;
 
+	/* Dynamic filtering according to the domain's creator. */
+	audit_set_landlock_hierarchy(youngest_denied);
 	ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
 			     AUDIT_LANDLOCK_DENY);
 	if (!ab)
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index f1a0d1b9af7c..7fe2a48ba102 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -39,6 +39,7 @@ void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
 #ifdef CONFIG_AUDIT
 		put_cred(hierarchy->details->cred);
 		put_pid(hierarchy->details->pid);
+		landlock_put_object(hierarchy->details->exe_object);
 		kfree(hierarchy->details);
 #endif /* CONFIG_AUDIT */
 
@@ -56,11 +57,13 @@ void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
  *            returned buffer, if any.
  * @path_size: Returned size of the @path string (including the trailing null
  *             character), if any.
+ * @inode: Returned inode of the executable, if any.
  *
  * Returns: A pointer to an allocated buffer where @path point to, %NULL if
  * there is no executable path, or an error otherwise.
  */
-static const void *get_current_exe(const char **path_str, size_t *path_size)
+static const void *get_current_exe(const char **path_str, size_t *path_size,
+				   struct inode **inode)
 {
 	struct mm_struct *mm = current->mm;
 	struct file *file __free(fput) = NULL;
@@ -93,6 +96,8 @@ static const void *get_current_exe(const char **path_str, size_t *path_size)
 
 	*path_size = size;
 	*path_str = path;
+	ihold(file_inode(file));
+	*inode = file_inode(file);
 	return no_free_ptr(buffer);
 }
 
@@ -108,8 +113,9 @@ static struct landlock_details *get_current_details(void)
 	size_t path_size = sizeof(null_path);
 	struct landlock_details *details;
 	const void *buffer __free(kfree) = NULL;
+	struct inode *inode __free(iput) = NULL;
 
-	buffer = get_current_exe(&path_str, &path_size);
+	buffer = get_current_exe(&path_str, &path_size, &inode);
 	if (IS_ERR(buffer))
 		return ERR_CAST(buffer);
 
@@ -125,6 +131,11 @@ static struct landlock_details *get_current_details(void)
 
 	memcpy(details->exe_path, path_str, path_size);
 	ktime_get_coarse_real_ts64(&details->creation);
+	if (inode) {
+		details->exe_object = landlock_get_inode_object(inode);
+		details->exe_ino = inode->i_ino;
+		details->exe_dev = inode->i_sb->s_dev;
+	}
 
 	WARN_ON_ONCE(current_cred() != current_real_cred());
 	details->cred = get_current_cred();
@@ -267,6 +278,45 @@ static void test_landlock_get_deny_masks(struct kunit *const test)
 
 #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
 
+/**
+ * landlock_read_domain_exe - Read the domain creator's exe information
+ *
+ * @ino: Returned inode number, only set if the returned value is true.
+ * @dev: Returned device number, only set if the returned value is true.
+ *
+ * Returns: True if the underlying exe's inode is still alive (i.e. its
+ * superblock was not unmounted).
+ *
+ * To avoid a race condition, the caller must make sure that the compared
+ * object could not be changed in the check window.  audit_filter() and
+ * audit_filter_rules() dereference the compared audit entry in an RCU
+ * read-side critical section, which means that the related checked ino/dev
+ * stays consistent (see audit_update_watch()).
+ */
+bool landlock_read_domain_exe(const struct landlock_hierarchy *const hierarchy,
+			      ino_t *const ino, dev_t *const dev)
+{
+	if (!hierarchy || WARN_ON_ONCE(!ino || !dev))
+		return false;
+
+	/*
+	 * If the underlying inode does not exist, this means that the inode's
+	 * superblock was unmounted, and @ino and @dev do not identify the same
+	 * file.  Similarly, a removed inode leads to the related audit rule
+	 * removal, see audit_watch_handle_event()'s handling of
+	 * FS_DELETE_SELF|FS_UNMOUNT|FS_MOVE_SELF.
+	 *
+	 * If the underlying inode exists, this means that the returned @ino
+	 * and @dev may match an audit rule.
+	 */
+	if (!READ_ONCE(hierarchy->details->exe_object->underobj))
+		return false;
+
+	*ino = hierarchy->details->exe_ino;
+	*dev = hierarchy->details->exe_dev;
+	return true;
+}
+
 #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
 
 static struct kunit_case test_cases[] = {
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 7176043bd0ff..7cf88b2bc72d 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -19,6 +19,7 @@
 #include <linux/time64.h>
 
 #include "access.h"
+#include "object.h"
 
 enum landlock_log_status {
 	LANDLOCK_LOG_PENDING = 0,
@@ -53,6 +54,25 @@ struct landlock_details {
 	 * identifies the same task.
 	 */
 	struct pid *pid;
+	/**
+	 * @exe_object: Landlock object tracking the executable binary that
+	 * restricted itself, for its whole lifetime.
+	 */
+	struct landlock_object *exe_object;
+	/**
+	 * @exe_ino: Inode number cache of the executable binary.  This should
+	 * only be read if @exe_object is not NULL, while holding the related
+	 * inode.  This is useful to avoid locking @exe_object or the
+	 * underlying inode.
+	 */
+	ino_t exe_ino;
+	/**
+	 * @exe_dev: Device number cache of the executable binary.  This should
+	 * only be read if @exe_object is not NULL, while holding the related
+	 * inode.  This is useful to avoid locking @exe_object or the
+	 * underlying inode.
+	 */
+	dev_t exe_dev;
 	/**
 	 * @comm: Command line of the task that initially restricted itself, at
 	 * creation time.  Always NULL terminated.
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 4b718b669ebe..d67f0da1b782 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -64,7 +64,7 @@ static void release_inode(struct landlock_object *const object)
 	 * Protects against concurrent use by hook_sb_delete() of the reference
 	 * to the underlying inode.
 	 */
-	object->underobj = NULL;
+	WRITE_ONCE(object->underobj, NULL);
 	/*
 	 * Makes sure that if the filesystem is concurrently unmounted,
 	 * hook_sb_delete() will wait for us to finish iput().
@@ -1323,7 +1323,7 @@ static void hook_sb_delete(struct super_block *const sb)
 		 */
 		spin_lock(&object->lock);
 		if (object->underobj == inode) {
-			object->underobj = NULL;
+			WRITE_ONCE(object->underobj, NULL);
 			spin_unlock(&object->lock);
 			rcu_read_unlock();
 
diff --git a/security/landlock/object.h b/security/landlock/object.h
index 5f28c35e8aa8..216351f45e1c 100644
--- a/security/landlock/object.h
+++ b/security/landlock/object.h
@@ -57,7 +57,9 @@ struct landlock_object {
 	/**
 	 * @underobj: Used when cleaning up an object and to mark an object as
 	 * tied to its underlying kernel structure.  This pointer is protected
-	 * by @lock.  Cf. landlock_release_inodes() and release_inode().
+	 * by @lock, but it may concurrently be checked (but not dereferenced).
+	 * Cf. landlock_release_inodes(), release_inode(), and
+	 * landlock_read_domain_exe().
 	 */
 	void *underobj;
 	union {
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 29/30] selftests/landlock: Test audit rule with AUDIT_EXE_LANDLOCK_DOM
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (27 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  2025-01-08 15:43 ` [PATCH v4 30/30] selftests/landlock: Test compatibility with audit rule lists Mickaël Salaün
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add audit_rule.exe_landlock_domain tests to filter Landlock denials
according to the binary that created the sandbox.

The wait-pipe.c test program is updated to sandbox itself and send a
(denied) signal to its parent.

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

Changes since v3:
- New patch.
---
 tools/testing/selftests/landlock/audit.h      |   1 +
 tools/testing/selftests/landlock/audit_test.c | 153 ++++++++++++++++++
 tools/testing/selftests/landlock/wait-pipe.c  |  30 +++-
 3 files changed, 183 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h
index 37979a62478c..fba96123776c 100644
--- a/tools/testing/selftests/landlock/audit.h
+++ b/tools/testing/selftests/landlock/audit.h
@@ -140,6 +140,7 @@ static int audit_filter_exe(const int audit_fd,
 
 	switch (filter->record_type) {
 	case AUDIT_EXE:
+	case AUDIT_EXE_LANDLOCK_DENY:
 		break;
 	default:
 		return -EINVAL;
diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
index d5330e843395..921f316ddbf8 100644
--- a/tools/testing/selftests/landlock/audit_test.c
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -7,7 +7,9 @@
 
 #define _GNU_SOURCE
 #include <errno.h>
+#include <limits.h>
 #include <linux/landlock.h>
+#include <stdlib.h>
 #include <sys/mount.h>
 #include <sys/prctl.h>
 #include <sys/types.h>
@@ -33,6 +35,22 @@ static int matches_log_umount(struct __test_metadata *const _metadata,
 				  REGEX_LANDLOCK_PREFIX " blockers=.*");
 }
 
+static int matches_log_signal(struct __test_metadata *const _metadata,
+			      int audit_fd, const pid_t opid)
+{
+	static const char log_template[] = REGEX_LANDLOCK_PREFIX
+		" blockers=scope.signal opid=%d ocomm=\"audit_test\"$";
+	char log_match[sizeof(log_template) + 10];
+	int log_match_len;
+
+	log_match_len =
+		snprintf(log_match, sizeof(log_match), log_template, opid);
+	if (log_match_len > sizeof(log_match))
+		return -E2BIG;
+
+	return audit_match_record(audit_fd, AUDIT_LANDLOCK_DENY, log_match);
+}
+
 FIXTURE(audit)
 {
 	struct audit_filter audit_filter;
@@ -155,4 +173,139 @@ TEST_F(audit, fs_deny)
 		_metadata->exit_code = KSFT_FAIL;
 }
 
+FIXTURE(audit_rule)
+{
+	struct audit_filter audit_filter_main, audit_filter_test;
+	int audit_fd;
+};
+
+FIXTURE_VARIANT(audit_rule)
+{
+	const bool with_exe_landlock_deny_child;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit_rule, exe_landlock_deny_child) {
+	/* clang-format on */
+	.with_exe_landlock_deny_child = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit_rule, exe_landlock_deny_parent) {
+	/* clang-format on */
+	.with_exe_landlock_deny_child = false,
+};
+
+FIXTURE_SETUP(audit_rule)
+{
+	const char *path = NULL;
+
+	disable_caps(_metadata);
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+
+	if (variant->with_exe_landlock_deny_child)
+		/* Filter on the sandboxer instead of the current exe. */
+		path = bin_wait_pipe;
+
+	self->audit_fd = audit_init();
+	EXPECT_LE(0, self->audit_fd)
+	{
+		const char *error_msg;
+
+		/* kill "$(auditctl -s | sed -ne 's/^pid \([0-9]\+\)$/\1/p')" */
+		if (self->audit_fd == -EEXIST)
+			error_msg = "socket already in use (e.g. auditd)";
+		else
+			error_msg = strerror(-self->audit_fd);
+		TH_LOG("Failed to initialize audit: %s", error_msg);
+	}
+
+	/* Applies main filter for the test task. */
+	EXPECT_EQ(0, audit_init_filter_exe(AUDIT_EXE, &self->audit_filter_main,
+					   bin_wait_pipe));
+	EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter_main,
+				      AUDIT_ADD_RULE, AUDIT_FILTER_EXCLUDE));
+
+	/* Applies test filter for the test task or the current task. */
+	EXPECT_EQ(0, audit_init_filter_exe(AUDIT_EXE_LANDLOCK_DENY,
+					   &self->audit_filter_test, path));
+	EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter_test,
+				      AUDIT_ADD_RULE, AUDIT_FILTER_EXCLUDE));
+
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+FIXTURE_TEARDOWN(audit_rule)
+{
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter_main,
+				      AUDIT_DEL_RULE, AUDIT_FILTER_EXCLUDE));
+	EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter_test,
+				      AUDIT_DEL_RULE, AUDIT_FILTER_EXCLUDE));
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+	EXPECT_EQ(0, close(self->audit_fd));
+}
+
+TEST_F(audit_rule, exe_landlock_deny)
+{
+	struct audit_records records;
+	int pipe_child[2], pipe_parent[2];
+	char buf_parent;
+	pid_t child;
+	int status;
+
+	ASSERT_EQ(0, pipe2(pipe_child, 0));
+	ASSERT_EQ(0, pipe2(pipe_parent, 0));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		char pipe_child_str[12], pipe_parent_str[12];
+		char *const argv[] = { (char *)bin_wait_pipe, pipe_child_str,
+				       pipe_parent_str, NULL };
+
+		/* Passes the pipe FDs to the executed binary. */
+		EXPECT_EQ(0, close(pipe_child[0]));
+		EXPECT_EQ(0, close(pipe_parent[1]));
+		snprintf(pipe_child_str, sizeof(pipe_child_str), "%d",
+			 pipe_child[1]);
+		snprintf(pipe_parent_str, sizeof(pipe_parent_str), "%d",
+			 pipe_parent[0]);
+
+		ASSERT_EQ(0, execve(argv[0], argv, NULL))
+		{
+			TH_LOG("Failed to execute \"%s\": %s", argv[0],
+			       strerror(errno));
+		};
+		_exit(1);
+		return;
+	}
+
+	EXPECT_EQ(0, close(pipe_child[1]));
+	EXPECT_EQ(0, close(pipe_parent[0]));
+
+	/* Waits for the child. */
+	EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1));
+
+	/* Tests that there was no denial until now. */
+	audit_count_records(self->audit_fd, &records);
+	EXPECT_EQ(0, records.deny);
+
+	/* Signals the child to terminate. */
+	EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
+
+	/* Tests that the audit record only matches the child. */
+	if (variant->with_exe_landlock_deny_child) {
+		EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd,
+						getpid()));
+	} else {
+		audit_count_records(self->audit_fd, &records);
+		EXPECT_EQ(0, records.deny);
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(0, WEXITSTATUS(status));
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/wait-pipe.c b/tools/testing/selftests/landlock/wait-pipe.c
index 0dbcd260a0fa..153f8ca93ac6 100644
--- a/tools/testing/selftests/landlock/wait-pipe.c
+++ b/tools/testing/selftests/landlock/wait-pipe.c
@@ -2,20 +2,31 @@
 /*
  * Write in a pipe and wait.
  *
- * Used by layout1.umount_sandboxer from fs_test.c
+ * Used by layout1.umount_sandboxer from fs_test.c and
+ * audit_rule.exe_landlock_deny from audit_test.c
  *
  * Copyright © 2024-2025 Microsoft Corporation
  */
 
 #define _GNU_SOURCE
+#include <linux/landlock.h>
+#include <linux/prctl.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/prctl.h>
 #include <unistd.h>
 
+#include "wrappers.h"
+
 int main(int argc, char *argv[])
 {
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.scoped = LANDLOCK_SCOPE_SIGNAL,
+	};
 	int pipe_child, pipe_parent;
 	char buf;
+	int ruleset_fd;
 
 	/* The first argument must be the file descriptor number of a pipe. */
 	if (argc != 3) {
@@ -26,6 +37,20 @@ int main(int argc, char *argv[])
 	pipe_child = atoi(argv[1]);
 	pipe_parent = atoi(argv[2]);
 
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	if (ruleset_fd < 0) {
+		perror("Failed to create a ruleset");
+		return 1;
+	}
+
+	prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+	if (landlock_restrict_self(ruleset_fd, 0)) {
+		perror("Failed to restrict self");
+		return 1;
+	}
+	close(ruleset_fd);
+
 	/* Signals that we are waiting. */
 	if (write(pipe_child, ".", 1) != 1) {
 		perror("Failed to write to first argument");
@@ -38,5 +63,8 @@ int main(int argc, char *argv[])
 		return 1;
 	}
 
+	/* Tries to send a signal. */
+	kill(getppid(), 0);
+
 	return 0;
 }
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH v4 30/30] selftests/landlock: Test compatibility with audit rule lists
  2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
                   ` (28 preceding siblings ...)
  2025-01-08 15:43 ` [PATCH v4 29/30] selftests/landlock: Test audit rule with AUDIT_EXE_LANDLOCK_DOM Mickaël Salaün
@ 2025-01-08 15:43 ` Mickaël Salaün
  29 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-08 15:43 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

Add compatibility.lists tests to make sure AUDIT_EXE_LANDLOCK_DENY is
only allowed for AUDIT_FILTER_EXCLUDE, AUDIT_FILTER_EXIT, and
AUDIT_FILTER_URING_EXIT.

Test coverage for security/landlock is 93.5% of 1635 lines according to
gcc/gcov-14.

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

Changes since v3:
- New patch.
---
 tools/testing/selftests/landlock/audit_test.c | 78 +++++++++++++++++++
 1 file changed, 78 insertions(+)

diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
index 921f316ddbf8..d9f227680641 100644
--- a/tools/testing/selftests/landlock/audit_test.c
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -308,4 +308,82 @@ TEST_F(audit_rule, exe_landlock_deny)
 	ASSERT_EQ(0, WEXITSTATUS(status));
 }
 
+FIXTURE(compatibility)
+{
+	struct audit_filter filter_self;
+	int audit_fd;
+};
+
+FIXTURE_SETUP(compatibility)
+{
+	disable_caps(_metadata);
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	self->audit_fd = audit_init_with_exe_filter(&self->filter_self);
+	EXPECT_LE(0, self->audit_fd)
+	{
+		const char *error_msg;
+
+		/* kill "$(auditctl -s | sed -ne 's/^pid \([0-9]\+\)$/\1/p')" */
+		if (self->audit_fd == -EEXIST)
+			error_msg = "socket already in use (e.g. auditd)";
+		else
+			error_msg = strerror(-self->audit_fd);
+		TH_LOG("Failed to initialize audit: %s", error_msg);
+	}
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+FIXTURE_TEARDOWN(compatibility)
+{
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	EXPECT_EQ(0, audit_cleanup(self->audit_fd, &self->filter_self));
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+TEST_F(compatibility, lists)
+{
+	struct audit_filter filter_test;
+	size_t num_ok = 0;
+	__u32 list;
+
+	EXPECT_EQ(0, audit_init_filter_exe(AUDIT_EXE_LANDLOCK_DENY,
+					   &filter_test, NULL));
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+
+	for (list = 0; list < AUDIT_NR_FILTERS; list++) {
+		int err;
+
+		switch (list) {
+		case AUDIT_FILTER_EXIT:
+		case AUDIT_FILTER_EXCLUDE:
+		case AUDIT_FILTER_URING_EXIT:
+			num_ok++;
+			err = 0;
+			break;
+		default:
+			err = -EINVAL;
+			break;
+		}
+
+		/*
+		 * Testing AUDIT_FILTER_ENTRY prints "auditfilter:
+		 * AUDIT_FILTER_ENTRY is deprecated" in kernel logs.
+		 */
+		EXPECT_EQ(err, audit_filter_exe(self->audit_fd, &filter_test,
+						AUDIT_ADD_RULE, list))
+		{
+			TH_LOG("Unexpected result for list %u", list);
+		}
+		EXPECT_EQ(err, audit_filter_exe(self->audit_fd, &filter_test,
+						AUDIT_DEL_RULE, list))
+		{
+			TH_LOG("Unexpected result for list %u", list);
+		}
+	}
+
+	/* Makes sure the three accepted lists are checked. */
+	EXPECT_EQ(3, num_ok);
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
 TEST_HARNESS_MAIN
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 03/30] landlock: Factor out check_access_path()
  2025-01-08 15:43 ` [PATCH v4 03/30] landlock: Factor out check_access_path() Mickaël Salaün
@ 2025-01-10 11:23   ` Mickaël Salaün
  0 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-10 11:23 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module

On Wed, Jan 08, 2025 at 04:43:11PM +0100, Mickaël Salaün wrote:
> Merge check_access_path() into current_check_access_path() and make
> hook_path_mknod() use it.
> 
> Cc: Günther Noack <gnoack@google.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-4-mic@digikod.net

Pushed in my next tree to simplify next patch series.

> ---
> 
> Changes since v1:
> - Rebased on the TCP patch series.
> - Remove inlining removal which was merged.
> ---
>  security/landlock/fs.c | 32 +++++++++++---------------------
>  1 file changed, 11 insertions(+), 21 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index e31b97a9f175..d911c924843f 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -908,28 +908,22 @@ static bool is_access_to_paths_allowed(
>  	return allowed_parent1 && allowed_parent2;
>  }
>  
> -static int check_access_path(const struct landlock_ruleset *const domain,
> -			     const struct path *const path,
> -			     access_mask_t access_request)
> -{
> -	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
> -
> -	access_request = landlock_init_layer_masks(
> -		domain, access_request, &layer_masks, LANDLOCK_KEY_INODE);
> -	if (is_access_to_paths_allowed(domain, path, access_request,
> -				       &layer_masks, NULL, 0, NULL, NULL))
> -		return 0;
> -	return -EACCES;
> -}
> -
>  static int current_check_access_path(const struct path *const path,
> -				     const access_mask_t access_request)
> +				     access_mask_t access_request)
>  {
>  	const struct landlock_ruleset *const dom = get_current_fs_domain();
> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
>  
>  	if (!dom)
>  		return 0;
> -	return check_access_path(dom, path, access_request);
> +
> +	access_request = landlock_init_layer_masks(
> +		dom, access_request, &layer_masks, LANDLOCK_KEY_INODE);
> +	if (is_access_to_paths_allowed(dom, path, access_request, &layer_masks,
> +				       NULL, 0, NULL, NULL))
> +		return 0;
> +
> +	return -EACCES;
>  }
>  
>  static access_mask_t get_mode_access(const umode_t mode)
> @@ -1414,11 +1408,7 @@ static int hook_path_mknod(const struct path *const dir,
>  			   struct dentry *const dentry, const umode_t mode,
>  			   const unsigned int dev)
>  {
> -	const struct landlock_ruleset *const dom = get_current_fs_domain();
> -
> -	if (!dom)
> -		return 0;
> -	return check_access_path(dom, dir, get_mode_access(mode));
> +	return current_check_access_path(dir, get_mode_access(mode));
>  }
>  
>  static int hook_path_symlink(const struct path *const dir,
> -- 
> 2.47.1
> 
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 05/30] landlock: Move access types
  2025-01-08 15:43 ` [PATCH v4 05/30] landlock: Move access types Mickaël Salaün
@ 2025-01-10 11:23   ` Mickaël Salaün
  0 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-10 11:23 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module

On Wed, Jan 08, 2025 at 04:43:13PM +0100, Mickaël Salaün wrote:
> Move LANDLOCK_ACCESS_FS_INITIALLY_DENIED, access_mask_t, struct
> access_mask, and struct access_masks_all to a dedicated access.h file.
> 
> Rename LANDLOCK_ACCESS_FS_INITIALLY_DENIED to
> _LANDLOCK_ACCESS_FS_INITIALLY_DENIED to make it clear that it's not part
> of UAPI.  Add some newlines when appropriate.
> 
> This file will be extended with following commits, and it will help to
> avoid dependency loops.
> 
> Cc: Günther Noack <gnoack@google.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-6-mic@digikod.net

Pushed in my next tree to simplify next patch series.

> ---
> 
> Changes since v2:
> - Rebased on the (now merged) masks improvement patches.
> - Move ACCESS_FS_OPTIONAL to a following patch introducing deny_masks_t,
>   spotted by Francis Laniel.
> - Move and rename LANDLOCK_ACCESS_FS_INITIALLY_DENIED to
>   _LANDLOCK_ACCESS_FS_INITIALLY_DENIED.
> 
> Changes since v1:
> - New patch
> ---
>  security/landlock/access.h  | 62 +++++++++++++++++++++++++++++++++++++
>  security/landlock/fs.c      |  3 +-
>  security/landlock/fs.h      |  1 +
>  security/landlock/ruleset.c |  1 +
>  security/landlock/ruleset.h | 47 ++--------------------------
>  5 files changed, 68 insertions(+), 46 deletions(-)
>  create mode 100644 security/landlock/access.h
> 
> diff --git a/security/landlock/access.h b/security/landlock/access.h
> new file mode 100644
> index 000000000000..9ee4b30a87e6
> --- /dev/null
> +++ b/security/landlock/access.h
> @@ -0,0 +1,62 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Landlock LSM - Access types and helpers
> + *
> + * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
> + * Copyright © 2018-2020 ANSSI
> + * Copyright © 2024-2025 Microsoft Corporation
> + */
> +
> +#ifndef _SECURITY_LANDLOCK_ACCESS_H
> +#define _SECURITY_LANDLOCK_ACCESS_H
> +
> +#include <linux/bitops.h>
> +#include <linux/build_bug.h>
> +#include <linux/kernel.h>
> +#include <uapi/linux/landlock.h>
> +
> +#include "limits.h"
> +
> +/*
> + * All access rights that are denied by default whether they are handled or not
> + * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
> + * entries when we need to get the absolute handled access masks.
> + */
> +/* clang-format off */
> +#define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
> +	LANDLOCK_ACCESS_FS_REFER)
> +/* clang-format on */
> +
> +typedef u16 access_mask_t;
> +
> +/* Makes sure all filesystem access rights can be stored. */
> +static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
> +/* Makes sure all network access rights can be stored. */
> +static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
> +/* Makes sure all scoped rights can be stored. */
> +static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
> +/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
> +static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
> +
> +/* Ruleset access masks. */
> +struct access_masks {
> +	access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
> +	access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> +	access_mask_t scope : LANDLOCK_NUM_SCOPE;
> +};
> +
> +union access_masks_all {
> +	struct access_masks masks;
> +	u32 all;
> +};
> +
> +/* Makes sure all fields are covered. */
> +static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
> +	      sizeof(typeof_member(union access_masks_all, all)));
> +
> +typedef u16 layer_mask_t;
> +
> +/* Makes sure all layers can be checked. */
> +static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
> +
> +#endif /* _SECURITY_LANDLOCK_ACCESS_H */
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index d911c924843f..3da5f1945158 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -36,6 +36,7 @@
>  #include <uapi/linux/fiemap.h>
>  #include <uapi/linux/landlock.h>
>  
> +#include "access.h"
>  #include "common.h"
>  #include "cred.h"
>  #include "fs.h"
> @@ -393,7 +394,7 @@ get_handled_fs_accesses(const struct landlock_ruleset *const domain)
>  {
>  	/* Handles all initially denied by default access rights. */
>  	return landlock_union_access_masks(domain).fs |
> -	       LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
> +	       _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
>  }
>  
>  static const struct access_masks any_fs = {
> diff --git a/security/landlock/fs.h b/security/landlock/fs.h
> index 1487e1f023a1..d445f411c26a 100644
> --- a/security/landlock/fs.h
> +++ b/security/landlock/fs.h
> @@ -13,6 +13,7 @@
>  #include <linux/init.h>
>  #include <linux/rcupdate.h>
>  
> +#include "access.h"
>  #include "ruleset.h"
>  #include "setup.h"
>  
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index a93bdbf52fff..cae69f2f01d9 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -20,6 +20,7 @@
>  #include <linux/spinlock.h>
>  #include <linux/workqueue.h>
>  
> +#include "access.h"
>  #include "limits.h"
>  #include "object.h"
>  #include "ruleset.h"
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 631e24d4ffe9..2f29b9f40392 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -9,58 +9,15 @@
>  #ifndef _SECURITY_LANDLOCK_RULESET_H
>  #define _SECURITY_LANDLOCK_RULESET_H
>  
> -#include <linux/bitops.h>
> -#include <linux/build_bug.h>
> -#include <linux/kernel.h>
>  #include <linux/mutex.h>
>  #include <linux/rbtree.h>
>  #include <linux/refcount.h>
>  #include <linux/workqueue.h>
> -#include <uapi/linux/landlock.h>
>  
> +#include "access.h"
>  #include "limits.h"
>  #include "object.h"
>  
> -/*
> - * All access rights that are denied by default whether they are handled or not
> - * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
> - * entries when we need to get the absolute handled access masks.
> - */
> -/* clang-format off */
> -#define LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
> -	LANDLOCK_ACCESS_FS_REFER)
> -/* clang-format on */
> -
> -typedef u16 access_mask_t;
> -/* Makes sure all filesystem access rights can be stored. */
> -static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
> -/* Makes sure all network access rights can be stored. */
> -static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
> -/* Makes sure all scoped rights can be stored. */
> -static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
> -/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
> -static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
> -
> -/* Ruleset access masks. */
> -struct access_masks {
> -	access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
> -	access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> -	access_mask_t scope : LANDLOCK_NUM_SCOPE;
> -};
> -
> -union access_masks_all {
> -	struct access_masks masks;
> -	u32 all;
> -};
> -
> -/* Makes sure all fields are covered. */
> -static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
> -	      sizeof(typeof_member(union access_masks_all, all)));
> -
> -typedef u16 layer_mask_t;
> -/* Makes sure all layers can be checked. */
> -static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
> -
>  /**
>   * struct landlock_layer - Access rights for a given layer
>   */
> @@ -366,7 +323,7 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>  {
>  	/* Handles all initially denied by default access rights. */
>  	return ruleset->access_masks[layer_level].fs |
> -	       LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
> +	       _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
>  }
>  
>  static inline access_mask_t
> -- 
> 2.47.1
> 
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 06/30] landlock: Simplify initially denied access rights
  2025-01-08 15:43 ` [PATCH v4 06/30] landlock: Simplify initially denied access rights Mickaël Salaün
@ 2025-01-10 11:24   ` Mickaël Salaün
  0 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-10 11:24 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module

On Wed, Jan 08, 2025 at 04:43:14PM +0100, Mickaël Salaün wrote:
> Upgrade domain's handled access masks when creating a domain from a
> ruleset, instead of converting them at runtime.  This is more consistent
> and helps with audit support.
> 
> Cc: Günther Noack <gnoack@google.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-7-mic@digikod.net

Pushed in my next tree to simplify next patch series.

> ---
> 
> Changes since v2:
> - New patch.
> ---
>  security/landlock/access.h  | 17 ++++++++++++++++-
>  security/landlock/fs.c      | 10 +---------
>  security/landlock/ruleset.c |  3 ++-
>  3 files changed, 19 insertions(+), 11 deletions(-)
> 
> diff --git a/security/landlock/access.h b/security/landlock/access.h
> index 9ee4b30a87e6..74fd8f399fbd 100644
> --- a/security/landlock/access.h
> +++ b/security/landlock/access.h
> @@ -20,7 +20,8 @@
>  /*
>   * All access rights that are denied by default whether they are handled or not
>   * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
> - * entries when we need to get the absolute handled access masks.
> + * entries when we need to get the absolute handled access masks, see
> + * landlock_upgrade_handled_access_masks().
>   */
>  /* clang-format off */
>  #define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
> @@ -59,4 +60,18 @@ typedef u16 layer_mask_t;
>  /* Makes sure all layers can be checked. */
>  static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
>  
> +/* Upgrades with all initially denied by default access rights. */
> +static inline struct access_masks
> +landlock_upgrade_handled_access_masks(struct access_masks access_masks)
> +{
> +	/*
> +	 * All access rights that are denied by default whether they are
> +	 * explicitly handled or not.
> +	 */
> +	if (access_masks.fs)
> +		access_masks.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
> +
> +	return access_masks;
> +}
> +
>  #endif /* _SECURITY_LANDLOCK_ACCESS_H */
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 3da5f1945158..9779170d9199 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -389,14 +389,6 @@ static bool is_nouser_or_private(const struct dentry *dentry)
>  		unlikely(IS_PRIVATE(d_backing_inode(dentry))));
>  }
>  
> -static access_mask_t
> -get_handled_fs_accesses(const struct landlock_ruleset *const domain)
> -{
> -	/* Handles all initially denied by default access rights. */
> -	return landlock_union_access_masks(domain).fs |
> -	       _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
> -}
> -
>  static const struct access_masks any_fs = {
>  	.fs = ~0,
>  };
> @@ -788,7 +780,7 @@ static bool is_access_to_paths_allowed(
>  		 * a superset of the meaningful requested accesses).
>  		 */
>  		access_masked_parent1 = access_masked_parent2 =
> -			get_handled_fs_accesses(domain);
> +			landlock_union_access_masks(domain).fs;
>  		is_dom_check = true;
>  	} else {
>  		if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index cae69f2f01d9..dbc528f5f3b7 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -385,7 +385,8 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>  		err = -EINVAL;
>  		goto out_unlock;
>  	}
> -	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
> +	dst->access_masks[dst->num_layers - 1] =
> +		landlock_upgrade_handled_access_masks(src->access_masks[0]);
>  
>  	/* Merges the @src inode tree. */
>  	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
> -- 
> 2.47.1
> 
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 11/30] landlock: Align partial refer access checks with final ones
  2025-01-08 15:43 ` [PATCH v4 11/30] landlock: Align partial refer access checks with final ones Mickaël Salaün
@ 2025-01-10 11:24   ` Mickaël Salaün
  0 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-10 11:24 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module

On Wed, Jan 08, 2025 at 04:43:19PM +0100, Mickaël Salaün wrote:
> Fix a logical issue that could have been visible if the source or the
> destination of a rename/link action was allowed for either the source or
> the destination but not both.  However, this logical bug is unreachable
> because either:
> - the rename/link action is allowed by the access rights tied to the
>   same mount point (without relying on access rights in a parent mount
>   point) and the access request is allowed (i.e. allow_parent1 and
>   allow_parent2 are true in current_check_refer_path),
> - or a common rule in a parent mount point updates the access check for
>   the source and the destination (cf. is_access_to_paths_allowed).
> 
> See the following layout1.refer_part_mount_tree_is_allowed test that
> work with and without this fix.
> 
> This fix does not impact current code but it is required for the audit
> support.
> 
> Cc: Günther Noack <gnoack@google.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-12-mic@digikod.net

Pushed in my next tree to simplify next patch series.

> ---
> 
> Changes since v2:
> - New patch.
> ---
>  security/landlock/fs.c | 14 +++++++++++++-
>  1 file changed, 13 insertions(+), 1 deletion(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 171012efb559..ddadc465581e 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -567,6 +567,12 @@ static void test_no_more_access(struct kunit *const test)
>  #undef NMA_TRUE
>  #undef NMA_FALSE
>  
> +static bool is_layer_masks_allowed(
> +	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
> +{
> +	return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
> +}
> +
>  /*
>   * Removes @layer_masks accesses that are not requested.
>   *
> @@ -584,7 +590,8 @@ scope_to_request(const access_mask_t access_request,
>  
>  	for_each_clear_bit(access_bit, &access_req, ARRAY_SIZE(*layer_masks))
>  		(*layer_masks)[access_bit] = 0;
> -	return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
> +
> +	return is_layer_masks_allowed(layer_masks);
>  }
>  
>  #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
> @@ -773,9 +780,14 @@ static bool is_access_to_paths_allowed(
>  	if (WARN_ON_ONCE(domain->num_layers < 1 || !layer_masks_parent1))
>  		return false;
>  
> +	allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
> +
>  	if (unlikely(layer_masks_parent2)) {
>  		if (WARN_ON_ONCE(!dentry_child1))
>  			return false;
> +
> +		allowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);
> +
>  		/*
>  		 * For a double request, first check for potential privilege
>  		 * escalation by looking at domain handled accesses (which are
> -- 
> 2.47.1
> 
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 12/30] selftests/landlock: Add test to check partial access in a mount tree
  2025-01-08 15:43 ` [PATCH v4 12/30] selftests/landlock: Add test to check partial access in a mount tree Mickaël Salaün
@ 2025-01-10 11:24   ` Mickaël Salaün
  0 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-10 11:24 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module

On Wed, Jan 08, 2025 at 04:43:20PM +0100, Mickaël Salaün wrote:
> Add layout1.refer_part_mount_tree_is_allowed to test the masked logical
> issue regarding collect_domain_accesses() calls followed by the
> is_access_to_paths_allowed() check in current_check_refer_path().  See
> previous commit.
> 
> This test should work without the previous fix as well, but it enables
> us to make sure future changes will not have impact regarding this
> behavior.
> 
> Cc: Günther Noack <gnoack@google.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-13-mic@digikod.net

Pushed in my next tree to simplify next patch series.

> ---
> 
> Changes since v2:
> - New patch.
> ---
>  tools/testing/selftests/landlock/fs_test.c | 54 ++++++++++++++++++++--
>  1 file changed, 50 insertions(+), 4 deletions(-)
> 
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index 6788762188fe..42ce1e79ba82 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -85,6 +85,9 @@ static const char file1_s3d1[] = TMP_DIR "/s3d1/f1";
>  /* dir_s3d2 is a mount point. */
>  static const char dir_s3d2[] = TMP_DIR "/s3d1/s3d2";
>  static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3";
> +static const char file1_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3/f1";
> +static const char dir_s3d4[] = TMP_DIR "/s3d1/s3d2/s3d4";
> +static const char file1_s3d4[] = TMP_DIR "/s3d1/s3d2/s3d4/f1";
>  
>  /*
>   * layout1 hierarchy:
> @@ -108,8 +111,11 @@ static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3";
>   * │           └── f2
>   * └── s3d1
>   *     ├── f1
> - *     └── s3d2
> - *         └── s3d3
> + *     └── s3d2 [mount point]
> + *         ├── s3d3
> + *         │   └── f1
> + *         └── s3d4
> + *             └── f1
>   */
>  
>  static bool fgrep(FILE *const inf, const char *const str)
> @@ -358,7 +364,8 @@ static void create_layout1(struct __test_metadata *const _metadata)
>  	ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s3d2));
>  	clear_cap(_metadata, CAP_SYS_ADMIN);
>  
> -	ASSERT_EQ(0, mkdir(dir_s3d3, 0700));
> +	create_file(_metadata, file1_s3d3);
> +	create_file(_metadata, file1_s3d4);
>  }
>  
>  static void remove_layout1(struct __test_metadata *const _metadata)
> @@ -378,7 +385,8 @@ static void remove_layout1(struct __test_metadata *const _metadata)
>  	EXPECT_EQ(0, remove_path(dir_s2d2));
>  
>  	EXPECT_EQ(0, remove_path(file1_s3d1));
> -	EXPECT_EQ(0, remove_path(dir_s3d3));
> +	EXPECT_EQ(0, remove_path(file1_s3d3));
> +	EXPECT_EQ(0, remove_path(file1_s3d4));
>  	set_cap(_metadata, CAP_SYS_ADMIN);
>  	umount(dir_s3d2);
>  	clear_cap(_metadata, CAP_SYS_ADMIN);
> @@ -2444,6 +2452,44 @@ TEST_F_FORK(layout1, refer_mount_root_deny)
>  	EXPECT_EQ(0, close(root_fd));
>  }
>  
> +TEST_F_FORK(layout1, refer_part_mount_tree_is_allowed)
> +{
> +	const struct rule layer1[] = {
> +		{
> +			/* Parent mount point. */
> +			.path = dir_s3d1,
> +			.access = LANDLOCK_ACCESS_FS_REFER |
> +				  LANDLOCK_ACCESS_FS_MAKE_REG,
> +		},
> +		{
> +			/*
> +			 * Removing the source file is allowed because its
> +			 * access rights are already a superset of the
> +			 * destination.
> +			 */
> +			.path = dir_s3d4,
> +			.access = LANDLOCK_ACCESS_FS_REFER |
> +				  LANDLOCK_ACCESS_FS_MAKE_REG |
> +				  LANDLOCK_ACCESS_FS_REMOVE_FILE,
> +		},
> +		{},
> +	};
> +	int ruleset_fd;
> +
> +	ASSERT_EQ(0, unlink(file1_s3d3));
> +	ruleset_fd = create_ruleset(_metadata,
> +				    LANDLOCK_ACCESS_FS_REFER |
> +					    LANDLOCK_ACCESS_FS_MAKE_REG |
> +					    LANDLOCK_ACCESS_FS_REMOVE_FILE,
> +				    layer1);
> +
> +	ASSERT_LE(0, ruleset_fd);
> +	enforce_ruleset(_metadata, ruleset_fd);
> +	ASSERT_EQ(0, close(ruleset_fd));
> +
> +	ASSERT_EQ(0, rename(file1_s3d4, file1_s3d3));
> +}
> +
>  TEST_F_FORK(layout1, reparent_link)
>  {
>  	const struct rule layer1[] = {
> -- 
> 2.47.1
> 
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 13/30] landlock: Optimize file path walks and prepare for audit support
  2025-01-08 15:43 ` [PATCH v4 13/30] landlock: Optimize file path walks and prepare for audit support Mickaël Salaün
@ 2025-01-10 11:24   ` Mickaël Salaün
  0 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-10 11:24 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module

On Wed, Jan 08, 2025 at 04:43:21PM +0100, Mickaël Salaün wrote:
> Always synchronize access_masked_parent* with access_request_parent*
> according to allowed_parent*.  This is required for audit support to be
> able to get back to the reason of denial.
> 
> In a rename/link action, instead of always checking a rule two times for
> the same parent directory of the source and the destination files, only
> check it when an action on a child was not already allowed.  This also
> enables us to keep consistent allowed_parent* status, which is required
> to get back to the reason of denial.
> 
> For internal mount points, only upgrade allowed_parent* to true but do
> not wrongfully set both of them to false otherwise.  This is also
> required to get back to the reason of denial.
> 
> This does not impact the current behavior but slightly optimize code and
> prepare for audit support that needs to know the exact reason why an
> access was denied.
> 
> Cc: Günther Noack <gnoack@google.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-14-mic@digikod.net

Pushed in my next tree to simplify next patch series.

> ---
> 
> Changes since v2:
> - New patch.
> ---
>  security/landlock/fs.c | 44 ++++++++++++++++++++++++++----------------
>  1 file changed, 27 insertions(+), 17 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index ddadc465581e..01f9d5e78218 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -854,15 +854,6 @@ static bool is_access_to_paths_allowed(
>  				     child1_is_directory, layer_masks_parent2,
>  				     layer_masks_child2,
>  				     child2_is_directory))) {
> -			allowed_parent1 = scope_to_request(
> -				access_request_parent1, layer_masks_parent1);
> -			allowed_parent2 = scope_to_request(
> -				access_request_parent2, layer_masks_parent2);
> -
> -			/* Stops when all accesses are granted. */
> -			if (allowed_parent1 && allowed_parent2)
> -				break;
> -
>  			/*
>  			 * Now, downgrades the remaining checks from domain
>  			 * handled accesses to requested accesses.
> @@ -870,15 +861,32 @@ static bool is_access_to_paths_allowed(
>  			is_dom_check = false;
>  			access_masked_parent1 = access_request_parent1;
>  			access_masked_parent2 = access_request_parent2;
> +
> +			allowed_parent1 =
> +				allowed_parent1 ||
> +				scope_to_request(access_masked_parent1,
> +						 layer_masks_parent1);
> +			allowed_parent2 =
> +				allowed_parent2 ||
> +				scope_to_request(access_masked_parent2,
> +						 layer_masks_parent2);
> +
> +			/* Stops when all accesses are granted. */
> +			if (allowed_parent1 && allowed_parent2)
> +				break;
>  		}
>  
>  		rule = find_rule(domain, walker_path.dentry);
> -		allowed_parent1 = landlock_unmask_layers(
> -			rule, access_masked_parent1, layer_masks_parent1,
> -			ARRAY_SIZE(*layer_masks_parent1));
> -		allowed_parent2 = landlock_unmask_layers(
> -			rule, access_masked_parent2, layer_masks_parent2,
> -			ARRAY_SIZE(*layer_masks_parent2));
> +		allowed_parent1 = allowed_parent1 ||
> +				  landlock_unmask_layers(
> +					  rule, access_masked_parent1,
> +					  layer_masks_parent1,
> +					  ARRAY_SIZE(*layer_masks_parent1));
> +		allowed_parent2 = allowed_parent2 ||
> +				  landlock_unmask_layers(
> +					  rule, access_masked_parent2,
> +					  layer_masks_parent2,
> +					  ARRAY_SIZE(*layer_masks_parent2));
>  
>  		/* Stops when a rule from each layer grants access. */
>  		if (allowed_parent1 && allowed_parent2)
> @@ -902,8 +910,10 @@ static bool is_access_to_paths_allowed(
>  			 * access to internal filesystems (e.g. nsfs, which is
>  			 * reachable through /proc/<pid>/ns/<namespace>).
>  			 */
> -			allowed_parent1 = allowed_parent2 =
> -				!!(walker_path.mnt->mnt_flags & MNT_INTERNAL);
> +			if (walker_path.mnt->mnt_flags & MNT_INTERNAL) {
> +				allowed_parent1 = true;
> +				allowed_parent2 = true;
> +			}
>  			break;
>  		}
>  		parent_dentry = dget_parent(walker_path.dentry);
> -- 
> 2.47.1
> 
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 20/30] selftests/landlock: Fix error message
  2025-01-08 15:43 ` [PATCH v4 20/30] selftests/landlock: Fix error message Mickaël Salaün
@ 2025-01-10 11:24   ` Mickaël Salaün
  0 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-10 11:24 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module

On Wed, Jan 08, 2025 at 04:43:28PM +0100, Mickaël Salaün wrote:
> The global variable errno may not be set in test_execute().  Do not use
> it in related error message.
> 
> Cc: Günther Noack <gnoack@google.com>
> Fixes: e1199815b47b ("selftests/landlock: Add user space tests")
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-21-mic@digikod.net

Pushed in my next tree to simplify next patch series.

> ---
> 
> Changes since v3:
> - New patch.
> ---
>  tools/testing/selftests/landlock/fs_test.c | 3 +--
>  1 file changed, 1 insertion(+), 2 deletions(-)
> 
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index 42ce1e79ba82..a359c0d3107f 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -2011,8 +2011,7 @@ static void test_execute(struct __test_metadata *const _metadata, const int err,
>  	ASSERT_EQ(1, WIFEXITED(status));
>  	ASSERT_EQ(err ? 2 : 0, WEXITSTATUS(status))
>  	{
> -		TH_LOG("Unexpected return code for \"%s\": %s", path,
> -		       strerror(errno));
> +		TH_LOG("Unexpected return code for \"%s\"", path);
>  	};
>  }
>  
> -- 
> 2.47.1
> 
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 21/30] selftests/landlock: Add wrappers.h
  2025-01-08 15:43 ` [PATCH v4 21/30] selftests/landlock: Add wrappers.h Mickaël Salaün
@ 2025-01-10 11:24   ` Mickaël Salaün
  0 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-10 11:24 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module

On Wed, Jan 08, 2025 at 04:43:29PM +0100, Mickaël Salaün wrote:
> Extract syscall wrappers to make them usable by standalone binaries (see
> next commit).
> 
> Cc: Günther Noack <gnoack@google.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-22-mic@digikod.net

Pushed in my next tree to simplify next patch series.

> ---
> 
> Changes since v3:
> - New patch.
> ---
>  tools/testing/selftests/landlock/common.h   | 37 +---------------
>  tools/testing/selftests/landlock/wrappers.h | 47 +++++++++++++++++++++
>  2 files changed, 48 insertions(+), 36 deletions(-)
>  create mode 100644 tools/testing/selftests/landlock/wrappers.h
> 
> diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
> index 61056fa074bb..8391ab574f64 100644
> --- a/tools/testing/selftests/landlock/common.h
> +++ b/tools/testing/selftests/landlock/common.h
> @@ -9,17 +9,15 @@
>  
>  #include <arpa/inet.h>
>  #include <errno.h>
> -#include <linux/landlock.h>
>  #include <linux/securebits.h>
>  #include <sys/capability.h>
>  #include <sys/socket.h>
> -#include <sys/syscall.h>
> -#include <sys/types.h>
>  #include <sys/un.h>
>  #include <sys/wait.h>
>  #include <unistd.h>
>  
>  #include "../kselftest_harness.h"
> +#include "wrappers.h"
>  
>  #define TMP_DIR "tmp"
>  
> @@ -30,34 +28,6 @@
>  /* TEST_F_FORK() should not be used for new tests. */
>  #define TEST_F_FORK(fixture_name, test_name) TEST_F(fixture_name, test_name)
>  
> -#ifndef landlock_create_ruleset
> -static inline int
> -landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
> -			const size_t size, const __u32 flags)
> -{
> -	return syscall(__NR_landlock_create_ruleset, attr, size, flags);
> -}
> -#endif
> -
> -#ifndef landlock_add_rule
> -static inline int landlock_add_rule(const int ruleset_fd,
> -				    const enum landlock_rule_type rule_type,
> -				    const void *const rule_attr,
> -				    const __u32 flags)
> -{
> -	return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
> -		       flags);
> -}
> -#endif
> -
> -#ifndef landlock_restrict_self
> -static inline int landlock_restrict_self(const int ruleset_fd,
> -					 const __u32 flags)
> -{
> -	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
> -}
> -#endif
> -
>  static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
>  {
>  	cap_t cap_p;
> @@ -250,11 +220,6 @@ struct service_fixture {
>  	};
>  };
>  
> -static pid_t __maybe_unused sys_gettid(void)
> -{
> -	return syscall(__NR_gettid);
> -}
> -
>  static void __maybe_unused set_unix_address(struct service_fixture *const srv,
>  					    const unsigned short index)
>  {
> diff --git a/tools/testing/selftests/landlock/wrappers.h b/tools/testing/selftests/landlock/wrappers.h
> new file mode 100644
> index 000000000000..32963a44876b
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/wrappers.h
> @@ -0,0 +1,47 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Landlock helpers
> + *
> + * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
> + * Copyright © 2019-2020 ANSSI
> + * Copyright © 2021-2024 Microsoft Corporation
> + */
> +
> +#define _GNU_SOURCE
> +#include <linux/landlock.h>
> +#include <sys/syscall.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +
> +#ifndef landlock_create_ruleset
> +static inline int
> +landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
> +			const size_t size, const __u32 flags)
> +{
> +	return syscall(__NR_landlock_create_ruleset, attr, size, flags);
> +}
> +#endif
> +
> +#ifndef landlock_add_rule
> +static inline int landlock_add_rule(const int ruleset_fd,
> +				    const enum landlock_rule_type rule_type,
> +				    const void *const rule_attr,
> +				    const __u32 flags)
> +{
> +	return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
> +		       flags);
> +}
> +#endif
> +
> +#ifndef landlock_restrict_self
> +static inline int landlock_restrict_self(const int ruleset_fd,
> +					 const __u32 flags)
> +{
> +	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
> +}
> +#endif
> +
> +static inline pid_t sys_gettid(void)
> +{
> +	return syscall(__NR_gettid);
> +}
> -- 
> 2.47.1
> 
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 22/30] selftests/landlock: Add layout1.umount_sandboxer tests
  2025-01-08 15:43 ` [PATCH v4 22/30] selftests/landlock: Add layout1.umount_sandboxer tests Mickaël Salaün
@ 2025-01-10 11:25   ` Mickaël Salaün
  0 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-10 11:25 UTC (permalink / raw)
  To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
  Cc: Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module

On Wed, Jan 08, 2025 at 04:43:30PM +0100, Mickaël Salaün wrote:
> Check that a domain is not tied to the executable file that created it.
> For instance, that could happen if a Landlock domain took a reference to
> a struct path.
> 
> Move global path names to common.h and replace copy_binary() with a more
> generic copy_file() helper.
> 
> Cc: Günther Noack <gnoack@google.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-23-mic@digikod.net

Pushed in my next tree to simplify next patch series.

> ---
> 
> Changes since v3:
> - New patch to check issue from v2.
> ---
>  tools/testing/selftests/landlock/Makefile     |  2 +-
>  tools/testing/selftests/landlock/common.h     |  3 +
>  tools/testing/selftests/landlock/fs_test.c    | 94 +++++++++++++++++--
>  .../selftests/landlock/sandbox-and-launch.c   | 82 ++++++++++++++++
>  tools/testing/selftests/landlock/wait-pipe.c  | 42 +++++++++
>  5 files changed, 213 insertions(+), 10 deletions(-)
>  create mode 100644 tools/testing/selftests/landlock/sandbox-and-launch.c
>  create mode 100644 tools/testing/selftests/landlock/wait-pipe.c
> 
> diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile
> index 348e2dbdb4e0..b1445c8bee50 100644
> --- a/tools/testing/selftests/landlock/Makefile
> +++ b/tools/testing/selftests/landlock/Makefile
> @@ -10,7 +10,7 @@ src_test := $(wildcard *_test.c)
>  
>  TEST_GEN_PROGS := $(src_test:.c=)
>  
> -TEST_GEN_PROGS_EXTENDED := true
> +TEST_GEN_PROGS_EXTENDED := true sandbox-and-launch wait-pipe
>  
>  # Short targets:
>  $(TEST_GEN_PROGS): LDLIBS += -lcap
> diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
> index 8391ab574f64..a604ea5d8297 100644
> --- a/tools/testing/selftests/landlock/common.h
> +++ b/tools/testing/selftests/landlock/common.h
> @@ -28,6 +28,9 @@
>  /* TEST_F_FORK() should not be used for new tests. */
>  #define TEST_F_FORK(fixture_name, test_name) TEST_F(fixture_name, test_name)
>  
> +static const char bin_sandbox_and_launch[] = "./sandbox-and-launch";
> +static const char bin_wait_pipe[] = "./wait-pipe";
> +
>  static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
>  {
>  	cap_t cap_p;
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index a359c0d3107f..8ac9aaf38eaa 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -59,7 +59,7 @@ int open_tree(int dfd, const char *filename, unsigned int flags)
>  #define RENAME_EXCHANGE (1 << 1)
>  #endif
>  
> -#define BINARY_PATH "./true"
> +static const char bin_true[] = "./true";
>  
>  /* Paths (sibling number and depth) */
>  static const char dir_s1d1[] = TMP_DIR "/s1d1";
> @@ -1965,8 +1965,8 @@ TEST_F_FORK(layout1, relative_chroot_chdir)
>  	test_relative_path(_metadata, REL_CHROOT_CHDIR);
>  }
>  
> -static void copy_binary(struct __test_metadata *const _metadata,
> -			const char *const dst_path)
> +static void copy_file(struct __test_metadata *const _metadata,
> +		      const char *const src_path, const char *const dst_path)
>  {
>  	int dst_fd, src_fd;
>  	struct stat statbuf;
> @@ -1976,11 +1976,10 @@ static void copy_binary(struct __test_metadata *const _metadata,
>  	{
>  		TH_LOG("Failed to open \"%s\": %s", dst_path, strerror(errno));
>  	}
> -	src_fd = open(BINARY_PATH, O_RDONLY | O_CLOEXEC);
> +	src_fd = open(src_path, O_RDONLY | O_CLOEXEC);
>  	ASSERT_LE(0, src_fd)
>  	{
> -		TH_LOG("Failed to open \"" BINARY_PATH "\": %s",
> -		       strerror(errno));
> +		TH_LOG("Failed to open \"%s\": %s", src_path, strerror(errno));
>  	}
>  	ASSERT_EQ(0, fstat(src_fd, &statbuf));
>  	ASSERT_EQ(statbuf.st_size,
> @@ -2028,9 +2027,9 @@ TEST_F_FORK(layout1, execute)
>  		create_ruleset(_metadata, rules[0].access, rules);
>  
>  	ASSERT_LE(0, ruleset_fd);
> -	copy_binary(_metadata, file1_s1d1);
> -	copy_binary(_metadata, file1_s1d2);
> -	copy_binary(_metadata, file1_s1d3);
> +	copy_file(_metadata, bin_true, file1_s1d1);
> +	copy_file(_metadata, bin_true, file1_s1d2);
> +	copy_file(_metadata, bin_true, file1_s1d3);
>  
>  	enforce_ruleset(_metadata, ruleset_fd);
>  	ASSERT_EQ(0, close(ruleset_fd));
> @@ -2048,6 +2047,83 @@ TEST_F_FORK(layout1, execute)
>  	test_execute(_metadata, 0, file1_s1d3);
>  }
>  
> +TEST_F_FORK(layout1, umount_sandboxer)
> +{
> +	int pipe_child[2], pipe_parent[2];
> +	char buf_parent;
> +	pid_t child;
> +	int status;
> +
> +	copy_file(_metadata, bin_sandbox_and_launch, file1_s3d3);
> +	ASSERT_EQ(0, pipe2(pipe_child, 0));
> +	ASSERT_EQ(0, pipe2(pipe_parent, 0));
> +
> +	child = fork();
> +	ASSERT_LE(0, child);
> +	if (child == 0) {
> +		char pipe_child_str[12], pipe_parent_str[12];
> +		char *const argv[] = { (char *)file1_s3d3,
> +				       (char *)bin_wait_pipe, pipe_child_str,
> +				       pipe_parent_str, NULL };
> +
> +		/* Passes the pipe FDs to the executed binary and its child. */
> +		EXPECT_EQ(0, close(pipe_child[0]));
> +		EXPECT_EQ(0, close(pipe_parent[1]));
> +		snprintf(pipe_child_str, sizeof(pipe_child_str), "%d",
> +			 pipe_child[1]);
> +		snprintf(pipe_parent_str, sizeof(pipe_parent_str), "%d",
> +			 pipe_parent[0]);
> +
> +		/*
> +		 * We need bin_sandbox_and_launch (copied inside the mount as
> +		 * file1_s3d3) to execute bin_wait_pipe (outside the mount) to
> +		 * make sure the mount point will not be EBUSY because of
> +		 * file1_s3d3 being in use.  This avoids a potential race
> +		 * condition between the following read() and umount() calls.
> +		 */
> +		ASSERT_EQ(0, execve(argv[0], argv, NULL))
> +		{
> +			TH_LOG("Failed to execute \"%s\": %s", argv[0],
> +			       strerror(errno));
> +		};
> +		_exit(1);
> +		return;
> +	}
> +
> +	EXPECT_EQ(0, close(pipe_child[1]));
> +	EXPECT_EQ(0, close(pipe_parent[0]));
> +
> +	/* Waits for the child to sandbox itself. */
> +	EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1));
> +
> +	/* Tests that the sandboxer is tied to its mount point. */
> +	set_cap(_metadata, CAP_SYS_ADMIN);
> +	EXPECT_EQ(-1, umount(dir_s3d2));
> +	EXPECT_EQ(EBUSY, errno);
> +	clear_cap(_metadata, CAP_SYS_ADMIN);
> +
> +	/* Signals the child to launch a grandchild. */
> +	EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
> +
> +	/* Waits for the grandchild. */
> +	EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1));
> +
> +	/* Tests that the domain's sandboxer is not tied to its mount point. */
> +	set_cap(_metadata, CAP_SYS_ADMIN);
> +	EXPECT_EQ(0, umount(dir_s3d2))
> +	{
> +		TH_LOG("Failed to umount \"%s\": %s", dir_s3d2,
> +		       strerror(errno));
> +	};
> +	clear_cap(_metadata, CAP_SYS_ADMIN);
> +
> +	/* Signals the grandchild to terminate. */
> +	EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
> +	ASSERT_EQ(child, waitpid(child, &status, 0));
> +	ASSERT_EQ(1, WIFEXITED(status));
> +	ASSERT_EQ(0, WEXITSTATUS(status));
> +}
> +
>  TEST_F_FORK(layout1, link)
>  {
>  	const struct rule layer1[] = {
> diff --git a/tools/testing/selftests/landlock/sandbox-and-launch.c b/tools/testing/selftests/landlock/sandbox-and-launch.c
> new file mode 100644
> index 000000000000..1ef49f349429
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/sandbox-and-launch.c
> @@ -0,0 +1,82 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Sandbox itself and execute another program (in a different mount point).
> + *
> + * Used by layout1.umount_sandboxer from fs_test.c
> + *
> + * Copyright © 2024 Microsoft Corporation
> + */
> +
> +#define _GNU_SOURCE
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <sys/prctl.h>
> +#include <unistd.h>
> +
> +#include "wrappers.h"
> +
> +int main(int argc, char *argv[])
> +{
> +	struct landlock_ruleset_attr ruleset_attr = {
> +		.scoped = LANDLOCK_SCOPE_SIGNAL,
> +	};
> +	int pipe_child, pipe_parent, ruleset_fd;
> +	char buf;
> +
> +	/*
> +	 * The first argument must be the file descriptor number of a pipe.
> +	 * The second argument must be the program to execute.
> +	 */
> +	if (argc != 4) {
> +		fprintf(stderr, "Wrong number of arguments (not three)\n");
> +		return 1;
> +	}
> +
> +	pipe_child = atoi(argv[2]);
> +	pipe_parent = atoi(argv[3]);
> +
> +	ruleset_fd =
> +		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> +	if (ruleset_fd < 0) {
> +		perror("Failed to create ruleset");
> +		return 1;
> +	}
> +
> +	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
> +		perror("Failed to call prctl()");
> +		return 1;
> +	}
> +
> +	if (landlock_restrict_self(ruleset_fd, 0)) {
> +		perror("Failed to restrict self");
> +		return 1;
> +	}
> +
> +	if (close(ruleset_fd)) {
> +		perror("Failed to close ruleset");
> +		return 1;
> +	}
> +
> +	/* Signals that we are sandboxed. */
> +	errno = 0;
> +	if (write(pipe_child, ".", 1) != 1) {
> +		perror("Failed to write to the second argument");
> +		return 1;
> +	}
> +
> +	/* Waits for the parent to try to umount. */
> +	if (read(pipe_parent, &buf, 1) != 1) {
> +		perror("Failed to write to the third argument");
> +		return 1;
> +	}
> +
> +	/* Shifts arguments. */
> +	argv[0] = argv[1];
> +	argv[1] = argv[2];
> +	argv[2] = argv[3];
> +	argv[3] = NULL;
> +	execve(argv[0], argv, NULL);
> +	perror("Failed to execute the provided binary");
> +	return 1;
> +}
> diff --git a/tools/testing/selftests/landlock/wait-pipe.c b/tools/testing/selftests/landlock/wait-pipe.c
> new file mode 100644
> index 000000000000..0dbcd260a0fa
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/wait-pipe.c
> @@ -0,0 +1,42 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Write in a pipe and wait.
> + *
> + * Used by layout1.umount_sandboxer from fs_test.c
> + *
> + * Copyright © 2024-2025 Microsoft Corporation
> + */
> +
> +#define _GNU_SOURCE
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +
> +int main(int argc, char *argv[])
> +{
> +	int pipe_child, pipe_parent;
> +	char buf;
> +
> +	/* The first argument must be the file descriptor number of a pipe. */
> +	if (argc != 3) {
> +		fprintf(stderr, "Wrong number of arguments (not two)\n");
> +		return 1;
> +	}
> +
> +	pipe_child = atoi(argv[1]);
> +	pipe_parent = atoi(argv[2]);
> +
> +	/* Signals that we are waiting. */
> +	if (write(pipe_child, ".", 1) != 1) {
> +		perror("Failed to write to first argument");
> +		return 1;
> +	}
> +
> +	/* Waits for the parent do its test. */
> +	if (read(pipe_parent, &buf, 1) != 1) {
> +		perror("Failed to write to the second argument");
> +		return 1;
> +	}
> +
> +	return 0;
> +}
> -- 
> 2.47.1
> 
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 27/30] fs: Add iput() cleanup helper
  2025-01-08 15:43 ` [PATCH v4 27/30] fs: Add iput() cleanup helper Mickaël Salaün
@ 2025-01-13 11:15   ` Mickaël Salaün
  2025-01-13 16:45     ` Al Viro
  2025-01-13 14:00   ` Jann Horn
  2025-01-13 14:36   ` (subset) " Christian Brauner
  2 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-13 11:15 UTC (permalink / raw)
  To: Al Viro, Christian Brauner, Jeff Layton, Josef Bacik
  Cc: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn,
	Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module, Al Viro,
	Christian Brauner, Jeff Layton, Josef Bacik, linux-fsdevel

Al, Christian, this standalone patch could be useful to others.  Feel
free to pick it in your tree.

On Wed, Jan 08, 2025 at 04:43:35PM +0100, Mickaël Salaün wrote:
> Add a simple scope-based helper to put an inode reference, similar to
> the fput() helper.
> 
> This is used in a following commit.
> 
> Cc: Al Viro <viro@zeniv.linux.org.uk>
> Cc: Christian Brauner <brauner@kernel.org>
> Cc: Jeff Layton <jlayton@kernel.org>
> Cc: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-28-mic@digikod.net
> ---
> 
> Changes since v3:
> - New patch.
> ---
>  include/linux/fs.h | 6 ++++--
>  1 file changed, 4 insertions(+), 2 deletions(-)
> 
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 7e29433c5ecc..bd5a28b0871f 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -47,6 +47,8 @@
>  #include <linux/rw_hint.h>
>  #include <linux/file_ref.h>
>  #include <linux/unicode.h>
> +#include <linux/cleanup.h>
> +#include <linux/err.h>
>  
>  #include <asm/byteorder.h>
>  #include <uapi/linux/fs.h>
> @@ -2698,6 +2700,8 @@ extern void iput(struct inode *);
>  int inode_update_timestamps(struct inode *inode, int flags);
>  int generic_update_time(struct inode *, int);
>  
> +DEFINE_FREE(iput, struct inode *, if (!IS_ERR_OR_NULL(_T)) iput(_T))
> +
>  /* /sys/fs */
>  extern struct kobject *fs_kobj;
>  
> @@ -3108,8 +3112,6 @@ static inline bool is_dot_dotdot(const char *name, size_t len)
>  		(len == 1 || (len == 2 && name[1] == '.'));
>  }
>  
> -#include <linux/err.h>
> -
>  /* needed for stackable file system support */
>  extern loff_t default_llseek(struct file *file, loff_t offset, int whence);
>  
> -- 
> 2.47.1
> 
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 27/30] fs: Add iput() cleanup helper
  2025-01-08 15:43 ` [PATCH v4 27/30] fs: Add iput() cleanup helper Mickaël Salaün
  2025-01-13 11:15   ` Mickaël Salaün
@ 2025-01-13 14:00   ` Jann Horn
  2025-01-13 15:00     ` Christian Brauner
  2025-01-13 14:36   ` (subset) " Christian Brauner
  2 siblings, 1 reply; 58+ messages in thread
From: Jann Horn @ 2025-01-13 14:00 UTC (permalink / raw)
  To: Mickaël Salaün, Christian Brauner, Al Viro
  Cc: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn,
	Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jeff Xu, Jorge Lucangeli Obes,
	Kees Cook, Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov,
	Phil Sutter, Praveen K Paladugu, Robert Salvet, Shervin Oloumi,
	Song Liu, Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module, Jeff Layton, Josef Bacik

On Wed, Jan 8, 2025 at 4:44 PM Mickaël Salaün <mic@digikod.net> wrote:
> Add a simple scope-based helper to put an inode reference, similar to
> the fput() helper.

Cleaning up inode references with scope-based cleanup seems dangerous
to me because, unlike most resources, holding a reference to an inode
beyond the lifetime of the associated superblock can actually cause
memory corruption; and scope-based cleanup is designed based on the
idea that the order and precise location of dropping a reference don't
matter so much.

So I would prefer to either not do this, or at least have some big
warning comment about usage requirements of scope-based inode
references.

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: (subset) [PATCH v4 27/30] fs: Add iput() cleanup helper
  2025-01-08 15:43 ` [PATCH v4 27/30] fs: Add iput() cleanup helper Mickaël Salaün
  2025-01-13 11:15   ` Mickaël Salaün
  2025-01-13 14:00   ` Jann Horn
@ 2025-01-13 14:36   ` Christian Brauner
  2 siblings, 0 replies; 58+ messages in thread
From: Christian Brauner @ 2025-01-13 14:36 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Christian Brauner, Ben Scarlato, Casey Schaufler, Charles Zaffery,
	Daniel Burgener, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module, Al Viro,
	Jeff Layton, Josef Bacik, Eric Paris, Paul Moore,
	Günther Noack, Serge E . Hallyn

On Wed, 08 Jan 2025 16:43:35 +0100, Mickaël Salaün wrote:
> Add a simple scope-based helper to put an inode reference, similar to
> the fput() helper.
> 
> This is used in a following commit.
> 
> 

Applied to the vfs-6.14.misc branch of the vfs/vfs.git tree.
Patches in the vfs-6.14.misc branch should appear in linux-next soon.

Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.

It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.

Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.

tree:   https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs-6.14.misc

[27/30] fs: Add iput() cleanup helper
        https://git.kernel.org/vfs/vfs/c/38b1ff0bcff1

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type
  2025-01-08 15:43 ` [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type Mickaël Salaün
@ 2025-01-13 14:55   ` Jann Horn
  2025-01-13 15:02     ` Christian Brauner
  2025-01-13 16:55     ` Mickaël Salaün
  2025-01-15 23:53   ` Paul Moore
  1 sibling, 2 replies; 58+ messages in thread
From: Jann Horn @ 2025-01-13 14:55 UTC (permalink / raw)
  To: Mickaël Salaün, Christian Brauner, Al Viro
  Cc: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn,
	Ben Scarlato, Casey Schaufler, Charles Zaffery, Daniel Burgener,
	Francis Laniel, James Morris, Jeff Xu, Jorge Lucangeli Obes,
	Kees Cook, Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov,
	Phil Sutter, Praveen K Paladugu, Robert Salvet, Shervin Oloumi,
	Song Liu, Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

+Christian and Al Viro to double-check what I'm saying

On Wed, Jan 8, 2025 at 4:44 PM Mickaël Salaün <mic@digikod.net> wrote:
> -static const void *get_current_exe(const char **path_str, size_t *path_size)
> +static const void *get_current_exe(const char **path_str, size_t *path_size,
> +                                  struct inode **inode)
>  {
>         struct mm_struct *mm = current->mm;
>         struct file *file __free(fput) = NULL;
> @@ -93,6 +96,8 @@ static const void *get_current_exe(const char **path_str, size_t *path_size)
>
>         *path_size = size;
>         *path_str = path;
> +       ihold(file_inode(file));
> +       *inode = file_inode(file);
>         return no_free_ptr(buffer);
>  }

This looks unsafe: Once the reference to the file has been dropped
(which happens implicitly on return from get_current_exe()), nothing
holds a reference on the mount point or superblock anymore (the file
was previously holding a reference to the mount point through
->f_path.mnt), and so the superblock can be torn down and freed. But
the reference to the inode lives longer and is only cleaned up on
return from the caller get_current_details().

So I think this code can hit the error check for "Busy inodes after
unmount" in generic_shutdown_super(), which indicates that in theory,
use-after-free can occur.

For context, here are two older kernel security issues that also
involved superblock UAF due to assuming that it's possible to just
hold refcounted references to inodes:

https://project-zero.issues.chromium.org/42451116
https://project-zero.issues.chromium.org/379667898

For fixing this, one option would be to copy the entire "struct path"
(which holds references on both the mount point and the inode) instead
of just copying the inode pointer.

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 27/30] fs: Add iput() cleanup helper
  2025-01-13 14:00   ` Jann Horn
@ 2025-01-13 15:00     ` Christian Brauner
  2025-01-13 16:55       ` Mickaël Salaün
  0 siblings, 1 reply; 58+ messages in thread
From: Christian Brauner @ 2025-01-13 15:00 UTC (permalink / raw)
  To: Jann Horn
  Cc: Mickaël Salaün, Al Viro, Eric Paris, Paul Moore,
	Günther Noack, Serge E . Hallyn, Ben Scarlato,
	Casey Schaufler, Charles Zaffery, Daniel Burgener, Francis Laniel,
	James Morris, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module, Jeff Layton, Josef Bacik

On Mon, Jan 13, 2025 at 03:00:20PM +0100, Jann Horn wrote:
> On Wed, Jan 8, 2025 at 4:44 PM Mickaël Salaün <mic@digikod.net> wrote:
> > Add a simple scope-based helper to put an inode reference, similar to
> > the fput() helper.
> 
> Cleaning up inode references with scope-based cleanup seems dangerous
> to me because, unlike most resources, holding a reference to an inode
> beyond the lifetime of the associated superblock can actually cause
> memory corruption; and scope-based cleanup is designed based on the
> idea that the order and precise location of dropping a reference don't
> matter so much.

That's in general a good point and I know there's been opposition to
this in the past when we discussed this. So fine by me.

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type
  2025-01-13 14:55   ` Jann Horn
@ 2025-01-13 15:02     ` Christian Brauner
  2025-01-13 16:55     ` Mickaël Salaün
  1 sibling, 0 replies; 58+ messages in thread
From: Christian Brauner @ 2025-01-13 15:02 UTC (permalink / raw)
  To: Jann Horn
  Cc: Mickaël Salaün, Al Viro, Eric Paris, Paul Moore,
	Günther Noack, Serge E . Hallyn, Ben Scarlato,
	Casey Schaufler, Charles Zaffery, Daniel Burgener, Francis Laniel,
	James Morris, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Mon, Jan 13, 2025 at 03:55:42PM +0100, Jann Horn wrote:
> +Christian and Al Viro to double-check what I'm saying
> 
> On Wed, Jan 8, 2025 at 4:44 PM Mickaël Salaün <mic@digikod.net> wrote:
> > -static const void *get_current_exe(const char **path_str, size_t *path_size)
> > +static const void *get_current_exe(const char **path_str, size_t *path_size,
> > +                                  struct inode **inode)
> >  {
> >         struct mm_struct *mm = current->mm;
> >         struct file *file __free(fput) = NULL;
> > @@ -93,6 +96,8 @@ static const void *get_current_exe(const char **path_str, size_t *path_size)
> >
> >         *path_size = size;
> >         *path_str = path;
> > +       ihold(file_inode(file));
> > +       *inode = file_inode(file);
> >         return no_free_ptr(buffer);
> >  }
> 
> This looks unsafe: Once the reference to the file has been dropped

s/looks/is/g

> (which happens implicitly on return from get_current_exe()), nothing
> holds a reference on the mount point or superblock anymore (the file
> was previously holding a reference to the mount point through
> ->f_path.mnt), and so the superblock can be torn down and freed. But
> the reference to the inode lives longer and is only cleaned up on
> return from the caller get_current_details().
> 
> So I think this code can hit the error check for "Busy inodes after
> unmount" in generic_shutdown_super(), which indicates that in theory,
> use-after-free can occur.

Yep, it sure would.

> 
> For context, here are two older kernel security issues that also
> involved superblock UAF due to assuming that it's possible to just
> hold refcounted references to inodes:
> 
> https://project-zero.issues.chromium.org/42451116
> https://project-zero.issues.chromium.org/379667898
> 
> For fixing this, one option would be to copy the entire "struct path"
> (which holds references on both the mount point and the inode) instead
> of just copying the inode pointer.

path_get() indeed

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 27/30] fs: Add iput() cleanup helper
  2025-01-13 11:15   ` Mickaël Salaün
@ 2025-01-13 16:45     ` Al Viro
  0 siblings, 0 replies; 58+ messages in thread
From: Al Viro @ 2025-01-13 16:45 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Christian Brauner, Jeff Layton, Josef Bacik, Eric Paris,
	Paul Moore, Günther Noack, Serge E . Hallyn, Ben Scarlato,
	Casey Schaufler, Charles Zaffery, Daniel Burgener, Francis Laniel,
	James Morris, Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module, linux-fsdevel

On Mon, Jan 13, 2025 at 12:15:05PM +0100, Mickaël Salaün wrote:
> Al, Christian, this standalone patch could be useful to others.  Feel
> free to pick it in your tree.

Bad idea - we'll end up having to treat uses of that as a serious red flag;
it's too easy to end up with lifetime extended too far.

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type
  2025-01-13 14:55   ` Jann Horn
  2025-01-13 15:02     ` Christian Brauner
@ 2025-01-13 16:55     ` Mickaël Salaün
  1 sibling, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-13 16:55 UTC (permalink / raw)
  To: Jann Horn
  Cc: Christian Brauner, Al Viro, Eric Paris, Paul Moore,
	Günther Noack, Serge E . Hallyn, Ben Scarlato,
	Casey Schaufler, Charles Zaffery, Daniel Burgener, Francis Laniel,
	James Morris, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Mon, Jan 13, 2025 at 03:55:42PM +0100, Jann Horn wrote:
> +Christian and Al Viro to double-check what I'm saying
> 
> On Wed, Jan 8, 2025 at 4:44 PM Mickaël Salaün <mic@digikod.net> wrote:
> > -static const void *get_current_exe(const char **path_str, size_t *path_size)
> > +static const void *get_current_exe(const char **path_str, size_t *path_size,
> > +                                  struct inode **inode)
> >  {
> >         struct mm_struct *mm = current->mm;
> >         struct file *file __free(fput) = NULL;
> > @@ -93,6 +96,8 @@ static const void *get_current_exe(const char **path_str, size_t *path_size)
> >
> >         *path_size = size;
> >         *path_str = path;
> > +       ihold(file_inode(file));
> > +       *inode = file_inode(file);
> >         return no_free_ptr(buffer);
> >  }
> 
> This looks unsafe: Once the reference to the file has been dropped
> (which happens implicitly on return from get_current_exe()), nothing
> holds a reference on the mount point or superblock anymore (the file
> was previously holding a reference to the mount point through
> ->f_path.mnt), and so the superblock can be torn down and freed. But
> the reference to the inode lives longer and is only cleaned up on
> return from the caller get_current_details().
> 
> So I think this code can hit the error check for "Busy inodes after
> unmount" in generic_shutdown_super(), which indicates that in theory,
> use-after-free can occur.
> 
> For context, here are two older kernel security issues that also
> involved superblock UAF due to assuming that it's possible to just
> hold refcounted references to inodes:
> 
> https://project-zero.issues.chromium.org/42451116
> https://project-zero.issues.chromium.org/379667898

Thanks for the detailed explanation!

> 
> For fixing this, one option would be to copy the entire "struct path"
> (which holds references on both the mount point and the inode) instead
> of just copying the inode pointer.

Yes, I'll do that.

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 27/30] fs: Add iput() cleanup helper
  2025-01-13 15:00     ` Christian Brauner
@ 2025-01-13 16:55       ` Mickaël Salaün
  0 siblings, 0 replies; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-13 16:55 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Jann Horn, Al Viro, Eric Paris, Paul Moore, Günther Noack,
	Serge E . Hallyn, Ben Scarlato, Casey Schaufler, Charles Zaffery,
	Daniel Burgener, Francis Laniel, James Morris, Jeff Xu,
	Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
	Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
	Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi,
	Tyler Hicks, audit, linux-kernel, linux-security-module,
	Jeff Layton, Josef Bacik

On Mon, Jan 13, 2025 at 04:00:31PM +0100, Christian Brauner wrote:
> On Mon, Jan 13, 2025 at 03:00:20PM +0100, Jann Horn wrote:
> > On Wed, Jan 8, 2025 at 4:44 PM Mickaël Salaün <mic@digikod.net> wrote:
> > > Add a simple scope-based helper to put an inode reference, similar to
> > > the fput() helper.
> > 
> > Cleaning up inode references with scope-based cleanup seems dangerous
> > to me because, unlike most resources, holding a reference to an inode
> > beyond the lifetime of the associated superblock can actually cause
> > memory corruption; and scope-based cleanup is designed based on the
> > idea that the order and precise location of dropping a reference don't
> > matter so much.
> 
> That's in general a good point and I know there's been opposition to
> this in the past when we discussed this. So fine by me.

Right, I'll use __free(path_put) instead.

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 8/30] landlock: Add AUDIT_LANDLOCK_DENY and log ptrace  denials
  2025-01-08 15:43 ` [PATCH v4 08/30] landlock: Add AUDIT_LANDLOCK_DENY and log ptrace denials Mickaël Salaün
@ 2025-01-15 23:53   ` Paul Moore
  2025-01-16 10:49     ` Mickaël Salaün
  0 siblings, 1 reply; 58+ messages in thread
From: Paul Moore @ 2025-01-15 23:53 UTC (permalink / raw)
  To: Mickaël Salaün, Eric Paris, Günther Noack,
	Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Jan  8, 2025 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> 
> Add a new AUDIT_LANDLOCK_DENY record type dedicated to any Landlock
> denials.

...

> diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> index 75e21a135483..60c909c396c0 100644
> --- a/include/uapi/linux/audit.h
> +++ b/include/uapi/linux/audit.h
> @@ -33,7 +33,7 @@
>   * 1100 - 1199 user space trusted application messages
>   * 1200 - 1299 messages internal to the audit daemon
>   * 1300 - 1399 audit event messages
> - * 1400 - 1499 SE Linux use
> + * 1400 - 1499 access control messages
>   * 1500 - 1599 kernel LSPP events
>   * 1600 - 1699 kernel crypto events
>   * 1700 - 1799 kernel anomaly records
> @@ -146,6 +146,7 @@
>  #define AUDIT_IPE_ACCESS	1420	/* IPE denial or grant */
>  #define AUDIT_IPE_CONFIG_CHANGE	1421	/* IPE config change */
>  #define AUDIT_IPE_POLICY_LOAD	1422	/* IPE policy load */
> +#define AUDIT_LANDLOCK_DENY	1423	/* Landlock denial */

I didn't have an opportunity to respond to your reply to my v3 comments
before you posted v4, but I see you've decided to stick with _DENY as
opposed to _ACCESS (or something similar).  Let me copy your reply
below so I can respond appropriately ...

> A stronger type with the "denied" semantic makes more sense to me,
> especially for Landlock which is unprivileged, and it makes it clear
> that it should only impact performance and log size (i.e. audit log
> creation) for denied actions.

This is not consistent with how audit is typically used.  Please
convert to AUDIT_LANDLOCK_ACCESS, or something similar.

> The next patch
> series will also contain a new kind of audit rule to specifically
> identify the origin of the policy that created this denied event, which
> should make more sense.

Generally speaking audit only wants to support a small number of message
types dedicated to a specific LSM.  If you're aware of additional message
types that you plan to propose in a future patchset, it's probably a
time to discuss those now.

> Because of its unprivileged nature, Landlock will never log granted
> accesses by default.  In the future, we might want a permissive-like
> mode for Landlock, but this will be optional, and I would also strongly
> prefer to add new audit record types for new semantics.

Once again, this isn't consistent with how audit is typically used and
I'm not seeing a compelling reason to rework how things are done.  Please
stick with encoding the success/failure, accept/reject, etc. states in
audit record fields, not the message types themselves.

--
paul-moore.com

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 9/30] landlock: Add AUDIT_LANDLOCK_DOM_{INFO,DROP} and  log domain properties
  2025-01-08 15:43 ` [PATCH v4 09/30] landlock: Add AUDIT_LANDLOCK_DOM_{INFO,DROP} and log domain properties Mickaël Salaün
@ 2025-01-15 23:53   ` Paul Moore
  2025-01-16 10:51     ` Mickaël Salaün
  0 siblings, 1 reply; 58+ messages in thread
From: Paul Moore @ 2025-01-15 23:53 UTC (permalink / raw)
  To: Mickaël Salaün, Eric Paris, Günther Noack,
	Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Jan  8, 2025 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> 
> Asynchronously log domain information when it first denies an access.
> This minimize the amount of generated logs, which makes it possible to
> always log denials since they should not happen (except with the new
> LANDLOCK_RESTRICT_SELF_QUIET flag).  These records are identified with
> the new AUDIT_LANDLOCK_DOM_INFO type.
> 
> The AUDIT_LANDLOCK_DOM_INFO message contains:
> - the "domain" ID which is described,
> - the "creation" time of this domain,
> - a minimal set of properties to easily identify the task that loaded
>   the domain's policy with landlock_restrict_self(2): "pid", "uid",
>   executable path ("exe"), and command line ("comm").
> 
> This requires each domain to save these task properties at creation
> time in the new struct landlock_details.  A reference to the PID is kept
> for the lifetime of the domain to avoid race conditions when
> investigating the related task.  The executable path is resolved and
> stored to not keep a reference to the filesystem and block related
> actions.  All these metadata are stored for the lifetime of the related
> domain and should then be minimal.  The required memory is not accounted
> to the task calling landlock_restrict_self(2) contrary to most other
> Landlock allocations (see related comment).
> 
> The AUDIT_LANDLOCK_DOM_INFO record follows the first AUDIT_LANDLOCK_DENY
> record for the same domain, which is always followed by AUDIT_SYSCALL
> and AUDIT_PROCTITLE.  This is in line with the audit logic to first
> record the cause of an event, and then add context with other types of
> record.
> 
> Audit event sample for a first denial:
> 
>   type=LANDLOCK_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
>   type=LANDLOCK_DOM_INFO msg=audit(1732186800.349:44): domain=195ba459b creation=1732186800.345 pid=300 uid=0 exe="/root/sandboxer" comm="sandboxer"
>   type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
> 
> Audit event sample for a following denial:
> 
>   type=LANDLOCK_DENY msg=audit(1732186800.372:45): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
>   type=SYSCALL msg=audit(1732186800.372:45): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
>
> Log domain deletion with the new AUDIT_LANDLOCK_DOM_DROP record type
> when a domain was previously logged.  This makes it possible for log
> parsers to free potential resources when a domain ID will never show
> again.
> 
> The AUDIT_LANDLOCK_DOM_DROP message contains:
> - the "domain" ID which is being freed,
> - the number of "denials" accounted to this domain, which is at least 1.
> 
> The number of denied access requests is useful to easily check how many
> access requests a domain blocked and potentially if some of them are
> missing in logs because of audit rate limiting or audit rules.  Rate
> limiting could also drop this record though.

Wait, what rate limiting?  Landlock shouldn't be adding any audit event
rate limiting beyond the queue management knobs built into the audit
subsystem.  If you are comfortable rate limiting the logging of an event
it is a good sign that it probably shouldn't be an audit event.

The audit subsystem is for security releveant events, not diagnostic,
debugging, or other "nice to know" messages.

> Audit event sample for a deletion of a domain that denied something:
> 
>   type=LANDLOCK_DOM_DROP msg=audit(1732186800.393:46): domain=195ba459b denials=2

As mentioned earlier, I don't like the number of different Landlock
specific audit record types that are being created.  I'm going to
suggest combining the LANDLOCK_DOM_INFO and LANDLOCK_DOM_DROP
records into one (LANDLOCK_DOM?) and using an "op=" field to indicate
creation/registration or destruction/unregistration of the domain ID.

> Cc: Günther Noack <gnoack@google.com>
> Cc: Paul Moore <paul@paul-moore.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20250108154338.1129069-10-mic@digikod.net
> ---
> Questions about AUDIT_LANDLOCK_DOM_INFO messages (keeping in mind that
> each logged metadata may need to be stored for the lifetime of each
> domain):
> - Should we also log the initially restricted task's loginuid?
> - Should we also log the initially restricted task's sessionid?

...

> diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> index 60c909c396c0..a72f7b3403be 100644
> --- a/include/uapi/linux/audit.h
> +++ b/include/uapi/linux/audit.h
> @@ -147,6 +147,8 @@
>  #define AUDIT_IPE_CONFIG_CHANGE	1421	/* IPE config change */
>  #define AUDIT_IPE_POLICY_LOAD	1422	/* IPE policy load */
>  #define AUDIT_LANDLOCK_DENY	1423	/* Landlock denial */
> +#define AUDIT_LANDLOCK_DOM_INFO	1424	/* Landlock domain properties */
> +#define AUDIT_LANDLOCK_DOM_DROP	1425	/* Landlock domain release */
>  
>  #define AUDIT_FIRST_KERN_ANOM_MSG   1700
>  #define AUDIT_LAST_KERN_ANOM_MSG    1799
> diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> index d90680a5026a..ccc591146f8a 100644
> --- a/security/landlock/audit.c
> +++ b/security/landlock/audit.c
> @@ -8,6 +8,8 @@
>  #include <kunit/test.h>
>  #include <linux/audit.h>
>  #include <linux/lsm_audit.h>
> +#include <linux/pid.h>
> +#include <linux/uidgid.h>
>  
>  #include "audit.h"
>  #include "domain.h"
> @@ -30,6 +32,43 @@ static void log_blockers(struct audit_buffer *const ab,
>  	audit_log_format(ab, "%s", get_blocker(type));
>  }
>  
> +static void log_node(struct landlock_hierarchy *const node)
> +{
> +	struct audit_buffer *ab;
> +
> +	if (WARN_ON_ONCE(!node))
> +		return;
> +
> +	/* Ignores already logged domains.  */
> +	if (READ_ONCE(node->log_status) == LANDLOCK_LOG_RECORDED)
> +		return;
> +
> +	ab = audit_log_start(audit_context(), GFP_ATOMIC,
> +			     AUDIT_LANDLOCK_DOM_INFO);
> +	if (!ab)
> +		return;
> +
> +	WARN_ON_ONCE(node->id == 0);
> +	audit_log_format(
> +		ab,
> +		"domain=%llx creation=%llu.%03lu pid=%d uid=%u exe=", node->id,
> +		/* See audit_log_start() */
> +		(unsigned long long)node->details->creation.tv_sec,
> +		node->details->creation.tv_nsec / 1000000,
> +		pid_nr(node->details->pid),
> +		from_kuid(&init_user_ns, node->details->cred->uid));
> +	audit_log_untrustedstring(ab, node->details->exe_path);
> +	audit_log_format(ab, " comm=");
> +	audit_log_untrustedstring(ab, node->details->comm);
> +	audit_log_end(ab);

I'm still struggling to understand why you need to log the domain's
creation time if you are connecting various Landlock audit events for a
single domain by the domain ID.  To be clear, I'm not opposed if you
want to include it, it just seems like there is a disconnect between
how audit is typically used and what you are proposing.

> +	/*
> +	 * There may be race condition leading to logging of the same domain
> +	 * several times but that is OK.
> +	 */
> +	WRITE_ONCE(node->log_status, LANDLOCK_LOG_RECORDED);
> +}
> +

--
paul-moore.com

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule  type
  2025-01-08 15:43 ` [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type Mickaël Salaün
  2025-01-13 14:55   ` Jann Horn
@ 2025-01-15 23:53   ` Paul Moore
  2025-01-16 10:57     ` Mickaël Salaün
  1 sibling, 1 reply; 58+ messages in thread
From: Paul Moore @ 2025-01-15 23:53 UTC (permalink / raw)
  To: Mickaël Salaün, Eric Paris, Günther Noack,
	Serge E . Hallyn
  Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
	Charles Zaffery, Daniel Burgener, Francis Laniel, James Morris,
	Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Jan  8, 2025 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> 
> Landlock manages a set of standalone security policies, which can be
> loaded by any process.  Because a sandbox policy may contain errors and
> can lead to log spam, we need a way to exclude some of them.  It is
> simple and it makes sense to identify Landlock domains (i.e. security
> policies) per binary path that loaded such policy.
> 
> Add a new AUDIT_EXE_LANDLOCK_DENY rule type to enables system
> administrator to filter logs according to the origin or the security
> policy responsible for a denial.

For reasons similar to why I didn't want to expose the audit timestamp
to users outside of audit, I'm not very enthusiastic about expanding
the audit filtering code at this point in time.

I'm not saying "no" exactly, just "not right now".


--
paul-moore.com

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 8/30] landlock: Add AUDIT_LANDLOCK_DENY and log ptrace denials
  2025-01-15 23:53   ` [PATCH v4 8/30] " Paul Moore
@ 2025-01-16 10:49     ` Mickaël Salaün
  2025-01-16 20:00       ` Paul Moore
  0 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-16 10:49 UTC (permalink / raw)
  To: Paul Moore
  Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
	Casey Schaufler, Charles Zaffery, Daniel Burgener, Francis Laniel,
	James Morris, Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Wed, Jan 15, 2025 at 06:53:06PM -0500, Paul Moore wrote:
> On Jan  8, 2025 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> > 
> > Add a new AUDIT_LANDLOCK_DENY record type dedicated to any Landlock
> > denials.
> 
> ...
> 
> > diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> > index 75e21a135483..60c909c396c0 100644
> > --- a/include/uapi/linux/audit.h
> > +++ b/include/uapi/linux/audit.h
> > @@ -33,7 +33,7 @@
> >   * 1100 - 1199 user space trusted application messages
> >   * 1200 - 1299 messages internal to the audit daemon
> >   * 1300 - 1399 audit event messages
> > - * 1400 - 1499 SE Linux use
> > + * 1400 - 1499 access control messages
> >   * 1500 - 1599 kernel LSPP events
> >   * 1600 - 1699 kernel crypto events
> >   * 1700 - 1799 kernel anomaly records
> > @@ -146,6 +146,7 @@
> >  #define AUDIT_IPE_ACCESS	1420	/* IPE denial or grant */
> >  #define AUDIT_IPE_CONFIG_CHANGE	1421	/* IPE config change */
> >  #define AUDIT_IPE_POLICY_LOAD	1422	/* IPE policy load */
> > +#define AUDIT_LANDLOCK_DENY	1423	/* Landlock denial */
> 
> I didn't have an opportunity to respond to your reply to my v3 comments
> before you posted v4, but I see you've decided to stick with _DENY as
> opposed to _ACCESS (or something similar).  Let me copy your reply
> below so I can respond appropriately ...
> 
> > A stronger type with the "denied" semantic makes more sense to me,
> > especially for Landlock which is unprivileged, and it makes it clear
> > that it should only impact performance and log size (i.e. audit log
> > creation) for denied actions.
> 
> This is not consistent with how audit is typically used.  Please
> convert to AUDIT_LANDLOCK_ACCESS, or something similar.

OK

> 
> > The next patch
> > series will also contain a new kind of audit rule to specifically
> > identify the origin of the policy that created this denied event, which
> > should make more sense.
> 
> Generally speaking audit only wants to support a small number of message
> types dedicated to a specific LSM.  If you're aware of additional message
> types that you plan to propose in a future patchset, it's probably a
> time to discuss those now.

The only other audit record type I'm thinking about would be one
dedicated to "potentially denied access", something similar to SELinux's
permissive mode.

> 
> > Because of its unprivileged nature, Landlock will never log granted
> > accesses by default.  In the future, we might want a permissive-like
> > mode for Landlock, but this will be optional, and I would also strongly
> > prefer to add new audit record types for new semantics.
> 
> Once again, this isn't consistent with how audit is typically used and
> I'm not seeing a compelling reason to rework how things are done.  Please
> stick with encoding the success/failure, accept/reject, etc. states in
> audit record fields, not the message types themselves.

OK

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 9/30] landlock: Add AUDIT_LANDLOCK_DOM_{INFO,DROP} and log domain properties
  2025-01-15 23:53   ` [PATCH v4 9/30] " Paul Moore
@ 2025-01-16 10:51     ` Mickaël Salaün
  2025-01-16 20:19       ` Paul Moore
  0 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-16 10:51 UTC (permalink / raw)
  To: Paul Moore
  Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
	Casey Schaufler, Charles Zaffery, Daniel Burgener, Francis Laniel,
	James Morris, Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Wed, Jan 15, 2025 at 06:53:07PM -0500, Paul Moore wrote:
> On Jan  8, 2025 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> > 
> > Asynchronously log domain information when it first denies an access.
> > This minimize the amount of generated logs, which makes it possible to
> > always log denials since they should not happen (except with the new
> > LANDLOCK_RESTRICT_SELF_QUIET flag).  These records are identified with
> > the new AUDIT_LANDLOCK_DOM_INFO type.
> > 
> > The AUDIT_LANDLOCK_DOM_INFO message contains:
> > - the "domain" ID which is described,
> > - the "creation" time of this domain,
> > - a minimal set of properties to easily identify the task that loaded
> >   the domain's policy with landlock_restrict_self(2): "pid", "uid",
> >   executable path ("exe"), and command line ("comm").
> > 
> > This requires each domain to save these task properties at creation
> > time in the new struct landlock_details.  A reference to the PID is kept
> > for the lifetime of the domain to avoid race conditions when
> > investigating the related task.  The executable path is resolved and
> > stored to not keep a reference to the filesystem and block related
> > actions.  All these metadata are stored for the lifetime of the related
> > domain and should then be minimal.  The required memory is not accounted
> > to the task calling landlock_restrict_self(2) contrary to most other
> > Landlock allocations (see related comment).
> > 
> > The AUDIT_LANDLOCK_DOM_INFO record follows the first AUDIT_LANDLOCK_DENY
> > record for the same domain, which is always followed by AUDIT_SYSCALL
> > and AUDIT_PROCTITLE.  This is in line with the audit logic to first
> > record the cause of an event, and then add context with other types of
> > record.
> > 
> > Audit event sample for a first denial:
> > 
> >   type=LANDLOCK_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
> >   type=LANDLOCK_DOM_INFO msg=audit(1732186800.349:44): domain=195ba459b creation=1732186800.345 pid=300 uid=0 exe="/root/sandboxer" comm="sandboxer"
> >   type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
> > 
> > Audit event sample for a following denial:
> > 
> >   type=LANDLOCK_DENY msg=audit(1732186800.372:45): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
> >   type=SYSCALL msg=audit(1732186800.372:45): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
> >
> > Log domain deletion with the new AUDIT_LANDLOCK_DOM_DROP record type
> > when a domain was previously logged.  This makes it possible for log
> > parsers to free potential resources when a domain ID will never show
> > again.
> > 
> > The AUDIT_LANDLOCK_DOM_DROP message contains:
> > - the "domain" ID which is being freed,
> > - the number of "denials" accounted to this domain, which is at least 1.
> > 
> > The number of denied access requests is useful to easily check how many
> > access requests a domain blocked and potentially if some of them are
> > missing in logs because of audit rate limiting or audit rules.  Rate
> > limiting could also drop this record though.
> 
> Wait, what rate limiting?  Landlock shouldn't be adding any audit event
> rate limiting beyond the queue management knobs built into the audit
> subsystem.  If you are comfortable rate limiting the logging of an event
> it is a good sign that it probably shouldn't be an audit event.

I was talking about current audit's rate limiting.

> 
> The audit subsystem is for security releveant events, not diagnostic,
> debugging, or other "nice to know" messages.

I agree, my goal was to only log denials with the minimal required
information to make sense of it.  "minimal" may be subjective though :)

> 
> > Audit event sample for a deletion of a domain that denied something:
> > 
> >   type=LANDLOCK_DOM_DROP msg=audit(1732186800.393:46): domain=195ba459b denials=2
> 
> As mentioned earlier, I don't like the number of different Landlock
> specific audit record types that are being created.  I'm going to
> suggest combining the LANDLOCK_DOM_INFO and LANDLOCK_DOM_DROP
> records into one (LANDLOCK_DOM?) and using an "op=" field to indicate
> creation/registration or destruction/unregistration of the domain ID.

I can squash them but they're just not about the same semantic at all.
One is an asynchronous event that describe a domain, and the other is a
synchronous event that informs about the end of a domain.  I think it
would be a missed opportunity to have cleaner messages and to simplify
log parsers.  Out of curiosity, are there existing audit record types
that mix semantic like this?

I wanted to give users the ability to filter each kind of record.  We'll
not be able to differentiate each kind of record with audit rules if we
use the same record type.  I don't understand why adding more audit
record types is an issue.

If we use an "op" field to differentiate these two types of information,
it would probably be "op=information" instead of "op=creation" because
the audit's timestamp will not identify the creation time of this
domain.

> 
> > Cc: Günther Noack <gnoack@google.com>
> > Cc: Paul Moore <paul@paul-moore.com>
> > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > Link: https://lore.kernel.org/r/20250108154338.1129069-10-mic@digikod.net
> > ---
> > Questions about AUDIT_LANDLOCK_DOM_INFO messages (keeping in mind that
> > each logged metadata may need to be stored for the lifetime of each
> > domain):
> > - Should we also log the initially restricted task's loginuid?
> > - Should we also log the initially restricted task's sessionid?
> 
> ...
> 
> > diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> > index 60c909c396c0..a72f7b3403be 100644
> > --- a/include/uapi/linux/audit.h
> > +++ b/include/uapi/linux/audit.h
> > @@ -147,6 +147,8 @@
> >  #define AUDIT_IPE_CONFIG_CHANGE	1421	/* IPE config change */
> >  #define AUDIT_IPE_POLICY_LOAD	1422	/* IPE policy load */
> >  #define AUDIT_LANDLOCK_DENY	1423	/* Landlock denial */
> > +#define AUDIT_LANDLOCK_DOM_INFO	1424	/* Landlock domain properties */
> > +#define AUDIT_LANDLOCK_DOM_DROP	1425	/* Landlock domain release */
> >  
> >  #define AUDIT_FIRST_KERN_ANOM_MSG   1700
> >  #define AUDIT_LAST_KERN_ANOM_MSG    1799
> > diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> > index d90680a5026a..ccc591146f8a 100644
> > --- a/security/landlock/audit.c
> > +++ b/security/landlock/audit.c
> > @@ -8,6 +8,8 @@
> >  #include <kunit/test.h>
> >  #include <linux/audit.h>
> >  #include <linux/lsm_audit.h>
> > +#include <linux/pid.h>
> > +#include <linux/uidgid.h>
> >  
> >  #include "audit.h"
> >  #include "domain.h"
> > @@ -30,6 +32,43 @@ static void log_blockers(struct audit_buffer *const ab,
> >  	audit_log_format(ab, "%s", get_blocker(type));
> >  }
> >  
> > +static void log_node(struct landlock_hierarchy *const node)
> > +{
> > +	struct audit_buffer *ab;
> > +
> > +	if (WARN_ON_ONCE(!node))
> > +		return;
> > +
> > +	/* Ignores already logged domains.  */
> > +	if (READ_ONCE(node->log_status) == LANDLOCK_LOG_RECORDED)
> > +		return;
> > +
> > +	ab = audit_log_start(audit_context(), GFP_ATOMIC,
> > +			     AUDIT_LANDLOCK_DOM_INFO);
> > +	if (!ab)
> > +		return;
> > +
> > +	WARN_ON_ONCE(node->id == 0);
> > +	audit_log_format(
> > +		ab,
> > +		"domain=%llx creation=%llu.%03lu pid=%d uid=%u exe=", node->id,
> > +		/* See audit_log_start() */
> > +		(unsigned long long)node->details->creation.tv_sec,
> > +		node->details->creation.tv_nsec / 1000000,
> > +		pid_nr(node->details->pid),
> > +		from_kuid(&init_user_ns, node->details->cred->uid));
> > +	audit_log_untrustedstring(ab, node->details->exe_path);
> > +	audit_log_format(ab, " comm=");
> > +	audit_log_untrustedstring(ab, node->details->comm);
> > +	audit_log_end(ab);
> 
> I'm still struggling to understand why you need to log the domain's
> creation time if you are connecting various Landlock audit events for a
> single domain by the domain ID.  To be clear, I'm not opposed if you
> want to include it, it just seems like there is a disconnect between
> how audit is typically used and what you are proposing.

For the reasons explained in this commit message, domain's creation cannot
be logged synchronously as other audit events.  However, timestamps are
useful to place them in the logs and order them according to other log
messages (i.e. to enrich log with more metadata).  Without this domain's
creation timestamp, we cannot know when it was created.  This
information is not strictly required but I think it can help get back to
the creation/creator of a domain.  I'll remove it if you think it
doesn't make sense to have such information with audit or if it falls
into the "nice to know" category.

> 
> > +	/*
> > +	 * There may be race condition leading to logging of the same domain
> > +	 * several times but that is OK.
> > +	 */
> > +	WRITE_ONCE(node->log_status, LANDLOCK_LOG_RECORDED);
> > +}
> > +
> 
> --
> paul-moore.com
> 

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule  type
  2025-01-15 23:53   ` Paul Moore
@ 2025-01-16 10:57     ` Mickaël Salaün
  2025-01-16 20:24       ` Paul Moore
  0 siblings, 1 reply; 58+ messages in thread
From: Mickaël Salaün @ 2025-01-16 10:57 UTC (permalink / raw)
  To: Paul Moore
  Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
	Casey Schaufler, Charles Zaffery, Daniel Burgener, Francis Laniel,
	James Morris, Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Wed, Jan 15, 2025 at 06:53:08PM -0500, Paul Moore wrote:
> On Jan  8, 2025 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> > 
> > Landlock manages a set of standalone security policies, which can be
> > loaded by any process.  Because a sandbox policy may contain errors and
> > can lead to log spam, we need a way to exclude some of them.  It is
> > simple and it makes sense to identify Landlock domains (i.e. security
> > policies) per binary path that loaded such policy.
> > 
> > Add a new AUDIT_EXE_LANDLOCK_DENY rule type to enables system
> > administrator to filter logs according to the origin or the security
> > policy responsible for a denial.
> 
> For reasons similar to why I didn't want to expose the audit timestamp
> to users outside of audit, I'm not very enthusiastic about expanding
> the audit filtering code at this point in time.
> 
> I'm not saying "no" exactly, just "not right now".

Contrary to MAC systems which are configured and tuned by one entity
(e.g. the sysadmin), Landlock security policies can be configured by a
lot of different entities (e.g. sysadmin, app developers, users).  One
noisy policy (e.g. a buggy sandboxed tar called in a loop by maintenance
scripts) could easily flood the audit logs without the ability for
sysadmins to filter such policy.  They will only be able to filter all
users that *may* trigger such log (by executing the buggy sandboxed
app), which would mean almost all users, which would mask all other
legitimate Landlock events, then nullifying the entire audit support for
Landlock.

My plan was to extend these new kind of filter types to PID, UID, GID,
and LOGINUID (a subset of the audit filter exclude list) to give the
necessary flexibility to filter policy creators.

We really need a way to filter policy creators, and that needs to be
part of the (initial) Landlock audit support for it to be viable.
What do you propose?

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 8/30] landlock: Add AUDIT_LANDLOCK_DENY and log ptrace denials
  2025-01-16 10:49     ` Mickaël Salaün
@ 2025-01-16 20:00       ` Paul Moore
  0 siblings, 0 replies; 58+ messages in thread
From: Paul Moore @ 2025-01-16 20:00 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
	Casey Schaufler, Charles Zaffery, Daniel Burgener, Francis Laniel,
	James Morris, Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Thu, Jan 16, 2025 at 5:49 AM Mickaël Salaün <mic@digikod.net> wrote:
> On Wed, Jan 15, 2025 at 06:53:06PM -0500, Paul Moore wrote:
> > On Jan  8, 2025 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:

...

> > > The next patch
> > > series will also contain a new kind of audit rule to specifically
> > > identify the origin of the policy that created this denied event, which
> > > should make more sense.
> >
> > Generally speaking audit only wants to support a small number of message
> > types dedicated to a specific LSM.  If you're aware of additional message
> > types that you plan to propose in a future patchset, it's probably a
> > time to discuss those now.
>
> The only other audit record type I'm thinking about would be one
> dedicated to "potentially denied access", something similar to SELinux's
> permissive mode.

In this case the "audit way" to handle this would be to add a
"permissive=[0|1]" field, or similar, to the AUDIT_LANDLOCK_ACCESS
message.  If this is something you are definitely going to add to
Landlock, I might suggest adding the "permissive=" field now so it is
present from the start.

-- 
paul-moore.com

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 9/30] landlock: Add AUDIT_LANDLOCK_DOM_{INFO,DROP} and log domain properties
  2025-01-16 10:51     ` Mickaël Salaün
@ 2025-01-16 20:19       ` Paul Moore
  0 siblings, 0 replies; 58+ messages in thread
From: Paul Moore @ 2025-01-16 20:19 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
	Casey Schaufler, Charles Zaffery, Daniel Burgener, Francis Laniel,
	James Morris, Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Thu, Jan 16, 2025 at 5:51 AM Mickaël Salaün <mic@digikod.net> wrote:
> On Wed, Jan 15, 2025 at 06:53:07PM -0500, Paul Moore wrote:
> > On Jan  8, 2025 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:

...

> > The audit subsystem is for security releveant events, not diagnostic,
> > debugging, or other "nice to know" messages.
>
> I agree, my goal was to only log denials with the minimal required
> information to make sense of it.  "minimal" may be subjective though :)

As a data point, it may be worth mentioning that it is not uncommon
for heavy audit users to generate more than 1TB of audit logs in a
day.  So yes, the scale of audit can vary quite a bit.

> > > Audit event sample for a deletion of a domain that denied something:
> > >
> > >   type=LANDLOCK_DOM_DROP msg=audit(1732186800.393:46): domain=195ba459b denials=2
> >
> > As mentioned earlier, I don't like the number of different Landlock
> > specific audit record types that are being created.  I'm going to
> > suggest combining the LANDLOCK_DOM_INFO and LANDLOCK_DOM_DROP
> > records into one (LANDLOCK_DOM?) and using an "op=" field to indicate
> > creation/registration or destruction/unregistration of the domain ID.
>
> I can squash them but they're just not about the same semantic at all.
> One is an asynchronous event that describe a domain, and the other is a
> synchronous event that informs about the end of a domain.

From my perspective, both messages report the status/lifecycle of a
Landlock domain ID and are thus well suited for a single message type.
Personally I question the value of this information, it seems overly
verbose when the access control decisions are the important things,
but you seem to believe this information is important so fine, we'll
add a message type for the domain information, but you only get one.

> If we use an "op" field to differentiate these two types of information,
> it would probably be "op=information" instead of "op=creation" because
> the audit's timestamp will not identify the creation time of this
> domain.

Call it whatever you like, I personally don't care so long as you
don't reuse a single "op=" value for multiple "operations".

> > > +static void log_node(struct landlock_hierarchy *const node)
> > > +{
> > > +   struct audit_buffer *ab;
> > > +
> > > +   if (WARN_ON_ONCE(!node))
> > > +           return;
> > > +
> > > +   /* Ignores already logged domains.  */
> > > +   if (READ_ONCE(node->log_status) == LANDLOCK_LOG_RECORDED)
> > > +           return;
> > > +
> > > +   ab = audit_log_start(audit_context(), GFP_ATOMIC,
> > > +                        AUDIT_LANDLOCK_DOM_INFO);
> > > +   if (!ab)
> > > +           return;
> > > +
> > > +   WARN_ON_ONCE(node->id == 0);
> > > +   audit_log_format(
> > > +           ab,
> > > +           "domain=%llx creation=%llu.%03lu pid=%d uid=%u exe=", node->id,
> > > +           /* See audit_log_start() */
> > > +           (unsigned long long)node->details->creation.tv_sec,
> > > +           node->details->creation.tv_nsec / 1000000,
> > > +           pid_nr(node->details->pid),
> > > +           from_kuid(&init_user_ns, node->details->cred->uid));
> > > +   audit_log_untrustedstring(ab, node->details->exe_path);
> > > +   audit_log_format(ab, " comm=");
> > > +   audit_log_untrustedstring(ab, node->details->comm);
> > > +   audit_log_end(ab);
> >
> > I'm still struggling to understand why you need to log the domain's
> > creation time if you are connecting various Landlock audit events for a
> > single domain by the domain ID.  To be clear, I'm not opposed if you
> > want to include it, it just seems like there is a disconnect between
> > how audit is typically used and what you are proposing.
>
> For the reasons explained in this commit message, domain's creation cannot
> be logged synchronously as other audit events.

Yeah, I've read that, and I disagree.  You *could* log a task's domain
creation synchronously as creation of a Landlock sandbox is a
process/syscall triggered event, you're *choosing* not to do so until
a denial occurs.  That's okay, but I'm tired of hearing that used as
an excuse to do other silly things.

> However, timestamps are
> useful to place them in the logs and order them according to other log
> messages (i.e. to enrich log with more metadata).  Without this domain's
> creation timestamp, we cannot know when it was created.

From my perspective this is because you are choosing not to record a
Landlock domain creation event.  I still have not read any reason why
this is not possible, only a design decision which is now causing you
to do some other unusual things from an audit perspective.

> This
> information is not strictly required but I think it can help get back to
> the creation/creator of a domain.  I'll remove it if you think it
> doesn't make sense to have such information with audit or if it falls
> into the "nice to know" category.

Ignoring my comments above for a moment, it does really seem to fall
into the "nice to know" category to me, but if you feel strongly about
it, it's okay to leave in ... it just isn't really consistent with how
things are generally audited.

-- 
paul-moore.com

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type
  2025-01-16 10:57     ` Mickaël Salaün
@ 2025-01-16 20:24       ` Paul Moore
  0 siblings, 0 replies; 58+ messages in thread
From: Paul Moore @ 2025-01-16 20:24 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
	Casey Schaufler, Charles Zaffery, Daniel Burgener, Francis Laniel,
	James Morris, Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
	Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
	Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
	Tahera Fahimi, Tyler Hicks, audit, linux-kernel,
	linux-security-module

On Thu, Jan 16, 2025 at 5:57 AM Mickaël Salaün <mic@digikod.net> wrote:
> On Wed, Jan 15, 2025 at 06:53:08PM -0500, Paul Moore wrote:
> > On Jan  8, 2025 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> > >
> > > Landlock manages a set of standalone security policies, which can be
> > > loaded by any process.  Because a sandbox policy may contain errors and
> > > can lead to log spam, we need a way to exclude some of them.  It is
> > > simple and it makes sense to identify Landlock domains (i.e. security
> > > policies) per binary path that loaded such policy.
> > >
> > > Add a new AUDIT_EXE_LANDLOCK_DENY rule type to enables system
> > > administrator to filter logs according to the origin or the security
> > > policy responsible for a denial.
> >
> > For reasons similar to why I didn't want to expose the audit timestamp
> > to users outside of audit, I'm not very enthusiastic about expanding
> > the audit filtering code at this point in time.
> >
> > I'm not saying "no" exactly, just "not right now".
>
> Contrary to MAC systems which are configured and tuned by one entity
> (e.g. the sysadmin), Landlock security policies can be configured by a
> lot of different entities (e.g. sysadmin, app developers, users).  One
> noisy policy (e.g. a buggy sandboxed tar called in a loop by maintenance
> scripts) could easily flood the audit logs without the ability for
> sysadmins to filter such policy.  They will only be able to filter all
> users that *may* trigger such log (by executing the buggy sandboxed
> app), which would mean almost all users, which would mask all other
> legitimate Landlock events, then nullifying the entire audit support for
> Landlock.
>
> My plan was to extend these new kind of filter types to PID, UID, GID,
> and LOGINUID (a subset of the audit filter exclude list) to give the
> necessary flexibility to filter policy creators.
>
> We really need a way to filter policy creators, and that needs to be
> part of the (initial) Landlock audit support for it to be viable.
> What do you propose?

If I recall correctly, there are other patches in the patchset which
provide some filtering of audit Landlock messages using mechanisms
specific to Landlock, that may be an option here.  If not, perhaps the
audit log is not the best way to log Landlock events?

-- 
paul-moore.com

^ permalink raw reply	[flat|nested] 58+ messages in thread

end of thread, other threads:[~2025-01-16 20:24 UTC | newest]

Thread overview: 58+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-01-08 15:43 [PATCH v4 00/30] Landlock audit support Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 01/30] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 02/30] lsm: Add audit_log_lsm_data() helper Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 03/30] landlock: Factor out check_access_path() Mickaël Salaün
2025-01-10 11:23   ` Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 04/30] landlock: Add unique ID generator Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 05/30] landlock: Move access types Mickaël Salaün
2025-01-10 11:23   ` Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 06/30] landlock: Simplify initially denied access rights Mickaël Salaün
2025-01-10 11:24   ` Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 07/30] landlock: Move domain hierarchy management and export helpers Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 08/30] landlock: Add AUDIT_LANDLOCK_DENY and log ptrace denials Mickaël Salaün
2025-01-15 23:53   ` [PATCH v4 8/30] " Paul Moore
2025-01-16 10:49     ` Mickaël Salaün
2025-01-16 20:00       ` Paul Moore
2025-01-08 15:43 ` [PATCH v4 09/30] landlock: Add AUDIT_LANDLOCK_DOM_{INFO,DROP} and log domain properties Mickaël Salaün
2025-01-15 23:53   ` [PATCH v4 9/30] " Paul Moore
2025-01-16 10:51     ` Mickaël Salaün
2025-01-16 20:19       ` Paul Moore
2025-01-08 15:43 ` [PATCH v4 10/30] landlock: Log mount-related denials Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 11/30] landlock: Align partial refer access checks with final ones Mickaël Salaün
2025-01-10 11:24   ` Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 12/30] selftests/landlock: Add test to check partial access in a mount tree Mickaël Salaün
2025-01-10 11:24   ` Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 13/30] landlock: Optimize file path walks and prepare for audit support Mickaël Salaün
2025-01-10 11:24   ` Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 14/30] landlock: Log file-related denials Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 15/30] landlock: Log truncate and IOCTL denials Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 16/30] landlock: Log TCP bind and connect denials Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 17/30] landlock: Log scoped denials Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 18/30] landlock: Control log events with LANDLOCK_RESTRICT_SELF_QUIET Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 19/30] samples/landlock: Do not log denials from the sandboxer by default Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 20/30] selftests/landlock: Fix error message Mickaël Salaün
2025-01-10 11:24   ` Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 21/30] selftests/landlock: Add wrappers.h Mickaël Salaün
2025-01-10 11:24   ` Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 22/30] selftests/landlock: Add layout1.umount_sandboxer tests Mickaël Salaün
2025-01-10 11:25   ` Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 23/30] selftests/landlock: Extend tests for landlock_restrict_self()'s flags Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 24/30] selftests/landlock: Add tests for audit and LANDLOCK_RESTRICT_SELF_QUIET Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 25/30] selftests/landlock: Add audit tests for ptrace Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 26/30] landlock: Export and rename landlock_get_inode_object() Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 27/30] fs: Add iput() cleanup helper Mickaël Salaün
2025-01-13 11:15   ` Mickaël Salaün
2025-01-13 16:45     ` Al Viro
2025-01-13 14:00   ` Jann Horn
2025-01-13 15:00     ` Christian Brauner
2025-01-13 16:55       ` Mickaël Salaün
2025-01-13 14:36   ` (subset) " Christian Brauner
2025-01-08 15:43 ` [PATCH v4 28/30] audit,landlock: Add AUDIT_EXE_LANDLOCK_DENY rule type Mickaël Salaün
2025-01-13 14:55   ` Jann Horn
2025-01-13 15:02     ` Christian Brauner
2025-01-13 16:55     ` Mickaël Salaün
2025-01-15 23:53   ` Paul Moore
2025-01-16 10:57     ` Mickaël Salaün
2025-01-16 20:24       ` Paul Moore
2025-01-08 15:43 ` [PATCH v4 29/30] selftests/landlock: Test audit rule with AUDIT_EXE_LANDLOCK_DOM Mickaël Salaün
2025-01-08 15:43 ` [PATCH v4 30/30] selftests/landlock: Test compatibility with audit rule lists Mickaël Salaün

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).