Re: [PATCH v2 1/1] audit: Mark audit_log_vformat() with __printf() attribute
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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