Re: [PATCH v2 1/1] audit: Mark audit_log_vformat() with __printf() attribute

2025-03-20 Thread Andy Shevchenko
On Thu, Mar 13, 2025 at 10:52:39AM +0200, Andy Shevchenko wrote:
> audit_log_vformat() is using printf() type of format, and GCC compiler
> (Debian 14.2.0-17) is not happy about this:
> 
> kernel/audit.c:1978:9: error: function ‘audit_log_vformat’ might be a 
> candidate for ‘gnu_printf’ format attribute [-Werror=suggest-attribute=format]
> kernel/audit.c:1987:17: error: function ‘audit_log_vformat’ might be a 
> candidate for ‘gnu_printf’ format attribute [-Werror=suggest-attribute=format]
> 
> Fix the compilation errors (`make W=1` when CONFIG_WERROR=y, which is default)
> by adding __printf() attribute.

Any comments on this? Can it be applied?

-- 
With Best Regards,
Andy Shevchenko





[PATCH v7 27/28] selftests/landlock: Add audit tests for network

2025-03-20 Thread Mickaël Salaün
Test all network blockers:
- net.bind_tcp
- net.connect_tcp

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

Cc: Günther Noack 
Cc: Paul Moore 
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- New patch.
---
 tools/testing/selftests/landlock/net_test.c | 132 
 1 file changed, 132 insertions(+)

diff --git a/tools/testing/selftests/landlock/net_test.c 
b/tools/testing/selftests/landlock/net_test.c
index d9de0ee49ebc..2a45208551e6 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -20,6 +20,7 @@
 #include 
 #include 
 
+#include "audit.h"
 #include "common.h"
 
 const short sock_port_start = (1 << 10);
@@ -1868,4 +1869,135 @@ TEST_F(port_specific, bind_connect_1023)
EXPECT_EQ(0, close(bind_fd));
 }
 
+static int matches_log_tcp(const int audit_fd, const char *const blockers,
+  const char *const dir_addr, const char *const addr,
+  const char *const dir_port)
+{
+   static const char log_template[] = REGEX_LANDLOCK_PREFIX
+   " blockers=%s %s=%s %s=1024$";
+   /*
+* Max strlen(blockers): 16
+* Max strlen(dir_addr): 5
+* Max strlen(addr): 12
+* Max strlen(dir_port): 4
+*/
+   char log_match[sizeof(log_template) + 37];
+   int log_match_len;
+
+   log_match_len = snprintf(log_match, sizeof(log_match), log_template,
+blockers, dir_addr, addr, dir_port);
+   if (log_match_len > sizeof(log_match))
+   return -E2BIG;
+
+   return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
+ NULL);
+}
+
+FIXTURE(audit)
+{
+   struct service_fixture srv0;
+   struct audit_filter audit_filter;
+   int audit_fd;
+};
+
+FIXTURE_VARIANT(audit)
+{
+   const char *const addr;
+   const struct protocol_variant prot;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit, ipv4) {
+   /* clang-format on */
+   .addr = "127\\.0\\.0\\.1",
+   .prot = {
+   .domain = AF_INET,
+   .type = SOCK_STREAM,
+   },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit, ipv6) {
+   /* clang-format on */
+   .addr = "::1",
+   .prot = {
+   .domain = AF_INET6,
+   .type = SOCK_STREAM,
+   },
+};
+
+FIXTURE_SETUP(audit)
+{
+   ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
+   setup_loopback(_metadata);
+
+   set_cap(_metadata, CAP_AUDIT_CONTROL);
+   self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
+   EXPECT_LE(0, self->audit_fd);
+   disable_caps(_metadata);
+};
+
+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, bind)
+{
+   const struct landlock_ruleset_attr ruleset_attr = {
+   .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_TCP,
+   };
+   struct audit_records records;
+   int ruleset_fd, sock_fd;
+
+   ruleset_fd =
+   landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+   ASSERT_LE(0, ruleset_fd);
+   enforce_ruleset(_metadata, ruleset_fd);
+   EXPECT_EQ(0, close(ruleset_fd));
+
+   sock_fd = socket_variant(&self->srv0);
+   ASSERT_LE(0, sock_fd);
+   EXPECT_EQ(-EACCES, bind_variant(sock_fd, &self->srv0));
+   EXPECT_EQ(0, matches_log_tcp(self->audit_fd, "net\\.bind_tcp", "saddr",
+variant->addr, "src"));
+
+   EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+   EXPECT_EQ(0, records.access);
+   EXPECT_EQ(1, records.domain);
+
+   EXPECT_EQ(0, close(sock_fd));
+}
+
+TEST_F(audit, connect)
+{
+   const struct landlock_ruleset_attr ruleset_attr = {
+   .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_TCP,
+   };
+   struct audit_records records;
+   int ruleset_fd, sock_fd;
+
+   ruleset_fd =
+   landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+   ASSERT_LE(0, ruleset_fd);
+   enforce_ruleset(_metadata, ruleset_fd);
+   EXPECT_EQ(0, close(ruleset_fd));
+
+   sock_fd = socket_variant(&self->srv0);
+   ASSERT_LE(0, sock_fd);
+   EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv0));
+   EXPECT_EQ(0, matches_log_tcp(self->audit_fd, "net\\.connect_tcp",
+"daddr", variant->addr, "dest"));
+
+   EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+   EXPECT_EQ(0, records.access);
+   EXPECT_EQ(1, records.domain);
+
+   EXPECT_EQ(0, close(sock_fd));

Re: [PATCH v2] audit,module: restore audit logging in load failure case

2025-03-20 Thread Petr Pavlu
On 3/17/25 20:57, Richard Guy Briggs wrote:
> The move of the module sanity check to earlier skipped the audit logging
> call in the case of failure and to a place where the previously used
> context is unavailable.
> 
> Add an audit logging call for the module loading failure case and get
> the module name when possible.
> 
> Link: https://issues.redhat.com/browse/RHEL-52839
> Fixes: 02da2cbab452 ("module: move check_modinfo() early to 
> early_mod_check()")
> Signed-off-by: Richard Guy Briggs 

The change looks reasonable to me from the modules perspective.

Nit: Viewing the linked address requires a login, so I'm not sure it's
appropriate for the commit message.

[...]

> diff --git a/kernel/module/main.c b/kernel/module/main.c
> index 1fb9ad289a6f..efa62ace1b23 100644
> --- a/kernel/module/main.c
> +++ b/kernel/module/main.c
> @@ -3346,7 +3346,7 @@ static int load_module(struct load_info *info, const 
> char __user *uargs,
>  
>   module_allocated = true;
>  
> - audit_log_kern_module(mod->name);
> + audit_log_kern_module(info->name);
>  
>   /* Reserve our place in the list. */
>   err = add_unformed_module(mod);
> @@ -3506,8 +3506,10 @@ static int load_module(struct load_info *info, const 
> char __user *uargs,
>* failures once the proper module was allocated and
>* before that.
>*/
> - if (!module_allocated)
> + if (!module_allocated) {
> + audit_log_kern_module(info->name ? info->name : 
> "(unavailable)");
>   mod_stat_bump_becoming(info, flags);
> + }
>   free_copy(info, flags);
>   return err;
>  }

Nit:
audit_log_kern_module(info->name ? info->name : 
"(unavailable)");
->
audit_log_kern_module(info->name ?: "(unavailable)");

-- 
Thanks,
Petr



[PATCH v3 0/5] Audit: Records for multiple security contexts

2025-03-20 Thread Casey Schaufler
The Linux audit system includes LSM based security "context" information
in its events. Historically, only one LSM that uses security contexts can
be active on a system. One of the few obsticles to allowing multiple LSM
support is the inability to report more than one security context in an
audit event. This patchset provides a mechanism to provide supplimental
records containing more than one security context for subjects and
objects.

The mechanism for reporting multiple security contexts inspired
considerable discussion. It would have been possible to add multiple
contexts to existing records using sophisticated formatting. This would
have significant backward compatibility issues, and require additional
parsing in user space code. Adding new records for an event that contain
the contexts is more in keeping with the way audit events have been
constructed in the past.

Only audit events associated with system calls have required multiple
records prior to this. Mechanism has been added allowing any event
to be composed of multiple records. This should make it easier to
add information to existing audit events without breaking backward
compatability.

v3:
Rework how security modules identify that they provide security
contexts to the audit system. Maintain a list within the audit
system of the security modules that provide security contexts.
Revert the separate counts of subject and object contexts.
v2:
Maintain separate counts for LSMs using subject contexts and object
contexts. AppArmor uses the former but not the latter.
Correct error handling in object record creation.

https://github.com/cschaufler/lsm-stacking#audit-6.14-rc1-v3

Casey Schaufler (5):
  Audit: Create audit_stamp structure
  LSM: security_lsmblob_to_secctx module selection
  Audit: Add record for multiple task security contexts
  Audit: multiple subject lsm values for netlabel
  Audit: Add record for multiple object contexts

 include/linux/audit.h|  19 +++
 include/linux/security.h |   6 +-
 include/uapi/linux/audit.h   |   2 +
 kernel/audit.c   | 255 +--
 kernel/audit.h   |  13 +-
 kernel/auditsc.c |  65 +++--
 net/netlabel/netlabel_user.c |   8 +-
 security/apparmor/lsm.c  |   3 +
 security/security.c  |  13 +-
 security/selinux/hooks.c |   3 +
 security/smack/smack_lsm.c   |   3 +
 11 files changed, 291 insertions(+), 99 deletions(-)

-- 
2.47.0




[PATCH v7 02/28] landlock: Add unique ID generator

2025-03-20 Thread Mickaël Salaün
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 across 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 secure 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 
Cc: Paul Moore 
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- Clean up headers.

Changes since v5:
- Add KUnit error message, suggested by Günther.

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   | 251 +++
 security/landlock/id.h   |  25 ++
 security/landlock/setup.c|   2 +
 tools/testing/kunit/configs/all_tests.config |   2 +
 6 files changed, 284 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 ..11fab9259c15
--- /dev/null
+++ b/security/landlock/id.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Unique identification number generator
+ *
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#include 
+#include 
+#include 
+#include 
+
+#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);
+   KUNI

[PATCH v7 10/28] landlock: Add AUDIT_LANDLOCK_DOMAIN and log domain status

2025-03-20 Thread Mickaël Salaün
Asynchronously log domain information when it first denies an access.
This minimize the amount of generated logs, which makes it possible to
always log denials for the current execution since they should not
happen.  These records are identified with the new AUDIT_LANDLOCK_DOMAIN
type.

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

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

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

Audit event sample for a first denial:

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

Audit event sample for a following denial:

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

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

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

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

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

Cc: Günther Noack 
Acked-by: Paul Moore  (Audit)
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- Improve consistency and clarity by renaming log_node() to log_domain()
  and "node" variables to "hierarchy".
- Fix unsigned integer check, reported by Dan Carpenter:
  
https://lore.kernel.org/r/2425110b-b5ca-4b33-bf75-e6fca0b0de63@stanley.mountain
- Fix inconsistent memory allocation flags and explain rationale,
  spotted by Paul.
- Fix domain deallocation logs by moving the call from cred put to
  hierarchy put.  Update landlock_log_drop_domain() accordingly.  This
  required to not include domain.h in ruleset.h anymore to avoid
  recursive include.
- Free hierarchy's details when putting a hierarchy.
- Save UID instead of keeping a reference to the current cred, which is
  not useful for now.
- Clean up includes.

Changes since v5:
- Add Acked-by Paul.
- Improve comment.
- Cosmetic code move in landlock_log_denial() for consistency, and to
  get a cleaner diff regarding the non-changing audit_enabled check.
- Remove unlikely().

Changes since v4:
- Rename AUDIT_LANDLOCK_DOM_{INFO,DROP} to AUDIT_LANDLOCK_DOMAIN and add
  a "status" field, as requested by Paul.
- Add a harcoded "mode=enforcing" to leave room for a potential future
  permissive mode, as suggested by Paul.
- Remove the "creation" timestamp, as suggested by Paul.
- Move LANDLOCK_PATH_MAX_SIZE to domain.h, check the size of the
  greatest landlock_details at build time, and improve comments.
- Improve audit check in landlock_log_drop_domain().
- Add missing headers.
- Fix typo in comment.
- Rebase on top of the landlock_log_denial() and subject type changes.

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

[PATCH v7 09/28] landlock: Add AUDIT_LANDLOCK_ACCESS and log ptrace denials

2025-03-20 Thread Mickaël Salaün
Add a new AUDIT_LANDLOCK_ACCESS record type dedicated to an access
request denied by a Landlock domain.  AUDIT_LANDLOCK_ACCESS indicates
that something unexpected happened.

For now, only denied access are logged, which means that any
AUDIT_LANDLOCK_ACCESS record is always followed by a SYSCALL record with
"success=no".  However, log parsers should check this syscall property
because this is the only sign that a request was denied.  Indeed, we
could have "success=yes" if Landlock would support a "permissive" mode.
We could also add a new field to AUDIT_LANDLOCK_DOMAIN for this mode
(see following commit).

By default, the only logged access requests are those coming from the
same executed program that enforced the Landlock restriction on itself.
In other words, no audit record are created for a task after it called
execve(2).  This is required to avoid log spam because programs may only
be aware of their own restrictions, but not the inherited ones.

Following commits will allow to conditionally generate
AUDIT_LANDLOCK_ACCESS records according to dedicated
landlock_restrict_self(2)'s flags.

The AUDIT_LANDLOCK_ACCESS 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 current/child task.  Indeed, the requester and the target are the
current task, but the action would be performed by the parent task.

Audit event sample:

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

A following commit adds user documentation.

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.

It is not useful to inline the audit_enabled check because other
computation are performed by landlock_log_denial().

Use scoped guards for RCU read-side critical sections.

Cc: Günther Noack 
Acked-by: Paul Moore  (Audit)
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- Improve consistency and clarity by renaming "node" variables to
  "hierarchy".
- Identify current (instead of the parent) as the task object in traceme
  requests.  It makes more sense to always log the target as the object.
- Improve is_valid_request() by checking the layer_plus_one number.
- Clean up includes and headers.
- Explain rationale for allocation flags.

Changes since v5:
- Add Acked-by Paul.
- Move request declarations in the landlock_log_denial() calls to
  not impact allowed requests with audit, and return as soon as possible
  when access is allowed.
- Remove the audit_context() check in landlock_log_denial() because the
  context may not already exist.  This issue was identified thanks to a
  new test.
- Remove unlikely().

Changes since v4:
- Rename AUDIT_LANDLOCK_DENY to AUDIT_LANDLOCK_ACCESS, requested by
  Paul.
- Make landlock_log_denial() get Landlock credential instead of Landlock
  domain to be able to filter on the domain_exe variable.
- Rebase on top of the migration from struct landlock_ruleset to struct
  landlock_cred_security.
- Rename landlock_init_current_hierarchy() to
  landlock_init_hierarchy_log().
- Rebase on top of the scoped guard patches.
- By default, do not log denials after an execution.
- Use scoped guards for RCU read-side critical sections.

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 S

[PATCH v7 12/28] landlock: Log file-related denials

2025-03-20 Thread Mickaël Salaün
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 
Depends-on: 058518c20920 ("landlock: Align partial refer access checks with 
final ones")
Depends-on: d617f0d72d80 ("landlock: Optimize file path walks and prepare for 
audit support")
Signed-off-by: Mickaël Salaün 
---

Changes since v5:
- Fix log_request_* update typo in is_access_to_paths_allowed().

Changes since v4:
- Rebase on top of the landlock_log_denial() and subject type changes.
- Add Depends-on tags.

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 | 178 --
 security/landlock/audit.h |   9 ++
 security/landlock/fs.c|  62 +++--
 3 files changed, 233 insertions(+), 16 deletions(-)

diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 423e8c61ebbd..d0c5f9ce1d43 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -7,23 +7,56 @@
 
 #include 
 #include 
+#include 
 #include 
 #include 
+#include 
 
 #include "audit.h"
+#include "common.h"
 #include "cred.h"
 #include "domain.h"
 #include "limits.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_TOPOLOGY:
+   WARN_ON_ONCE(access_bit != -1);
return "fs.change_topology";
+
+   case LANDLOCK_REQUEST_FS_ACCESS:
+   if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings)))
+   return "unknown";
+   return fs_access_strings[access_bit];
}
 
WARN_ON_ONCE(1);
@@ -31,9 +64,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 acc

[PATCH v7 22/28] selftests/landlock: Add tests for audit flags and domain IDs

2025-03-20 Thread Mickaël Salaün
Add audit_test.c to check with and without LANDLOCK_RESTRICT_SELF_*
flags against the two Landlock audit record types:
AUDIT_LANDLOCK_ACCESS and AUDIT_LANDLOCK_DOMAIN.

Check consistency of domain IDs per layer in AUDIT_LANDLOCK_ACCESS and
AUDIT_LANDLOCK_DOMAIN messages: denied access, domain allocation, and
domain deallocation.

These tests use signal scoping to make it simple.  They are not in the
scoped_signal_test.c file but in the new dedicated audit_test.c file.

Tests are run with audit filters to ensure the audit records come from
the test program.  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 
Cc: Paul Moore 
Cc: Phil Sutter 
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- Fix the race condition on some systems when checking the asynchronous
  domain deallacation event.
- Update audit_count_records() to check for audit errors.
- Add audit.layers tests to check consistency of domain IDs per layer.
- Move and simplify the domain allocated and deallocated helpers to
  audit.h
- Add missing include.

Changes since v5:
- Enhance audit_match_record() with an __u64 *domain_id argument set
  according to the audit logs.
- Rename audit_fork.flags to audit_flags.signal .
- Rename variants according to new flags.
- Check consistency of domain IDs.

Changes since v4:
- Update with the new landlock_restrict_self()'s flags, the new audit
  rule types, and message fields.
- Simplify audit_filter_exe() and audit_init_filter_exe().
- Test with kill() instead of umount().
- Test domain deallocation events.

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  | 437 ++
 tools/testing/selftests/landlock/audit_test.c | 332 +
 tools/testing/selftests/landlock/common.h |   2 +
 tools/testing/selftests/landlock/config   |   1 +
 4 files changed, 772 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 ..08a5c53bd6f5
--- /dev/null
+++ b/tools/testing/selftests/landlock/audit.h
@@ -0,0 +1,437 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Landlock audit helpers
+ *
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#define _GNU_SOURCE
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#endif
+
+#ifndef __maybe_unused
+#define __maybe_unused __attribute__((__unused__))
+#endif
+
+#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 const struct timeval audit_tv_dom_drop = {
+   /*
+* Because domain deallocation is tied to asynchronous credential
+* freeing, receiving such event may take some time.  In practice,
+* on a small VM, it should not exceed 100k usec, but let's wait up
+* to 1 second to be safe.
+*/
+   .tv_sec = 1,
+};
+
+static const struct timeval audit_tv_default = {
+   .tv_usec = 1,
+};
+
+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_

[PATCH v7 23/28] selftests/landlock: Test audit with restrict flags

2025-03-20 Thread Mickaël Salaün
Add audit_exec tests to filter Landlock denials according to
cross-execution or muted subdomains.

Add a wait-pipe-sandbox.c test program to sandbox itself and send a
(denied) signals to its parent.

Cc: Günther Noack 
Cc: Paul Moore 
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- Check audit_count_records() calls for audit errors.
- Simplify matches_log_fs_read_root().
- Update .gitignore

Changes since v5:
- Rename audit_exec.flags to audit_exec.signal_and_open .

Changes since v4:
- Revamp to test the Landlock syscall flags instead of the audit rules.
- Copy wait-pipe.c to wait-pipe-sandbox.c and extend it.
- Fix regex.

Changes since v3:
- New patch.
---
 tools/testing/selftests/landlock/.gitignore   |   1 +
 tools/testing/selftests/landlock/Makefile |   6 +-
 tools/testing/selftests/landlock/audit_test.c | 219 ++
 tools/testing/selftests/landlock/common.h |   1 +
 .../selftests/landlock/wait-pipe-sandbox.c| 131 +++
 5 files changed, 357 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/landlock/wait-pipe-sandbox.c

diff --git a/tools/testing/selftests/landlock/.gitignore 
b/tools/testing/selftests/landlock/.gitignore
index 335b2b1a3463..a820329cae0d 100644
--- a/tools/testing/selftests/landlock/.gitignore
+++ b/tools/testing/selftests/landlock/.gitignore
@@ -2,3 +2,4 @@
 /sandbox-and-launch
 /true
 /wait-pipe
+/wait-pipe-sandbox
diff --git a/tools/testing/selftests/landlock/Makefile 
b/tools/testing/selftests/landlock/Makefile
index 5cb0828f0514..a3f449914bf9 100644
--- a/tools/testing/selftests/landlock/Makefile
+++ b/tools/testing/selftests/landlock/Makefile
@@ -10,7 +10,11 @@ src_test := $(wildcard *_test.c)
 
 TEST_GEN_PROGS := $(src_test:.c=)
 
-TEST_GEN_PROGS_EXTENDED := true sandbox-and-launch wait-pipe
+TEST_GEN_PROGS_EXTENDED := \
+   true \
+   sandbox-and-launch \
+   wait-pipe \
+   wait-pipe-sandbox
 
 # Short targets:
 $(TEST_GEN_PROGS): LDLIBS += -lcap -lpthread
diff --git a/tools/testing/selftests/landlock/audit_test.c 
b/tools/testing/selftests/landlock/audit_test.c
index 59764dc18ecb..a0643070c403 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 
+#include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -329,4 +331,221 @@ TEST_F(audit_flags, signal)
}
 }
 
+static int matches_log_fs_read_root(int audit_fd)
+{
+   return audit_match_record(
+   audit_fd, AUDIT_LANDLOCK_ACCESS,
+   REGEX_LANDLOCK_PREFIX
+   " blockers=fs\\.read_dir path=\"/\" dev=\"[^\"]\\+\" 
ino=[0-9]\\+$",
+   NULL);
+}
+
+FIXTURE(audit_exec)
+{
+   struct audit_filter audit_filter;
+   int audit_fd;
+};
+
+FIXTURE_VARIANT(audit_exec)
+{
+   const int restrict_flags;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit_exec, default) {
+   /* clang-format on */
+   .restrict_flags = 0,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit_exec, same_exec_off) {
+   /* clang-format on */
+   .restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit_exec, subdomains_off) {
+   /* clang-format on */
+   .restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit_exec, cross_exec_on) {
+   /* clang-format on */
+   .restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit_exec, subdomains_off_and_cross_exec_on) {
+   /* clang-format on */
+   .restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF |
+ LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON,
+};
+
+FIXTURE_SETUP(audit_exec)
+{
+   disable_caps(_metadata);
+   set_cap(_metadata, CAP_AUDIT_CONTROL);
+
+   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 test filter for the bin_wait_pipe_sandbox program. */
+   EXPECT_EQ(0, audit_init_filter_exe(&self->audit_filter,
+  bin_wait_pipe_sandbox));
+   EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter,
+ AUDIT_ADD_RULE));
+
+   clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+FIXTURE_TEARDOWN(audit_exec)
+{
+   set_cap(_metadata, CAP_AUDIT_CONTROL);
+   EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter,
+

[PATCH v7 28/28] landlock: Add audit documentation

2025-03-20 Thread Mickaël Salaün
Because audit is dedicated to the system administrator, create a new
entry in Documentation/admin-guide/LSM .  Extend other Landlock
documentation's pages with this new one.

Extend UAPI with the new log flags.

Extend the guiding principles with logs.

Cc: Günther Noack 
Cc: Paul Moore 
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- Extend UAPI with ABI v7.

Changes since v5:
- Extend the guiding principles with logs.

Changes since v4:
- New patch.
---
 Documentation/admin-guide/LSM/index.rst|   1 +
 Documentation/admin-guide/LSM/landlock.rst | 158 +
 Documentation/security/landlock.rst|  13 +-
 Documentation/userspace-api/landlock.rst   |  17 +++
 MAINTAINERS|   1 +
 5 files changed, 189 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/admin-guide/LSM/landlock.rst

diff --git a/Documentation/admin-guide/LSM/index.rst 
b/Documentation/admin-guide/LSM/index.rst
index ce63be6d64ad..b44ef68f6e4d 100644
--- a/Documentation/admin-guide/LSM/index.rst
+++ b/Documentation/admin-guide/LSM/index.rst
@@ -48,3 +48,4 @@ subdirectories.
Yama
SafeSetID
ipe
+   landlock
diff --git a/Documentation/admin-guide/LSM/landlock.rst 
b/Documentation/admin-guide/LSM/landlock.rst
new file mode 100644
index ..9e61607def08
--- /dev/null
+++ b/Documentation/admin-guide/LSM/landlock.rst
@@ -0,0 +1,158 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. Copyright © 2025 Microsoft Corporation
+
+
+Landlock: system-wide management
+
+
+:Author: Mickaël Salaün
+:Date: March 2025
+
+Landlock can leverage the audit framework to log events.
+
+User space documentation can be found here:
+Documentation/userspace-api/landlock.rst.
+
+Audit
+=
+
+Denied access requests are logged by default for a sandboxed program if `audit`
+is enabled.  This default behavior can be changed with the
+sys_landlock_restrict_self() flags (cf.
+Documentation/userspace-api/landlock.rst).  Landlock logs can also be masked
+thanks to audit rules.  Landlock can generate 2 audit record types.
+
+Record types
+
+
+AUDIT_LANDLOCK_ACCESS
+This record type identifies a denied access request to a kernel resource.
+The ``domain`` field indicates the ID of the domain that blocked the
+request.  The ``blockers`` field indicates the cause(s) of this denial
+(separated by a comma), and the following fields identify the kernel object
+(similar to SELinux).  There may be more than one of this record type per
+audit event.
+
+Example with a file link request generating two records in the same event::
+
+domain=195ba459b blockers=fs.refer path="/usr/bin" dev="vda2" ino=351
+domain=195ba459b blockers=fs.make_reg,fs.refer path="/usr/local" 
dev="vda2" ino=365
+
+AUDIT_LANDLOCK_DOMAIN
+This record type describes the status of a Landlock domain.  The ``status``
+field can be either ``allocated`` or ``deallocated``.
+
+The ``allocated`` status is part of the same audit event and follows
+the first logged ``AUDIT_LANDLOCK_ACCESS`` record of a domain.  It 
identifies
+Landlock domain information at the time of the sys_landlock_restrict_self()
+call with the following fields:
+
+- the ``domain`` ID
+- the enforcement ``mode``
+- the domain creator's ``pid``
+- the domain creator's ``uid``
+- the domain creator's executable path (``exe``)
+- the domain creator's command line (``comm``)
+
+Example::
+
+domain=195ba459b status=allocated mode=enforcing pid=300 uid=0 
exe="/root/sandboxer" comm="sandboxer"
+
+The ``deallocated`` status is an event on its own and it identifies a
+Landlock domain release.  After such event, it is guarantee that the
+related domain ID will never be reused during the lifetime of the system.
+The ``domain`` field indicates the ID of the domain which is released, and
+the ``denials`` field indicates the total number of denied access request,
+which might not have been logged according to the audit rules and
+sys_landlock_restrict_self()'s flags.
+
+Example::
+
+domain=195ba459b status=deallocated denials=3
+
+
+Event samples
+--
+
+Here are two examples of log events (see serial numbers).
+
+In this example a sandboxed program (``kill``) tries to send a signal to the
+init process, which is denied because of the signal scoping restriction
+(``LL_SCOPED=s``)::
+
+  $ LL_FS_RO=/ LL_FS_RW=/ LL_SCOPED=s LL_FORCE_LOG=1 ./sandboxer kill 1
+
+This command generates two events, each identified with a unique serial
+number following a timestamp (``msg=audit(1729738800.268:30)``).  The first
+event (serial ``30``) contains 4 records.  The first record
+(``type=LANDLOCK_ACCESS``) shows an access denied by the domain `1a6fdc66f`.
+The cause of this denial is signal scopping restriction
+(``blockers=scope.signal``).  The process that would have re

[PATCH v7 14/28] landlock: Log truncate and IOCTL denials

2025-03-20 Thread Mickaël Salaün
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 aligned blob size is
still 16 bytes because this new one-byte deny_masks field follows the
existing two-bytes allowed_access field and precede the packed
fown_subject.

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 ioctlcmd=0x5401

Cc: Günther Noack 
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- Remove useless include.

Changes since v5:
- Switch to LSM_AUDIT_DATA_IOCTL_OP for IOCTL hooks, and update sample
  accordingly.
- Move request declarations in the landlock_log_denial() calls to
  not impact allowed requests with audit, and remove update_request()
  which is now useless.

Changes since v4:
- Rebase on top of the landlock_log_denial() and subject type changes.

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  | 101 ++--
 security/landlock/audit.h  |   4 ++
 security/landlock/domain.c | 133 +
 security/landlock/domain.h |   7 ++
 security/landlock/fs.c |  34 ++
 security/landlock/fs.h |   9 +++
 7 files changed, 306 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 d0c5f9ce1d43..f50ce677e3a0 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -12,6 +12,7 @@
 #include 
 #include 
 
+#include "access.h"
 #include "audit.h"
 #include "common.h"
 #include "cred.h"
@@ -249,6 +250,88 @@ 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);

[PATCH v7 00/28] Landlock audit support

2025-03-20 Thread Mickaël Salaün
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.

# Main changes from previous version

A lot of new tests for domain layers, filesystem, and netwoking.  the
previous test race condition is fixed.  Test coverage is now 94%!

Some issues fixed thanks to the new tests, code simplification, a few
cosmetic changes, and an improved documentation.

This series is rebased on top of these fixes:
https://lore.kernel.org/r/20250318161443.279194-1-...@digikod.net

# 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 new flags to not log denials
related to this specific domain.  Except for well-understood exceptions,
these flags 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_ACCESS msg=audit(1729738800.268:30): domain=1a6fdc66f 
blockers=scope.signal opid=1 ocomm="systemd"
  type=LANDLOCK_DOMAIN msg=audit(1729738800.268:30): domain=1a6fdc66f 
status=allocated mode=enforcing pid=286 uid=0 exe="/root/sandboxer" 
comm="sandboxer"
  type=SYSCALL msg=audit(1729738800.268:30): arch=c03e syscall=62 
success=no exit=-1 [..] ppid=272 pid=286 auid=0 uid=0 gid=0 [...] comm="kill" 
[...]
  type=PROCTITLE msg=audit(1729738800.268:30): proctitle=6B696C6C0031
  type=LANDLOCK_DOMAIN msg=audit(1729738800.324:31): domain=1a6fdc66f 
status=deallocated denials=1

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

  type=LANDLOCK_ACCESS msg=audit(1729738800.221:33): domain=1a6fdc679 
blockers=fs.write_file path="/dev/tty" dev="devtmpfs" ino=9
  type=LANDLOCK_DOMAIN msg=audit(1729738800.221:33): domain=1a6fdc679 
status=allocated mode=enforcing pid=289 uid=0 exe="/root/sandboxer" 
comm="sandboxer"
  type=SYSCALL msg=audit(1729738800.221:33): arch=c03e syscall=257 
success=no exit=-13 [...] ppid=272 pid=289 auid=0 uid=0 gid=0 [...] comm="sh" 
[...]
  type=PROCTITLE msg=audit(1729738800.221:33): 
proctitle=7368002D63006563686F203E202F6574632F706173737764
  type=LANDLOCK_ACCESS msg=audit(1729738800.221:34): domain=1a6fdc679 
blockers=fs.write_file path="/etc/passwd" dev="vda2" ino=143821
  type=SYSCALL msg=audit(1729738800.221:34): arch=c03e syscall=257 
success=no exit=-13 [...] ppid=272 pid=289 auid=0 uid=0 gid=0 [...] comm="sh" 
[...]
  type=PROCTITLE msg=audit(1729738800.221:34): 
proctitle=7368002D63006563686F203E202F6574632F706173737764
  type=LANDLOCK_DOMAIN msg=audit(1729738800.261:35): domain=1a6fdc679 
status=deallocated denials=2

# Previous versions

v6: https://lore.kernel.org/r/20250308184422.2159360-1-...@digikod.net
v5: https://lore.kernel.org/r/20250131163059.1139617-1-...@digikod.net
v4: https://lore.kernel.org/r/20250108154338.1129069-1-...@digikod.net
v3: https://lore.kernel.org/r/20241122143353.59367-1-...@digikod.net
v2: https://lore.kernel.org/r/20241022161009.982584-1-...@digikod.net
v1: https://lore.kernel.org/r/20230921061641.273654-1-...@digikod.net

Regards,

Mickaël Salaün (28):
  lsm: Add audit_log_lsm_data() helper
  landlock: Add unique ID generator
  landlock: Move domain hierarchy management
  landlock: Pr

[PATCH v7 03/28] landlock: Move domain hierarchy management

2025-03-20 Thread Mickaël Salaün
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.

Cc: Günther Noack 
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- Clean up headers.

Changes since v4:
- Revert v3 changes because of the new audit rule patch removal.

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

Changes since v1:
- New patch.
---
 security/landlock/domain.h  | 48 +
 security/landlock/ruleset.c | 21 +++-
 security/landlock/ruleset.h | 17 +
 security/landlock/task.c|  1 +
 4 files changed, 53 insertions(+), 34 deletions(-)
 create mode 100644 security/landlock/domain.h

diff --git a/security/landlock/domain.h b/security/landlock/domain.h
new file mode 100644
index ..d22712e5fb0f
--- /dev/null
+++ b/security/landlock/domain.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Domain management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün 
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_DOMAIN_H
+#define _SECURITY_LANDLOCK_DOMAIN_H
+
+#include 
+#include 
+
+/**
+ * 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;
+};
+
+static inline void
+landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy)
+{
+   if (hierarchy)
+   refcount_inc(&hierarchy->usage);
+}
+
+static inline 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);
+   }
+}
+
+#endif /* _SECURITY_LANDLOCK_DOMAIN_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index bff4e40a3093..adb7f87828df 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -23,6 +23,7 @@
 #include 
 
 #include "access.h"
+#include "domain.h"
 #include "limits.h"
 #include "object.h"
 #include "ruleset.h"
@@ -307,22 +308,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)
@@ -477,7 +462,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:
@@ -501,7 +486,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 52f4f0af6ab0..bbb5996545d2 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -17,6 +17,7 @@
 #include 
 
 #include "access.h"
+#include "domain.h"
 #include "limits.h"
 #include "object.h"
 
@@ -108,22 +109,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 4578ce6e319d..e04646d80e78 100644
--- a/security/landlock/task.c
+++ b/security/landl

[PATCH v7 08/28] landlock: Identify domain execution crossing

2025-03-20 Thread Mickaël Salaün
Extend struct landlock_cred_security with a domain_exec bitmask to
identify which Landlock domain were created by the current task's bprm.
The whole bitmask is reset on each execve(2) call.

Cc: Günther Noack 
Cc: Paul Moore 
Signed-off-by: Mickaël Salaün 
---

Changes since v5:
- Add documentation and pack struct landlock_cred_security to minimize
  struct landlock_file_security.

Changes since v4:
- New patch.
---
 security/landlock/cred.c | 26 ++
 security/landlock/cred.h | 32 +++-
 security/landlock/syscalls.c |  5 +
 3 files changed, 58 insertions(+), 5 deletions(-)

diff --git a/security/landlock/cred.c b/security/landlock/cred.c
index db9fe7d906ba..a22756fe3b71 100644
--- a/security/landlock/cred.c
+++ b/security/landlock/cred.c
@@ -4,8 +4,10 @@
  *
  * Copyright © 2017-2020 Mickaël Salaün 
  * Copyright © 2018-2020 ANSSI
+ * Copyright © 2025 Microsoft Corporation
  */
 
+#include 
 #include 
 #include 
 
@@ -17,11 +19,12 @@
 static void hook_cred_transfer(struct cred *const new,
   const struct cred *const old)
 {
-   struct landlock_ruleset *const old_dom = landlock_cred(old)->domain;
+   const struct landlock_cred_security *const old_llcred =
+   landlock_cred(old);
 
-   if (old_dom) {
-   landlock_get_ruleset(old_dom);
-   landlock_cred(new)->domain = old_dom;
+   if (old_llcred->domain) {
+   landlock_get_ruleset(old_llcred->domain);
+   *landlock_cred(new) = *old_llcred;
}
 }
 
@@ -40,10 +43,25 @@ static void hook_cred_free(struct cred *const cred)
landlock_put_ruleset_deferred(dom);
 }
 
+#ifdef CONFIG_AUDIT
+
+static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
+{
+   /* Resets for each execution. */
+   landlock_cred(bprm->cred)->domain_exec = 0;
+   return 0;
+}
+
+#endif /* CONFIG_AUDIT */
+
 static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(cred_prepare, hook_cred_prepare),
LSM_HOOK_INIT(cred_transfer, hook_cred_transfer),
LSM_HOOK_INIT(cred_free, hook_cred_free),
+
+#ifdef CONFIG_AUDIT
+   LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec),
+#endif /* CONFIG_AUDIT */
 };
 
 __init void landlock_add_cred_hooks(void)
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
index eb691130dd67..3bf18551d7b8 100644
--- a/security/landlock/cred.h
+++ b/security/landlock/cred.h
@@ -10,17 +10,47 @@
 #ifndef _SECURITY_LANDLOCK_CRED_H
 #define _SECURITY_LANDLOCK_CRED_H
 
+#include 
 #include 
 #include 
 #include 
 
 #include "access.h"
+#include "limits.h"
 #include "ruleset.h"
 #include "setup.h"
 
+/**
+ * struct landlock_cred_security - Credential security blob
+ *
+ * This structure is packed to minimize the size of struct
+ * landlock_file_security.  However, it is always aligned in the LSM cred blob,
+ * see lsm_set_blob_size().
+ */
 struct landlock_cred_security {
+   /**
+* @domain: Immutable ruleset enforced on a task.
+*/
struct landlock_ruleset *domain;
-};
+
+#ifdef CONFIG_AUDIT
+   /**
+* @domain_exec: Bitmask identifying the domain layers that were 
enforced by
+* the current task's executed file (i.e. no new execve(2) since
+* landlock_restrict_self(2)).
+*/
+   u16 domain_exec;
+#endif /* CONFIG_AUDIT */
+} __packed;
+
+#ifdef CONFIG_AUDIT
+
+/* Makes sure all layer executions can be stored. */
+static_assert(BITS_PER_TYPE(typeof_member(struct landlock_cred_security,
+ domain_exec)) >=
+ LANDLOCK_MAX_NUM_LAYERS);
+
+#endif /* CONFIG_AUDIT */
 
 static inline struct landlock_cred_security *
 landlock_cred(const struct cred *cred)
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index cf9e0483e542..b7b268f43a3b 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -510,5 +510,10 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, 
ruleset_fd, const __u32,
/* Replaces the old (prepared) domain. */
landlock_put_ruleset(new_llcred->domain);
new_llcred->domain = new_dom;
+
+#ifdef CONFIG_AUDIT
+   new_llcred->domain_exec |= 1 << (new_dom->num_layers - 1);
+#endif /* CONFIG_AUDIT */
+
return commit_creds(new_cred);
 }
-- 
2.49.0




[PATCH v7 26/28] selftests/landlock: Add audit tests for filesystem

2025-03-20 Thread Mickaël Salaün
Test all filesystem blockers, including events with several records, and
record with several blockers:
- 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
- fs.change_topology

Cc: Günther Noack 
Cc: Paul Moore 
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- Check audit_count_records() calls for audit errors.
- Add regression test for commit d617f0d72d80 ("landlock: Optimize file
  path walks and prepare for audit support").
- Add test_rename and test_exchange tests.
- Check domain allocation interwinded record.
- Add the execute_make test.
- Use a set of access rights instead of only one to make sure only the
  relevant access rights are blocked and logged.
- Add new audit_layout1.mount test.
- Add comments.

Changes since v5:
- New patch.
---
 tools/testing/selftests/landlock/audit.h   |  35 ++
 tools/testing/selftests/landlock/common.h  |  16 +
 tools/testing/selftests/landlock/fs_test.c | 594 +
 3 files changed, 645 insertions(+)

diff --git a/tools/testing/selftests/landlock/audit.h 
b/tools/testing/selftests/landlock/audit.h
index 08a5c53bd6f5..b9054086a0c9 100644
--- a/tools/testing/selftests/landlock/audit.h
+++ b/tools/testing/selftests/landlock/audit.h
@@ -208,6 +208,41 @@ static int audit_set_status(int fd, __u32 key, __u32 val)
return audit_request(fd, &msg, NULL);
 }
 
+/* Returns a pointer to the last filled character of @dst, which is `\0`.  */
+static __maybe_unused char *regex_escape(const char *const src, char *dst,
+size_t dst_size)
+{
+   char *d = dst;
+
+   for (const char *s = src; *s; s++) {
+   switch (*s) {
+   case '$':
+   case '*':
+   case '.':
+   case '[':
+   case '\\':
+   case ']':
+   case '^':
+   if (d >= dst + dst_size - 2)
+   return (char *)-ENOMEM;
+
+   *d++ = '\\';
+   *d++ = *s;
+   break;
+   default:
+   if (d >= dst + dst_size - 1)
+   return (char *)-ENOMEM;
+
+   *d++ = *s;
+   }
+   }
+   if (d >= dst + dst_size - 1)
+   return (char *)-ENOMEM;
+
+   *d = '\0';
+   return d;
+}
+
 /*
  * @domain_id: The domain ID extracted from the audit message (if the first 
part
  * of @pattern is REGEX_LANDLOCK_PREFIX).  It is set to 0 if the domain ID is
diff --git a/tools/testing/selftests/landlock/common.h 
b/tools/testing/selftests/landlock/common.h
index 6e1d143ddfa7..88a3c78f5d98 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -208,6 +208,22 @@ enforce_ruleset(struct __test_metadata *const _metadata, 
const int ruleset_fd)
}
 }
 
+static void __maybe_unused
+drop_access_rights(struct __test_metadata *const _metadata,
+  const struct landlock_ruleset_attr *const ruleset_attr)
+{
+   int ruleset_fd;
+
+   ruleset_fd =
+   landlock_create_ruleset(ruleset_attr, sizeof(*ruleset_attr), 0);
+   EXPECT_LE(0, ruleset_fd)
+   {
+   TH_LOG("Failed to create a ruleset: %s", strerror(errno));
+   }
+   enforce_ruleset(_metadata, ruleset_fd);
+   EXPECT_EQ(0, close(ruleset_fd));
+}
+
 struct protocol_variant {
int domain;
int type;
diff --git a/tools/testing/selftests/landlock/fs_test.c 
b/tools/testing/selftests/landlock/fs_test.c
index aa6f2c1cbec7..f819011a8798 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -41,6 +41,7 @@
 #define _ASM_GENERIC_FCNTL_H
 #include 
 
+#include "audit.h"
 #include "common.h"
 
 #ifndef renameat2
@@ -5554,4 +,597 @@ TEST_F_FORK(layout3_fs, release_inodes)
ASSERT_EQ(EACCES, test_open(TMP_DIR, O_RDONLY));
 }
 
+static int matches_log_fs_extra(struct __test_metadata *const _metadata,
+   int audit_fd, const char *const blockers,
+   const char *const path, const char *const extra)
+{
+   static const char log_template[] = REGEX_LANDLOCK_PREFIX
+   " blockers=fs\\.%s path=\"%s\" dev=\"[^\"]\\+\" ino=[0-9]\\+$";
+   char *absolute_path = NULL;
+   size_t log_match_remaining = sizeof(log_template) + strlen(blockers) +
+PATH_MAX * 2 +
+(extra ? strlen(extra) : 0) + 1;
+   char log_match[log_match_remaining];
+   char *log_match_cursor = log_match;
+   size_t chunk_len;
+
+   chunk_len = snprintf(log_match_cursor, log_match_remaining,
+REGEX_LANDLOCK_PREFIX " blo

[PATCH v7 24/28] selftests/landlock: Add audit tests for ptrace

2025-03-20 Thread Mickaël Salaün
Add tests for all ptrace actions checking "blockers=ptrace" records.

This also improves PTRACE_TRACEME and PTRACE_ATTACH tests by making sure
that the restrictions comes from Landlock, and with the expected
process.  These extended tests are like enhanced errno checks that make
sure Landlock enforcement is consistent.

Cc: Günther Noack 
Cc: Paul Moore 
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- Check audit_count_records() calls for audit errors.
- Update and fix PTRACE_TRACEME test to reflect kernel change.

Changes since v5:
- Move all audit tests to a new audit.trace test suite.
- Simplify tests by only checking PTRACE_TRACEME and PTRACE_ATTACH with
  one scenario.  This is preferable to not impact existing tests.
- Make sure there is no unknown Landlock audit record.

Changes since v3:
- Update test coverage.

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

diff --git a/tools/testing/selftests/landlock/ptrace_test.c 
b/tools/testing/selftests/landlock/ptrace_test.c
index 8f31b673ff2d..4e356334ecb7 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 
  * Copyright © 2019-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
  */
 
 #define _GNU_SOURCE
@@ -17,6 +18,7 @@
 #include 
 #include 
 
+#include "audit.h"
 #include "common.h"
 
 /* Copied from security/yama/yama_lsm.c */
@@ -434,4 +436,142 @@ TEST_F(hierarchy, trace)
_metadata->exit_code = KSFT_FAIL;
 }
 
+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_ACCESS, log_match,
+ NULL);
+}
+
+FIXTURE(audit)
+{
+   struct audit_filter audit_filter;
+   int audit_fd;
+};
+
+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);
+   clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+FIXTURE_TEARDOWN_PARENT(audit)
+{
+   EXPECT_EQ(0, audit_cleanup(-1, NULL));
+}
+
+/* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
+TEST_F(audit, trace)
+{
+   pid_t child;
+   int status;
+   int pipe_child[2], pipe_parent[2];
+   int yama_ptrace_scope;
+   char buf_parent;
+   struct audit_records records;
+
+   /* Makes sure there is no superfluous logged records. */
+   EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+   EXPECT_EQ(0, records.access);
+   EXPECT_EQ(0, records.domain);
+
+   yama_ptrace_scope = get_yama_ptrace_scope();
+   ASSERT_LE(0, yama_ptrace_scope);
+
+   if (yama_ptrace_scope > YAMA_SCOPE_DISABLED)
+   TH_LOG("Incomplete tests due to Yama restrictions (scope %d)",
+  yama_ptrace_scope);
+
+   /*
+* Removes all effective and permitted capabilities to not interfere
+* with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS.
+*/
+   drop_caps(_metadata);
+
+   ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+   ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+
+   child = fork();
+   ASSERT_LE(0, child);
+   if (child == 0) {
+   char buf_child;
+
+   ASSERT_EQ(0, close(pipe_parent[1]));
+   ASSERT_EQ(0, close(pipe_child[0]));
+
+   /* Waits for the parent to be in a domain, if any. */
+   ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+
+   /* Tests child PTRACE_TRACEME. */
+   EXPECT_EQ(-1, ptrace(PTRACE_TRACEME));
+   EXPECT_EQ(EPERM, errno);
+   /* We should see the child process. */
+   EXPECT_EQ(0, matches_log_ptrace(_metadata, self->audit_fd,
+   getpid()));
+
+   EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+   EXPECT_EQ(0, records.access);
+   /* Checks for a domain creation. */
+   EXPECT_EQ(1, records.domain);
+
+   /*
+* Signals that the PTRACE_ATTACH test is done and the
+* PTRACE_TRACEME test is ongoing.
+*/
+   ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+
+   /* Waits for the parent PTRACE

[PATCH v7 13/28] landlock: Factor out IOCTL hooks

2025-03-20 Thread Mickaël Salaün
Compat and non-compat IOCTL hooks are almost the same, except to compare
the IOCTL command.  Factor out these two IOCTL hooks to highlight the
difference and minimize audit changes (see next commit).

Cc: Günther Noack 
Signed-off-by: Mickaël Salaün 
---

Changes since v6:
- New patch.
---
 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 ba03439caab9..c67ef35248e3 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1698,8 +1698,8 @@ static int hook_file_truncate(struct file *const file)
return -EACCES;
 }
 
-static int hook_file_ioctl(struct file *file, unsigned int cmd,
-  unsigned long arg)
+static int hook_file_ioctl_common(const struct file *const file,
+ const unsigned int cmd, const bool is_compat)
 {
access_mask_t allowed_access = landlock_file(file)->allowed_access;
 
@@ -1715,33 +1715,23 @@ static int hook_file_ioctl(struct file *file, unsigned 
int cmd,
if (!is_device(file))
return 0;
 
-   if (is_masked_device_ioctl(cmd))
+   if (unlikely(is_compat) ? is_masked_device_ioctl_compat(cmd) :
+ is_masked_device_ioctl(cmd))
return 0;
 
return -EACCES;
 }
 
+static int hook_file_ioctl(struct file *file, unsigned int cmd,
+  unsigned long arg)
+{
+   return hook_file_ioctl_common(file, cmd, false);
+}
+
 static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
  unsigned long arg)
 {
-   access_mask_t allowed_access = landlock_file(file)->allowed_access;
-
-   /*
-* It is the access rights at the time of opening the file which
-* determine whether IOCTL can be used on the opened file later.
-*
-* The access right is attached to the opened file in hook_file_open().
-*/
-   if (allowed_access & LANDLOCK_ACCESS_FS_IOCTL_DEV)
-   return 0;
-
-   if (!is_device(file))
-   return 0;
-
-   if (is_masked_device_ioctl_compat(cmd))
-   return 0;
-
-   return -EACCES;
+   return hook_file_ioctl_common(file, cmd, true);
 }
 
 static void hook_file_set_fowner(struct file *file)
-- 
2.49.0




[PATCH v7 20/28] selftests/landlock: Add test for invalid ruleset file descriptor

2025-03-20 Thread Mickaël Salaün
To align with fs_test's layout1.inval and layout0.proc_nsfs which test
EBADFD for landlock_add_rule(2), create a new base_test's
restrict_self_fd which test EBADFD for landlock_restrict_self(2).

Cc: Günther Noack 
Cc: Paul Moore 
Signed-off-by: Mickaël Salaün 
---

Changes since v5:
- New standalone patch (that can be backported).
---
 tools/testing/selftests/landlock/base_test.c | 11 +++
 1 file changed, 11 insertions(+)

diff --git a/tools/testing/selftests/landlock/base_test.c 
b/tools/testing/selftests/landlock/base_test.c
index 7dc431a0e18e..cb13416533d2 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -269,6 +269,17 @@ TEST(restrict_self_checks_ordering)
ASSERT_EQ(0, close(ruleset_fd));
 }
 
+TEST(restrict_self_fd)
+{
+   int fd;
+
+   fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
+   ASSERT_LE(0, fd);
+
+   EXPECT_EQ(-1, landlock_restrict_self(fd, 0));
+   EXPECT_EQ(EBADFD, errno);
+}
+
 TEST(ruleset_fd_io)
 {
struct landlock_ruleset_attr ruleset_attr = {
-- 
2.49.0