* [PATCH v3 00/23] Landlock audit support
@ 2024-11-22 14:33 Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 01/23] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set Mickaël Salaün
` (23 more replies)
0 siblings, 24 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Hi,
This patch series adds audit support to Landlock.
Logging denied requests is useful for different use cases:
- sysadmins: to look for users' issues
- security experts: to detect attack attempts
- power users: to understand denials
- app developers: to ease and speed up sandboxing support
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 constraints (e.g. scoped domain).
# Changes from previous version
This third patch series reduces the amount of domain information
records: instead of creating a record for a domain hierarchy, only the
domain that denied the request is logged, which is enough.
The log format for domain information don't include the parent anymore
but the creation time instead, which is useful to know how old a domain
is relative to a first denial. We also now use hexadecimal numbers for
domain IDs.
Another major addition of this patch series are the new tests. The new
syscall flag is tested, and all the ptrace tests are extended to check
the source of the denials (e.g. Landlock or Yama). This greatly improve
test consistency and I plan to extend all Landlock tests with these
audit checks.
The sandboxer sample is also updated to not generate logs by default.
# Design
Log records are created for any denied actions caused by a Landlock
policy, which means that a well-sandboxed applications should not log
anything except for unattended access requests that might be the result
of attacks or bugs.
However, sandbox tools creating restricted environments could lead to
abundant log entries because the sandboxed processes may not be aware of
the related restrictions. To avoid log spam, the
landlock_restrict_self(2) syscall gets a new
LANDLOCK_RESTRICT_SELF_LOGLESS flag to not log denials related to this
specific domain. Except for well-understood exceptions, this flag
should not be set. Indeed, applications sandboxing themselves should
only try to bypass their own sandbox if they are compromised, which
should ring a bell thanks to log events.
When an action is denied, the related Landlock domain ID is specified.
If this domain was not previously described in a log record, one is
created. This record contains the domain ID, its creation time, and
informations about the process that enforced the restriction (at the
time of the call to landlock_restrict_self): PID, UID, executable path,
and name (comm).
This new approach also brings building blocks for an upcoming
unprivileged introspection interface. The unique Landlock IDs will be
useful to tie audit log entries to running processes, and to get
properties of the related Landlock domains. This will replace the
previously logged ruleset properties.
# Samples
Here are two examples of log events:
$ LL_FS_RO=/ LL_FS_RW=/ LL_SCOPED=s LL_FORCE_LOG=1 ./sandboxer kill 1
type=UNKNOWN[1423] msg=audit(1732186800.268:30): domain=1a6fdc66f blockers=scope_signal opid=1 ocomm="systemd"
type=UNKNOWN[1424] msg=audit(1732186800.268:30): domain=1a6fdc66f creation=1732186800.264 pid=286 uid=0 exe="/root/sandboxer" comm="sandboxer"UID="root"
type=SYSCALL msg=audit(1732186800.268:30): arch=c000003e syscall=62 success=no exit=-1 [..] ppid=272 pid=286 auid=0 uid=0 gid=0 [...] comm="kill" [...]
type=PROCTITLE msg=audit(1732186800.268:30): proctitle=6B696C6C0031
type=UNKNOWN[1425] msg=audit(1732186800.324:31): domain=1a6fdc66f
$ LL_FS_RO=/ LL_FS_RW=/tmp LL_FORCE_LOG=1 ./sandboxer sh -c "echo > /etc/passwd"
type=UNKNOWN[1423] msg=audit(1732186800.221:33): domain=1a6fdc679 blockers=fs_write_file path="/dev/tty" dev="devtmpfs" ino=9
type=UNKNOWN[1424] msg=audit(1732186800.221:33): domain=1a6fdc679 creation=1732186800.221 pid=289 uid=0 exe="/root/sandboxer" comm="sandboxer"UID="root"
type=SYSCALL msg=audit(1732186800.221:33): arch=c000003e syscall=257 success=no exit=-13 [...] ppid=272 pid=289 auid=0 uid=0 gid=0 [...] comm="sh" [...]
type=PROCTITLE msg=audit(1732186800.221:33): proctitle=7368002D63006563686F203E202F6574632F706173737764
type=UNKNOWN[1423] msg=audit(1732186800.221:34): domain=1a6fdc679 blockers=fs_write_file path="/etc/passwd" dev="vda2" ino=143821
type=SYSCALL msg=audit(1732186800.221:34): arch=c000003e syscall=257 success=no exit=-13 [...] ppid=272 pid=289 auid=0 uid=0 gid=0 [...] comm="sh" [...]
type=PROCTITLE msg=audit(1732186800.221:34): proctitle=7368002D63006563686F203E202F6574632F706173737764
type=UNKNOWN[1425] msg=audit(1732186800.261:35): domain=1a6fdc679
# Future changes
It would be interesting to enhance audit with the ability to filter on
the executable path that created a sandbox, or to filter on a Landlock
domain ID.
Previous versions:
v1: https://lore.kernel.org/r/20241022161009.982584-1-mic@digikod.net
v1: https://lore.kernel.org/r/20230921061641.273654-1-mic@digikod.net
Regards,
Mickaël Salaün (23):
lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are
set
lsm: Add audit_log_lsm_data() helper
landlock: Factor out check_access_path()
landlock: Add unique ID generator
landlock: Move access types
landlock: Simplify initially denied access rights
landlock: Move domain hierarchy management
landlock: Log ptrace denials
audit: Add a new audit_get_ctime() helper
landlock: Log domain properties and release
landlock: Log mount-related denials
landlock: Align partial refer access checks with final ones
selftests/landlock: Add test to check partial access in a mount tree
landlock: Optimize file path walks and prepare for audit support
landlock: Log file-related denials
landlock: Log truncate and ioctl denials
landlock: Log TCP bind and connect denials
landlock: Log scoped denials
landlock: Control log events with LANDLOCK_RESTRICT_SELF_LOGLESS
samples/landlock: Do not log denials from the sandboxer by default
selftests/landlock: Extend tests for landlock_restrict_self()'s flags
selftests/landlock: Add tests for audit
selftests/landlock: Add audit tests for ptrace
Documentation/userspace-api/landlock.rst | 2 +-
include/linux/audit.h | 8 +
include/linux/lsm_audit.h | 22 +
include/uapi/linux/audit.h | 5 +-
include/uapi/linux/landlock.h | 14 +
kernel/auditsc.c | 21 +-
samples/landlock/sandboxer.c | 35 +-
security/Kconfig | 5 +
security/Makefile | 2 +-
security/landlock/.kunitconfig | 2 +
security/landlock/Makefile | 2 +
security/landlock/access.h | 100 ++++
security/landlock/audit.c | 495 ++++++++++++++++++
security/landlock/audit.h | 76 +++
security/landlock/domain.c | 195 +++++++
security/landlock/domain.h | 117 +++++
security/landlock/fs.c | 279 +++++++---
security/landlock/fs.h | 10 +
security/landlock/id.c | 242 +++++++++
security/landlock/id.h | 25 +
security/landlock/net.c | 51 +-
security/landlock/ruleset.c | 35 +-
security/landlock/ruleset.h | 96 ++--
security/landlock/setup.c | 2 +
security/landlock/syscalls.c | 26 +-
security/landlock/task.c | 150 +++++-
security/lsm_audit.c | 27 +-
tools/testing/kunit/configs/all_tests.config | 2 +
tools/testing/selftests/landlock/audit.h | 308 +++++++++++
tools/testing/selftests/landlock/audit_test.c | 168 ++++++
tools/testing/selftests/landlock/base_test.c | 18 +-
tools/testing/selftests/landlock/common.h | 2 +
tools/testing/selftests/landlock/config | 1 +
tools/testing/selftests/landlock/fs_test.c | 54 +-
.../testing/selftests/landlock/ptrace_test.c | 62 ++-
35 files changed, 2454 insertions(+), 205 deletions(-)
create mode 100644 security/landlock/access.h
create mode 100644 security/landlock/audit.c
create mode 100644 security/landlock/audit.h
create mode 100644 security/landlock/domain.c
create mode 100644 security/landlock/domain.h
create mode 100644 security/landlock/id.c
create mode 100644 security/landlock/id.h
create mode 100644 tools/testing/selftests/landlock/audit.h
create mode 100644 tools/testing/selftests/landlock/audit_test.c
base-commit: adc218676eef25575469234709c2d87185ca223a
--
2.47.0
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH v3 01/23] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2025-01-04 16:47 ` [PATCH v3 1/23] " Paul Moore
2024-11-22 14:33 ` [PATCH v3 02/23] lsm: Add audit_log_lsm_data() helper Mickaël Salaün
` (22 subsequent siblings)
23 siblings, 1 reply; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
When CONFIG_AUDIT is set, its CONFIG_NET dependency is also set, and the
dev_get_by_index and init_net symbols (used by dump_common_audit_data)
are found by the linker. dump_common_audit_data() should then failed to
build when CONFIG_NET is not set. However, because the compiler is
smart, it knows that audit_log_start() always return NULL when
!CONFIG_AUDIT, and it doesn't build the body of common_lsm_audit(). As
a side effect, dump_common_audit_data() is not built and the linker
doesn't error out because of missing symbols.
Let's only build lsm_audit.o when CONFIG_SECURITY and CONFIG_AUDIT are
both set, which is checked with the new CONFIG_HAS_SECURITY_AUDIT.
ipv4_skb_to_auditdata() and ipv6_skb_to_auditdata() are only used by
Smack if CONFIG_AUDIT is set, so they don't need fake implementations.
Because common_lsm_audit() is used in multiple places without
CONFIG_AUDIT checks, add a fake implementation.
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: James Morris <jmorris@namei.org>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Serge E. Hallyn <serge@hallyn.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-2-mic@digikod.net
---
Changes since v2:
- Add CONFIG_HAS_SECURITY_AUDIT to fix the build with AUDIT &&
!SECURITY, reported by Guenter Roeck.
---
include/linux/lsm_audit.h | 14 ++++++++++++++
security/Kconfig | 5 +++++
security/Makefile | 2 +-
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 97a8b21eb033..c2b01380262c 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -116,14 +116,28 @@ struct common_audit_data {
#define v4info fam.v4
#define v6info fam.v6
+#ifdef CONFIG_AUDIT
+
int ipv4_skb_to_auditdata(struct sk_buff *skb,
struct common_audit_data *ad, u8 *proto);
+#if IS_ENABLED(CONFIG_IPV6)
int ipv6_skb_to_auditdata(struct sk_buff *skb,
struct common_audit_data *ad, u8 *proto);
+#endif /* IS_ENABLED(CONFIG_IPV6) */
void common_lsm_audit(struct common_audit_data *a,
void (*pre_audit)(struct audit_buffer *, void *),
void (*post_audit)(struct audit_buffer *, void *));
+#else /* CONFIG_AUDIT */
+
+static inline void common_lsm_audit(struct common_audit_data *a,
+ void (*pre_audit)(struct audit_buffer *, void *),
+ void (*post_audit)(struct audit_buffer *, void *))
+{
+}
+
+#endif /* CONFIG_AUDIT */
+
#endif
diff --git a/security/Kconfig b/security/Kconfig
index 28e685f53bd1..f10dbf15c294 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -64,6 +64,11 @@ config SECURITY
If you are unsure how to answer this question, answer N.
+config HAS_SECURITY_AUDIT
+ def_bool y
+ depends on AUDIT
+ depends on SECURITY
+
config SECURITYFS
bool "Enable the securityfs filesystem"
help
diff --git a/security/Makefile b/security/Makefile
index cc0982214b84..22ff4c8bd8ce 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -15,7 +15,7 @@ obj-$(CONFIG_SECURITY) += security.o
obj-$(CONFIG_SECURITYFS) += inode.o
obj-$(CONFIG_SECURITY_SELINUX) += selinux/
obj-$(CONFIG_SECURITY_SMACK) += smack/
-obj-$(CONFIG_SECURITY) += lsm_audit.o
+obj-$(CONFIG_HAS_SECURITY_AUDIT) += lsm_audit.o
obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 02/23] lsm: Add audit_log_lsm_data() helper
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 01/23] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2025-01-05 1:23 ` [PATCH v3 2/23] " Paul Moore
2024-11-22 14:33 ` [PATCH v3 03/23] landlock: Factor out check_access_path() Mickaël Salaün
` (21 subsequent siblings)
23 siblings, 1 reply; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Extract code from dump_common_audit_data() into the audit_log_lsm_data()
helper. This helps reuse common LSM audit data while not abusing
AUDIT_AVC records because of the common_lsm_audit() helper.
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: James Morris <jmorris@namei.org>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Serge E. Hallyn <serge@hallyn.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-3-mic@digikod.net
---
Changes since v1:
- Fix commit message (spotted by Paul).
- Constify dump_common_audit_data()'s and audit_log_lsm_data()'s "a"
argument.
- Fix build without CONFIG_NET: see previous patch.
---
include/linux/lsm_audit.h | 8 ++++++++
security/lsm_audit.c | 27 ++++++++++++++++++---------
2 files changed, 26 insertions(+), 9 deletions(-)
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index c2b01380262c..b62769a7c5fa 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -130,6 +130,9 @@ void common_lsm_audit(struct common_audit_data *a,
void (*pre_audit)(struct audit_buffer *, void *),
void (*post_audit)(struct audit_buffer *, void *));
+void audit_log_lsm_data(struct audit_buffer *ab,
+ const struct common_audit_data *a);
+
#else /* CONFIG_AUDIT */
static inline void common_lsm_audit(struct common_audit_data *a,
@@ -138,6 +141,11 @@ static inline void common_lsm_audit(struct common_audit_data *a,
{
}
+static inline void audit_log_lsm_data(struct audit_buffer *ab,
+ const struct common_audit_data *a)
+{
+}
+
#endif /* CONFIG_AUDIT */
#endif
diff --git a/security/lsm_audit.c b/security/lsm_audit.c
index 849e832719e2..de29ce8ff708 100644
--- a/security/lsm_audit.c
+++ b/security/lsm_audit.c
@@ -189,16 +189,13 @@ static inline void print_ipv4_addr(struct audit_buffer *ab, __be32 addr,
}
/**
- * dump_common_audit_data - helper to dump common audit data
+ * audit_log_lsm_data - helper to log common LSM audit data
* @ab : the audit buffer
* @a : common audit data
- *
*/
-static void dump_common_audit_data(struct audit_buffer *ab,
- struct common_audit_data *a)
+void audit_log_lsm_data(struct audit_buffer *ab,
+ const struct common_audit_data *a)
{
- char comm[sizeof(current->comm)];
-
/*
* To keep stack sizes in check force programmers to notice if they
* start making this union too large! See struct lsm_network_audit
@@ -206,9 +203,6 @@ static void dump_common_audit_data(struct audit_buffer *ab,
*/
BUILD_BUG_ON(sizeof(a->u) > sizeof(void *)*2);
- audit_log_format(ab, " pid=%d comm=", task_tgid_nr(current));
- audit_log_untrustedstring(ab, memcpy(comm, current->comm, sizeof(comm)));
-
switch (a->type) {
case LSM_AUDIT_DATA_NONE:
return;
@@ -428,6 +422,21 @@ static void dump_common_audit_data(struct audit_buffer *ab,
} /* switch (a->type) */
}
+/**
+ * dump_common_audit_data - helper to dump common audit data
+ * @ab : the audit buffer
+ * @a : common audit data
+ */
+static void dump_common_audit_data(struct audit_buffer *ab,
+ const struct common_audit_data *a)
+{
+ char comm[sizeof(current->comm)];
+
+ audit_log_format(ab, " pid=%d comm=", task_tgid_nr(current));
+ audit_log_untrustedstring(ab, memcpy(comm, current->comm, sizeof(comm)));
+ audit_log_lsm_data(ab, a);
+}
+
/**
* common_lsm_audit - generic LSM auditing function
* @a: auxiliary audit data
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 03/23] landlock: Factor out check_access_path()
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 01/23] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 02/23] lsm: Add audit_log_lsm_data() helper Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 04/23] landlock: Add unique ID generator Mickaël Salaün
` (20 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Merge check_access_path() into current_check_access_path() and make
hook_path_mknod() use it.
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-4-mic@digikod.net
---
Changes since v1:
- Rebased on the TCP patch series.
- Remove inlining removal which was merged.
---
security/landlock/fs.c | 32 +++++++++++---------------------
1 file changed, 11 insertions(+), 21 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index e31b97a9f175..d911c924843f 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -908,28 +908,22 @@ static bool is_access_to_paths_allowed(
return allowed_parent1 && allowed_parent2;
}
-static int check_access_path(const struct landlock_ruleset *const domain,
- const struct path *const path,
- access_mask_t access_request)
-{
- layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
-
- access_request = landlock_init_layer_masks(
- domain, access_request, &layer_masks, LANDLOCK_KEY_INODE);
- if (is_access_to_paths_allowed(domain, path, access_request,
- &layer_masks, NULL, 0, NULL, NULL))
- return 0;
- return -EACCES;
-}
-
static int current_check_access_path(const struct path *const path,
- const access_mask_t access_request)
+ access_mask_t access_request)
{
const struct landlock_ruleset *const dom = get_current_fs_domain();
+ layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
if (!dom)
return 0;
- return check_access_path(dom, path, access_request);
+
+ access_request = landlock_init_layer_masks(
+ dom, access_request, &layer_masks, LANDLOCK_KEY_INODE);
+ if (is_access_to_paths_allowed(dom, path, access_request, &layer_masks,
+ NULL, 0, NULL, NULL))
+ return 0;
+
+ return -EACCES;
}
static access_mask_t get_mode_access(const umode_t mode)
@@ -1414,11 +1408,7 @@ static int hook_path_mknod(const struct path *const dir,
struct dentry *const dentry, const umode_t mode,
const unsigned int dev)
{
- const struct landlock_ruleset *const dom = get_current_fs_domain();
-
- if (!dom)
- return 0;
- return check_access_path(dom, dir, get_mode_access(mode));
+ return current_check_access_path(dir, get_mode_access(mode));
}
static int hook_path_symlink(const struct path *const dir,
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 04/23] landlock: Add unique ID generator
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (2 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 03/23] landlock: Factor out check_access_path() Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 05/23] landlock: Move access types Mickaël Salaün
` (19 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Landlock IDs can be generated to uniquely identify Landlock objects.
For now, only Landlock domains get an ID at creation time. These IDs
map to immutable domain hierarchies.
Landlock IDs have important properties:
- They are unique during the lifetime of the running system thanks to
the 64-bit values: at worse, 2^60 - 2*2^32 useful IDs.
- They are always greater than 2^32 and must then be stored in 64-bit
integer types.
- The initial ID (at boot time) is randomly picked between 2^32 and
2^33, which limits collisions in logs between different boots.
- IDs are sequential, which enables users to order them.
- IDs may not be consecutive but increase with a random 2^4 step, which
limits side channels.
Such IDs can be exposed to unprivileged processes, even if it is not the
case with this audit patch series. The domain IDs will be useful for
user space to identify sandboxes and get their properties.
These Landlock IDs are more robust that other absolute kernel IDs such
as pipe's inodes which rely on a shared global counter.
For checkpoint/restore features (i.e. CRIU), we could easily implement a
privileged interface (e.g. sysfs) to set the next ID counter.
IDR/IDA are not used because we only need a bijection from Landlock
objects to Landlock IDs, and we must not recycle IDs. This enables us
to identify all Landlock objects during the lifetime of the system (e.g.
in logs), but not to access an object from an ID nor know if an ID is
assigned. Using a counter is simpler, it scales (i.e. avoids growing
memory footprint), and it does not require locking. We'll use proper
file descriptors (with IDs used as inode numbers) to access Landlock
objects.
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-5-mic@digikod.net
---
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 | 242 +++++++++++++++++++
security/landlock/id.h | 25 ++
security/landlock/setup.c | 2 +
tools/testing/kunit/configs/all_tests.config | 2 +
6 files changed, 275 insertions(+)
create mode 100644 security/landlock/id.c
create mode 100644 security/landlock/id.h
diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig
index 03e119466604..f9423f01ac5b 100644
--- a/security/landlock/.kunitconfig
+++ b/security/landlock/.kunitconfig
@@ -1,4 +1,6 @@
+CONFIG_AUDIT=y
CONFIG_KUNIT=y
+CONFIG_NET=y
CONFIG_SECURITY=y
CONFIG_SECURITY_LANDLOCK=y
CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index b4538b7cf7d2..e1777abbc413 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -4,3 +4,5 @@ landlock-y := setup.o syscalls.o object.o ruleset.o \
cred.o task.o fs.o
landlock-$(CONFIG_INET) += net.o
+
+landlock-$(CONFIG_AUDIT) += id.o
diff --git a/security/landlock/id.c b/security/landlock/id.c
new file mode 100644
index 000000000000..0ae5fee28b67
--- /dev/null
+++ b/security/landlock/id.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Unique identification number generator
+ *
+ * Copyright © 2024 Microsoft Corporation
+ */
+
+#include <kunit/test.h>
+#include <linux/atomic.h>
+#include <linux/random.h>
+#include <linux/spinlock.h>
+
+#include "common.h"
+#include "id.h"
+
+#define COUNTER_PRE_INIT 0
+
+static atomic64_t next_id = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+static void __init init_id(atomic64_t *const counter, const u32 random_32bits)
+{
+ u64 init;
+
+ /*
+ * Ensures sure 64-bit values are always used by user space (or may
+ * fail with -EOVERFLOW), and makes this testable.
+ */
+ init = 1ULL << 32;
+
+ /*
+ * Makes a large (2^32) boot-time value to limit ID collision in logs
+ * from different boots, and to limit info leak about the number of
+ * initially (relative to the reader) created elements (e.g. domains).
+ */
+ init += random_32bits;
+
+ /* Sets first or ignores. This will be the first ID. */
+ atomic64_cmpxchg(counter, COUNTER_PRE_INIT, init);
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void __init test_init_min(struct kunit *const test)
+{
+ atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+ init_id(&counter, 0);
+ KUNIT_EXPECT_EQ(test, atomic64_read(&counter), 1ULL + U32_MAX);
+}
+
+static void __init test_init_max(struct kunit *const test)
+{
+ atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+ init_id(&counter, ~0);
+ KUNIT_EXPECT_EQ(test, atomic64_read(&counter), 1 + (2ULL * U32_MAX));
+}
+
+static void __init test_init_once(struct kunit *const test)
+{
+ const u64 first_init = 1ULL + U32_MAX;
+ atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+ init_id(&counter, 0);
+ KUNIT_EXPECT_EQ(test, atomic64_read(&counter), first_init);
+
+ init_id(&counter, ~0);
+ KUNIT_EXPECT_EQ(test, atomic64_read(&counter), first_init);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+void __init landlock_init_id(void)
+{
+ return init_id(&next_id, get_random_u32());
+}
+
+/*
+ * It's not worth it to try to hide the monotonic counter because it can still
+ * be inferred (with N counter ranges), and if we are allowed to read the inode
+ * number we should also be allowed to read the time creation anyway, and it
+ * can be handy to store and sort domain IDs for user space.
+ *
+ * Returns the value of next_id and increment it to let some space for the next
+ * one.
+ */
+static u64 get_id(size_t number_of_ids, atomic64_t *const counter,
+ u8 random_4bits)
+{
+ u64 id, step;
+
+ /*
+ * We should return at least 1 ID, and we may need a set of consecutive
+ * ones (e.g. to generate a set of inodes).
+ */
+ if (WARN_ON_ONCE(number_of_ids <= 0))
+ number_of_ids = 1;
+
+ /*
+ * Blurs the next ID guess with 1/16 ratio. We get 2^(64 - 4) -
+ * (2 * 2^32), so a bit less than 2^60 available IDs, which should be
+ * much more than enough considering the number of CPU cycles required
+ * to get a new ID (e.g. a full landlock_restrict_self() call), and the
+ * cost of draining all available IDs during the system's uptime.
+ */
+ random_4bits = random_4bits % (1 << 4);
+ step = number_of_ids + random_4bits;
+
+ /* It is safe to cast a signed atomic to an unsigned value. */
+ id = atomic64_fetch_add(step, counter);
+
+ /* Warns if landlock_init_id() was not called. */
+ WARN_ON_ONCE(id == COUNTER_PRE_INIT);
+ return id;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_range1_rand0(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id(1, &counter, 0), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id(get_random_u8(), &counter, get_random_u8()),
+ init + 1);
+}
+
+static void test_range1_rand1(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id(1, &counter, 1), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id(get_random_u8(), &counter, get_random_u8()),
+ init + 2);
+}
+
+static void test_range1_rand16(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id(1, &counter, 16), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id(get_random_u8(), &counter, get_random_u8()),
+ init + 1);
+}
+
+static void test_range2_rand0(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id(2, &counter, 0), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id(get_random_u8(), &counter, get_random_u8()),
+ init + 2);
+}
+
+static void test_range2_rand1(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id(2, &counter, 1), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id(get_random_u8(), &counter, get_random_u8()),
+ init + 3);
+}
+
+static void test_range2_rand2(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id(2, &counter, 2), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id(get_random_u8(), &counter, get_random_u8()),
+ init + 4);
+}
+
+static void test_range2_rand16(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id(2, &counter, 16), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id(get_random_u8(), &counter, get_random_u8()),
+ init + 2);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+u64 landlock_get_id(size_t number_of_ids)
+{
+ return get_id(number_of_ids, &next_id, get_random_u8());
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static struct kunit_case __refdata test_cases[] = {
+ /* clang-format off */
+ KUNIT_CASE(test_init_min),
+ KUNIT_CASE(test_init_max),
+ KUNIT_CASE(test_init_once),
+ KUNIT_CASE(test_range1_rand0),
+ KUNIT_CASE(test_range1_rand1),
+ KUNIT_CASE(test_range1_rand16),
+ KUNIT_CASE(test_range2_rand0),
+ KUNIT_CASE(test_range2_rand1),
+ KUNIT_CASE(test_range2_rand2),
+ KUNIT_CASE(test_range2_rand16),
+ {}
+ /* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+ .name = "landlock_id",
+ .test_cases = test_cases,
+};
+
+kunit_test_init_section_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/id.h b/security/landlock/id.h
new file mode 100644
index 000000000000..689ba7607472
--- /dev/null
+++ b/security/landlock/id.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Unique identification number generator
+ *
+ * Copyright © 2024 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_ID_H
+#define _SECURITY_LANDLOCK_ID_H
+
+#ifdef CONFIG_AUDIT
+
+void __init landlock_init_id(void);
+
+u64 landlock_get_id(size_t number_of_ids);
+
+#else /* CONFIG_AUDIT */
+
+static inline void __init landlock_init_id(void)
+{
+}
+
+#endif /* CONFIG_AUDIT */
+
+#endif /* _SECURITY_LANDLOCK_ID_H */
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index 28519a45b11f..d297083efcb1 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -13,6 +13,7 @@
#include "common.h"
#include "cred.h"
#include "fs.h"
+#include "id.h"
#include "net.h"
#include "setup.h"
#include "task.h"
@@ -33,6 +34,7 @@ const struct lsm_id landlock_lsmid = {
static int __init landlock_init(void)
{
+ landlock_init_id();
landlock_add_cred_hooks();
landlock_add_task_hooks();
landlock_add_fs_hooks();
diff --git a/tools/testing/kunit/configs/all_tests.config b/tools/testing/kunit/configs/all_tests.config
index b3b00269a52a..ea1f824ae70f 100644
--- a/tools/testing/kunit/configs/all_tests.config
+++ b/tools/testing/kunit/configs/all_tests.config
@@ -44,6 +44,8 @@ CONFIG_DAMON_DBGFS_DEPRECATED=y
CONFIG_REGMAP_BUILD=y
+CONFIG_AUDIT=y
+
CONFIG_SECURITY=y
CONFIG_SECURITY_APPARMOR=y
CONFIG_SECURITY_LANDLOCK=y
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 05/23] landlock: Move access types
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (3 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 04/23] landlock: Add unique ID generator Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 06/23] landlock: Simplify initially denied access rights Mickaël Salaün
` (18 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Move LANDLOCK_ACCESS_FS_INITIALLY_DENIED, access_mask_t, struct
access_mask, and struct access_masks_all to a dedicated access.h file.
Rename LANDLOCK_ACCESS_FS_INITIALLY_DENIED to
_LANDLOCK_ACCESS_FS_INITIALLY_DENIED to make it clear that it's not part
of UAPI. Add some newlines when appropriate.
This file will be extended with following commits, and it will help to
avoid dependency loops.
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-6-mic@digikod.net
---
Changes since v2:
- Rebased on the (now merged) masks improvement patches.
- Move ACCESS_FS_OPTIONAL to a following patch introducing deny_masks_t,
spotted by Francis Laniel.
- Move and rename LANDLOCK_ACCESS_FS_INITIALLY_DENIED to
_LANDLOCK_ACCESS_FS_INITIALLY_DENIED.
Changes since v1:
- New patch
---
security/landlock/access.h | 62 +++++++++++++++++++++++++++++++++++++
security/landlock/fs.c | 3 +-
security/landlock/fs.h | 1 +
security/landlock/ruleset.c | 1 +
security/landlock/ruleset.h | 47 ++--------------------------
5 files changed, 68 insertions(+), 46 deletions(-)
create mode 100644 security/landlock/access.h
diff --git a/security/landlock/access.h b/security/landlock/access.h
new file mode 100644
index 000000000000..a64694287d2c
--- /dev/null
+++ b/security/landlock/access.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Access types and helpers
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2024 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_ACCESS_H
+#define _SECURITY_LANDLOCK_ACCESS_H
+
+#include <linux/bitops.h>
+#include <linux/build_bug.h>
+#include <linux/kernel.h>
+#include <uapi/linux/landlock.h>
+
+#include "limits.h"
+
+/*
+ * All access rights that are denied by default whether they are handled or not
+ * by a ruleset/layer. This must be ORed with all ruleset->access_masks[]
+ * entries when we need to get the absolute handled access masks.
+ */
+/* clang-format off */
+#define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
+ LANDLOCK_ACCESS_FS_REFER)
+/* clang-format on */
+
+typedef u16 access_mask_t;
+
+/* Makes sure all filesystem access rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
+/* Makes sure all network access rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
+/* Makes sure all scoped rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
+/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
+static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
+
+/* Ruleset access masks. */
+struct access_masks {
+ access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
+ access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
+ access_mask_t scope : LANDLOCK_NUM_SCOPE;
+};
+
+union access_masks_all {
+ struct access_masks masks;
+ u32 all;
+};
+
+/* Makes sure all fields are covered. */
+static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
+ sizeof(typeof_member(union access_masks_all, all)));
+
+typedef u16 layer_mask_t;
+
+/* Makes sure all layers can be checked. */
+static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
+
+#endif /* _SECURITY_LANDLOCK_ACCESS_H */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index d911c924843f..3da5f1945158 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -36,6 +36,7 @@
#include <uapi/linux/fiemap.h>
#include <uapi/linux/landlock.h>
+#include "access.h"
#include "common.h"
#include "cred.h"
#include "fs.h"
@@ -393,7 +394,7 @@ get_handled_fs_accesses(const struct landlock_ruleset *const domain)
{
/* Handles all initially denied by default access rights. */
return landlock_union_access_masks(domain).fs |
- LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+ _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
}
static const struct access_masks any_fs = {
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index 1487e1f023a1..d445f411c26a 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/rcupdate.h>
+#include "access.h"
#include "ruleset.h"
#include "setup.h"
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index a93bdbf52fff..cae69f2f01d9 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -20,6 +20,7 @@
#include <linux/spinlock.h>
#include <linux/workqueue.h>
+#include "access.h"
#include "limits.h"
#include "object.h"
#include "ruleset.h"
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 631e24d4ffe9..2f29b9f40392 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -9,58 +9,15 @@
#ifndef _SECURITY_LANDLOCK_RULESET_H
#define _SECURITY_LANDLOCK_RULESET_H
-#include <linux/bitops.h>
-#include <linux/build_bug.h>
-#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/rbtree.h>
#include <linux/refcount.h>
#include <linux/workqueue.h>
-#include <uapi/linux/landlock.h>
+#include "access.h"
#include "limits.h"
#include "object.h"
-/*
- * All access rights that are denied by default whether they are handled or not
- * by a ruleset/layer. This must be ORed with all ruleset->access_masks[]
- * entries when we need to get the absolute handled access masks.
- */
-/* clang-format off */
-#define LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
- LANDLOCK_ACCESS_FS_REFER)
-/* clang-format on */
-
-typedef u16 access_mask_t;
-/* Makes sure all filesystem access rights can be stored. */
-static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
-/* Makes sure all network access rights can be stored. */
-static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
-/* Makes sure all scoped rights can be stored. */
-static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
-/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
-static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
-
-/* Ruleset access masks. */
-struct access_masks {
- access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
- access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
- access_mask_t scope : LANDLOCK_NUM_SCOPE;
-};
-
-union access_masks_all {
- struct access_masks masks;
- u32 all;
-};
-
-/* Makes sure all fields are covered. */
-static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
- sizeof(typeof_member(union access_masks_all, all)));
-
-typedef u16 layer_mask_t;
-/* Makes sure all layers can be checked. */
-static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
-
/**
* struct landlock_layer - Access rights for a given layer
*/
@@ -366,7 +323,7 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
{
/* Handles all initially denied by default access rights. */
return ruleset->access_masks[layer_level].fs |
- LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+ _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
}
static inline access_mask_t
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 06/23] landlock: Simplify initially denied access rights
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (4 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 05/23] landlock: Move access types Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 07/23] landlock: Move domain hierarchy management Mickaël Salaün
` (17 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Upgrade domain's handled access masks when creating a domain from a
ruleset, instead of converting them at runtime. This is more consistent
and helps with audit support.
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-7-mic@digikod.net
---
Changes since v2:
- New patch.
---
security/landlock/access.h | 17 ++++++++++++++++-
security/landlock/fs.c | 10 +---------
security/landlock/ruleset.c | 3 ++-
3 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/security/landlock/access.h b/security/landlock/access.h
index a64694287d2c..1e302775cc23 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -20,7 +20,8 @@
/*
* All access rights that are denied by default whether they are handled or not
* by a ruleset/layer. This must be ORed with all ruleset->access_masks[]
- * entries when we need to get the absolute handled access masks.
+ * entries when we need to get the absolute handled access masks, see
+ * landlock_upgrade_handled_access_masks().
*/
/* clang-format off */
#define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
@@ -59,4 +60,18 @@ typedef u16 layer_mask_t;
/* Makes sure all layers can be checked. */
static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
+/* Upgrades with all initially denied by default access rights. */
+static inline struct access_masks
+landlock_upgrade_handled_access_masks(struct access_masks access_masks)
+{
+ /*
+ * All access rights that are denied by default whether they are
+ * explicitly handled or not.
+ */
+ if (access_masks.fs)
+ access_masks.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+
+ return access_masks;
+}
+
#endif /* _SECURITY_LANDLOCK_ACCESS_H */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 3da5f1945158..9779170d9199 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -389,14 +389,6 @@ static bool is_nouser_or_private(const struct dentry *dentry)
unlikely(IS_PRIVATE(d_backing_inode(dentry))));
}
-static access_mask_t
-get_handled_fs_accesses(const struct landlock_ruleset *const domain)
-{
- /* Handles all initially denied by default access rights. */
- return landlock_union_access_masks(domain).fs |
- _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
-}
-
static const struct access_masks any_fs = {
.fs = ~0,
};
@@ -788,7 +780,7 @@ static bool is_access_to_paths_allowed(
* a superset of the meaningful requested accesses).
*/
access_masked_parent1 = access_masked_parent2 =
- get_handled_fs_accesses(domain);
+ landlock_union_access_masks(domain).fs;
is_dom_check = true;
} else {
if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index cae69f2f01d9..dbc528f5f3b7 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -385,7 +385,8 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
err = -EINVAL;
goto out_unlock;
}
- dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
+ dst->access_masks[dst->num_layers - 1] =
+ landlock_upgrade_handled_access_masks(src->access_masks[0]);
/* Merges the @src inode tree. */
err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 07/23] landlock: Move domain hierarchy management
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (5 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 06/23] landlock: Simplify initially denied access rights Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 08/23] landlock: Log ptrace denials Mickaël Salaün
` (16 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Create a new domain.h file containing the struct landlock_hierarchy
definition and helpers. This type will grow with audit support. This
also prepares for a new domain type.
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-8-mic@digikod.net
---
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 000000000000..015d61fd81ec
--- /dev/null
+++ b/security/landlock/domain.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Domain management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_DOMAIN_H
+#define _SECURITY_LANDLOCK_DOMAIN_H
+
+#include <linux/mm.h>
+#include <linux/refcount.h>
+
+/**
+ * struct landlock_hierarchy - Node in a domain hierarchy
+ */
+struct landlock_hierarchy {
+ /**
+ * @parent: Pointer to the parent node, or NULL if it is a root
+ * Landlock domain.
+ */
+ struct landlock_hierarchy *parent;
+ /**
+ * @usage: Number of potential children domains plus their parent
+ * domain.
+ */
+ refcount_t usage;
+};
+
+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 dbc528f5f3b7..4b3dfa3e6fcb 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -21,6 +21,7 @@
#include <linux/workqueue.h>
#include "access.h"
+#include "domain.h"
#include "limits.h"
#include "object.h"
#include "ruleset.h"
@@ -305,22 +306,6 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers));
}
-static void get_hierarchy(struct landlock_hierarchy *const hierarchy)
-{
- if (hierarchy)
- refcount_inc(&hierarchy->usage);
-}
-
-static void put_hierarchy(struct landlock_hierarchy *hierarchy)
-{
- while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) {
- const struct landlock_hierarchy *const freeme = hierarchy;
-
- hierarchy = hierarchy->parent;
- kfree(freeme);
- }
-}
-
static int merge_tree(struct landlock_ruleset *const dst,
struct landlock_ruleset *const src,
const enum landlock_key_type key_type)
@@ -475,7 +460,7 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
err = -EINVAL;
goto out_unlock;
}
- get_hierarchy(parent->hierarchy);
+ landlock_get_hierarchy(parent->hierarchy);
child->hierarchy->parent = parent->hierarchy;
out_unlock:
@@ -499,7 +484,7 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
free_rule(freeme, LANDLOCK_KEY_NET_PORT);
#endif /* IS_ENABLED(CONFIG_INET) */
- put_hierarchy(ruleset->hierarchy);
+ landlock_put_hierarchy(ruleset->hierarchy);
kfree(ruleset);
}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 2f29b9f40392..39169b6860e3 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -15,6 +15,7 @@
#include <linux/workqueue.h>
#include "access.h"
+#include "domain.h"
#include "limits.h"
#include "object.h"
@@ -106,22 +107,6 @@ struct landlock_rule {
struct landlock_layer layers[] __counted_by(num_layers);
};
-/**
- * struct landlock_hierarchy - Node in a ruleset hierarchy
- */
-struct landlock_hierarchy {
- /**
- * @parent: Pointer to the parent node, or NULL if it is a root
- * Landlock domain.
- */
- struct landlock_hierarchy *parent;
- /**
- * @usage: Number of potential children domains plus their parent
- * domain.
- */
- refcount_t usage;
-};
-
/**
* struct landlock_ruleset - Landlock ruleset
*
diff --git a/security/landlock/task.c b/security/landlock/task.c
index dc7dab78392e..98894ad1abc7 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -18,6 +18,7 @@
#include "common.h"
#include "cred.h"
+#include "domain.h"
#include "fs.h"
#include "ruleset.h"
#include "setup.h"
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 08/23] landlock: Log ptrace denials
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (6 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 07/23] landlock: Move domain hierarchy management Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-12-20 14:36 ` Francis Laniel
2025-01-05 1:23 ` [PATCH v3 8/23] " Paul Moore
2024-11-22 14:33 ` [PATCH v3 09/23] audit: Add a new audit_get_ctime() helper Mickaël Salaün
` (15 subsequent siblings)
23 siblings, 2 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Add audit support to ptrace_access_check and ptrace_traceme hooks.
Add a new AUDIT_LANDLOCK_DENY record type dedicated to any Landlock
denials.
Log the domain ID restricting the action, the domain's blockers that are
missing to allow the requested access, and the target task.
The blockers are implicit restrictions (e.g. ptrace), or explicit access
rights (e.g. filesystem), or explicit scopes (e.g. signal).
For the ptrace_access_check case, we log the current/parent domain and
the child task. For the ptrace_traceme case, we log the parent domain
and the parent task. Indeed, the requester is the current task, but the
action would be performed by the parent task.
The quick return for non-landlocked tasks is moved from task_ptrace() to
each LSM hooks.
Audit event sample:
type=LL_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-9-mic@digikod.net
---
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.
Changes since v1:
- Move most audit code to this patch.
- Rebase on the TCP patch series.
- Don't log missing access right: simplify and make it generic for rule
types.
- Don't log errno and then don't wrap the error with
landlock_log_request(), as suggested by Jeff.
- Add a WARN_ON_ONCE() check to never dereference null pointers.
- Only log when audit is enabled.
- Don't log task's PID/TID with log_task() because it would be redundant
with the SYSCALL record.
- Move the "op" in front and rename "domain" to "denying_domain" to make
it more consistent with other entries.
- Don't update the request with the domain ID but add an helper to get
it from the layer masks (and in a following commit with a struct
file).
- Revamp get_domain_id_from_layer_masks() into
get_level_from_layer_masks().
- For ptrace_traceme, log the parent domain instead of the current one.
- Add documentation.
- Rename AUDIT_LANDLOCK_DENIAL to AUDIT_LANDLOCK_DENY.
- Only log the domain ID and the target task.
- Log "blockers", which are either implicit restrictions (e.g. ptrace)
or explicit access rights (e.g. filesystem), or scopes (e.g. signal).
- Don't log LSM hook names/operations.
- Pick an audit event ID folling the IPE ones.
- Add KUnit tests.
---
include/uapi/linux/audit.h | 3 +-
security/landlock/Makefile | 2 +-
security/landlock/audit.c | 137 ++++++++++++++++++++++++++++++++++++
security/landlock/audit.h | 52 ++++++++++++++
security/landlock/domain.c | 21 ++++++
security/landlock/domain.h | 17 +++++
security/landlock/ruleset.c | 3 +
security/landlock/task.c | 91 ++++++++++++++++++------
8 files changed, 302 insertions(+), 24 deletions(-)
create mode 100644 security/landlock/audit.c
create mode 100644 security/landlock/audit.h
create mode 100644 security/landlock/domain.c
diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index 75e21a135483..60c909c396c0 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -33,7 +33,7 @@
* 1100 - 1199 user space trusted application messages
* 1200 - 1299 messages internal to the audit daemon
* 1300 - 1399 audit event messages
- * 1400 - 1499 SE Linux use
+ * 1400 - 1499 access control messages
* 1500 - 1599 kernel LSPP events
* 1600 - 1699 kernel crypto events
* 1700 - 1799 kernel anomaly records
@@ -146,6 +146,7 @@
#define AUDIT_IPE_ACCESS 1420 /* IPE denial or grant */
#define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */
#define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */
+#define AUDIT_LANDLOCK_DENY 1423 /* Landlock denial */
#define AUDIT_FIRST_KERN_ANOM_MSG 1700
#define AUDIT_LAST_KERN_ANOM_MSG 1799
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index e1777abbc413..31512ee4b041 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -5,4 +5,4 @@ landlock-y := setup.o syscalls.o object.o ruleset.o \
landlock-$(CONFIG_INET) += net.o
-landlock-$(CONFIG_AUDIT) += id.o
+landlock-$(CONFIG_AUDIT) += id.o domain.o audit.o
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
new file mode 100644
index 000000000000..eab6b3a8a231
--- /dev/null
+++ b/security/landlock/audit.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Audit helpers
+ *
+ * Copyright © 2023-2024 Microsoft Corporation
+ */
+
+#include <kunit/test.h>
+#include <linux/audit.h>
+#include <linux/lsm_audit.h>
+
+#include "audit.h"
+#include "domain.h"
+#include "ruleset.h"
+
+static const char *get_blocker(const enum landlock_request_type type)
+{
+ switch (type) {
+ case LANDLOCK_REQUEST_PTRACE:
+ return "ptrace";
+ }
+
+ WARN_ON_ONCE(1);
+ return "unknown";
+}
+
+static void log_blockers(struct audit_buffer *const ab,
+ const enum landlock_request_type type)
+{
+ audit_log_format(ab, "%s", get_blocker(type));
+}
+
+static struct landlock_hierarchy *
+get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
+{
+ struct landlock_hierarchy *node = domain->hierarchy;
+ ssize_t i;
+
+ if (WARN_ON_ONCE(layer >= domain->num_layers))
+ return node;
+
+ for (i = domain->num_layers - 1; i > layer; i--) {
+ if (WARN_ON_ONCE(!node->parent))
+ break;
+
+ node = node->parent;
+ }
+
+ return node;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_hierarchy(struct kunit *const test)
+{
+ struct landlock_hierarchy dom0_node = {
+ .id = 10,
+ };
+ struct landlock_hierarchy dom1_node = {
+ .parent = &dom0_node,
+ .id = 20,
+ };
+ struct landlock_hierarchy dom2_node = {
+ .parent = &dom1_node,
+ .id = 30,
+ };
+ struct landlock_ruleset dom2 = {
+ .hierarchy = &dom2_node,
+ .num_layers = 3,
+ };
+
+ KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
+ KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
+ KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
+ KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+static bool is_valid_request(const struct landlock_request *const request)
+{
+ if (WARN_ON_ONCE(!request->layer_plus_one))
+ return false;
+
+ return true;
+}
+
+/**
+ * landlock_log_denial - Create audit records related to a denial
+ *
+ * @domain: The domain denying an action.
+ * @request: Detail of the user space request.
+ */
+void landlock_log_denial(const struct landlock_ruleset *const domain,
+ const struct landlock_request *const request)
+{
+ struct audit_buffer *ab;
+ struct landlock_hierarchy *youngest_denied;
+
+ if (WARN_ON_ONCE(!domain || !domain->hierarchy || !request))
+ return;
+
+ if (!is_valid_request(request))
+ return;
+
+ if (!audit_enabled)
+ return;
+
+ ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
+ AUDIT_LANDLOCK_DENY);
+ if (!ab)
+ return;
+
+ youngest_denied = get_hierarchy(domain, request->layer_plus_one - 1);
+ audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
+ log_blockers(ab, request->type);
+ audit_log_lsm_data(ab, &request->audit);
+ audit_log_end(ab);
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static struct kunit_case test_cases[] = {
+ /* clang-format off */
+ KUNIT_CASE(test_get_hierarchy),
+ {}
+ /* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+ .name = "landlock_audit",
+ .test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
new file mode 100644
index 000000000000..f33095afcd2f
--- /dev/null
+++ b/security/landlock/audit.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Audit helpers
+ *
+ * Copyright © 2023-2024 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_AUDIT_H
+#define _SECURITY_LANDLOCK_AUDIT_H
+
+#include <linux/audit.h>
+#include <linux/lsm_audit.h>
+
+#include "ruleset.h"
+
+enum landlock_request_type {
+ LANDLOCK_REQUEST_PTRACE = 1,
+};
+
+/*
+ * We should be careful to only use a variable of this type for
+ * landlock_log_denial(). This way, the compiler can remove it entirely if
+ * CONFIG_AUDIT is not set.
+ */
+struct landlock_request {
+ /* Mandatory fields. */
+ enum landlock_request_type type;
+ struct common_audit_data audit;
+
+ /**
+ * layer_plus_one: First layer level that denies the request + 1. The
+ * extra one is useful to detect uninitialized field.
+ */
+ size_t layer_plus_one;
+};
+
+#ifdef CONFIG_AUDIT
+
+void landlock_log_denial(const struct landlock_ruleset *const domain,
+ const struct landlock_request *const request);
+
+#else /* CONFIG_AUDIT */
+
+static inline void
+landlock_log_denial(const struct landlock_ruleset *const domain,
+ const struct landlock_request *const request)
+{
+}
+
+#endif /* CONFIG_AUDIT */
+
+#endif /* _SECURITY_LANDLOCK_AUDIT_H */
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
new file mode 100644
index 000000000000..965e4dc21975
--- /dev/null
+++ b/security/landlock/domain.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Domain management
+ *
+ * Copyright © 2024 Microsoft Corporation
+ */
+
+#include "domain.h"
+#include "id.h"
+
+/**
+ * landlock_init_current_hierarchy - Partially initialize landlock_hierarchy
+ *
+ * @hierarchy: The hierarchy to initialize.
+ *
+ * @hierarchy->parent and @hierarchy->usage should already be set.
+ */
+void landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
+{
+ hierarchy->id = landlock_get_id(1);
+}
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 015d61fd81ec..f82d831ca0a7 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -4,6 +4,7 @@
*
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2018-2020 ANSSI
+ * Copyright © 2024 Microsoft Corporation
*/
#ifndef _SECURITY_LANDLOCK_DOMAIN_H
@@ -26,6 +27,13 @@ struct landlock_hierarchy {
* domain.
*/
refcount_t usage;
+
+#ifdef CONFIG_AUDIT
+ /**
+ * @id: Landlock domain ID, sets once at domain creation time.
+ */
+ u64 id;
+#endif /* CONFIG_AUDIT */
};
static inline void
@@ -45,4 +53,13 @@ static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
}
}
+#ifdef CONFIG_AUDIT
+void landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy);
+#else /* CONFIG_AUDIT */
+static inline void
+landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
+{
+}
+#endif /* CONFIG_AUDIT */
+
#endif /* _SECURITY_LANDLOCK_DOMAIN_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 4b3dfa3e6fcb..7a88fd9b2473 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -21,6 +21,7 @@
#include <linux/workqueue.h>
#include "access.h"
+#include "audit.h"
#include "domain.h"
#include "limits.h"
#include "object.h"
@@ -503,6 +504,7 @@ static void free_ruleset_work(struct work_struct *const work)
free_ruleset(ruleset);
}
+/* Only called by hook_cred_free(). */
void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
{
if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
@@ -562,6 +564,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
if (err)
goto out_put_dom;
+ landlock_init_current_hierarchy(new_dom->hierarchy);
return new_dom;
out_put_dom:
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 98894ad1abc7..1decd6f114e8 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -10,12 +10,14 @@
#include <linux/cred.h>
#include <linux/errno.h>
#include <linux/kernel.h>
+#include <linux/lsm_audit.h>
#include <linux/lsm_hooks.h>
#include <linux/rcupdate.h>
#include <linux/sched.h>
#include <net/af_unix.h>
#include <net/sock.h>
+#include "audit.h"
#include "common.h"
#include "cred.h"
#include "domain.h"
@@ -38,41 +40,29 @@ static bool domain_scope_le(const struct landlock_ruleset *const parent,
{
const struct landlock_hierarchy *walker;
+ /* Quick return for non-landlocked tasks. */
if (!parent)
return true;
+
if (!child)
return false;
+
for (walker = child->hierarchy; walker; walker = walker->parent) {
if (walker == parent->hierarchy)
/* @parent is in the scoped hierarchy of @child. */
return true;
}
+
/* There is no relationship between @parent and @child. */
return false;
}
-static bool task_is_scoped(const struct task_struct *const parent,
- const struct task_struct *const child)
-{
- bool is_scoped;
- const struct landlock_ruleset *dom_parent, *dom_child;
-
- rcu_read_lock();
- dom_parent = landlock_get_task_domain(parent);
- dom_child = landlock_get_task_domain(child);
- is_scoped = domain_scope_le(dom_parent, dom_child);
- rcu_read_unlock();
- return is_scoped;
-}
-
-static int task_ptrace(const struct task_struct *const parent,
- const struct task_struct *const child)
+static int domain_ptrace(const struct landlock_ruleset *const parent,
+ const struct landlock_ruleset *const child)
{
- /* Quick return for non-landlocked tasks. */
- if (!landlocked(parent))
- return 0;
- if (task_is_scoped(parent, child))
+ if (domain_scope_le(parent, child))
return 0;
+
return -EPERM;
}
@@ -92,7 +82,36 @@ static int task_ptrace(const struct task_struct *const parent,
static int hook_ptrace_access_check(struct task_struct *const child,
const unsigned int mode)
{
- return task_ptrace(current, child);
+ const struct landlock_ruleset *parent_dom, *child_dom;
+ struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_PTRACE,
+ .audit = {
+ .type = LSM_AUDIT_DATA_TASK,
+ .u.tsk = child,
+ },
+ };
+ int err;
+
+ /* Quick return for non-landlocked tasks. */
+ parent_dom = landlock_get_current_domain();
+ if (!parent_dom)
+ return 0;
+
+ rcu_read_lock();
+ child_dom = landlock_get_task_domain(child);
+ err = domain_ptrace(parent_dom, child_dom);
+ rcu_read_unlock();
+
+ /*
+ * For the ptrace_access_check case, we log the current/parent domain
+ * and the child task.
+ */
+ if (err && !(mode & PTRACE_MODE_NOAUDIT)) {
+ request.layer_plus_one = parent_dom->num_layers;
+ landlock_log_denial(parent_dom, &request);
+ }
+
+ return err;
}
/**
@@ -109,7 +128,35 @@ static int hook_ptrace_access_check(struct task_struct *const child,
*/
static int hook_ptrace_traceme(struct task_struct *const parent)
{
- return task_ptrace(parent, current);
+ const struct landlock_ruleset *parent_dom, *child_dom;
+ struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_PTRACE,
+ .audit = {
+ .type = LSM_AUDIT_DATA_TASK,
+ .u.tsk = parent,
+ },
+ };
+ int err;
+
+ child_dom = landlock_get_current_domain();
+ rcu_read_lock();
+ parent_dom = landlock_get_task_domain(parent);
+ err = domain_ptrace(parent_dom, child_dom);
+
+ /*
+ * For the ptrace_traceme case, we log the domain which is the cause of
+ * the denial, which means the parent domain instead of the current
+ * domain. This may look weird because the ptrace_traceme action is a
+ * request to be traced, but the semantic is consistent with
+ * hook_ptrace_access_check().
+ */
+ if (err) {
+ request.layer_plus_one = parent_dom->num_layers;
+ landlock_log_denial(parent_dom, &request);
+ }
+
+ rcu_read_unlock();
+ return err;
}
/**
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 09/23] audit: Add a new audit_get_ctime() helper
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (7 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 08/23] landlock: Log ptrace denials Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2025-01-05 1:23 ` [PATCH v3 9/23] " Paul Moore
2024-11-22 14:33 ` [PATCH v3 10/23] landlock: Log domain properties and release Mickaël Salaün
` (14 subsequent siblings)
23 siblings, 1 reply; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
It may be useful to synchronize with the audit's timestamp e.g., to
identify asynchronous events as being created with a previous audit
record (see next commit).
auditsc_get_stamp() does more than just getting a timestamp, so add a
new helper instead of exposing it and risking side effects.
It should be noted that we cannot reliably expose event's serial numbers
because there may not be any related event, which would then create
holes in the sequence of serial numbers.
Cc: Eric Paris <eparis@redhat.com>
Cc: Paul Moore <paul@paul-moore.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-10-mic@digikod.net
---
Changes since v2:
- New patch.
---
include/linux/audit.h | 8 ++++++++
kernel/auditsc.c | 21 ++++++++++++++++++---
2 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/include/linux/audit.h b/include/linux/audit.h
index 0050ef288ab3..cff07525c6ae 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -12,6 +12,7 @@
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <linux/audit_arch.h>
+#include <linux/time64.h>
#include <uapi/linux/audit.h>
#include <uapi/linux/netfilter/nf_tables.h>
#include <uapi/linux/fanotify.h>
@@ -408,6 +409,7 @@ extern int __audit_socketcall(int nargs, unsigned long *args);
extern int __audit_sockaddr(int len, void *addr);
extern void __audit_fd_pair(int fd1, int fd2);
extern void __audit_mq_open(int oflag, umode_t mode, struct mq_attr *attr);
+extern struct timespec64 audit_get_ctime(const struct audit_context *ctx);
extern void __audit_mq_sendrecv(mqd_t mqdes, size_t msg_len, unsigned int msg_prio, const struct timespec64 *abs_timeout);
extern void __audit_mq_notify(mqd_t mqdes, const struct sigevent *notification);
extern void __audit_mq_getsetattr(mqd_t mqdes, struct mq_attr *mqstat);
@@ -653,6 +655,12 @@ static inline int audit_sockaddr(int len, void *addr)
}
static inline void audit_mq_open(int oflag, umode_t mode, struct mq_attr *attr)
{ }
+static inline struct timespec64 audit_get_ctime(const struct audit_context *ctx)
+{
+ struct timespec64 t = {};
+
+ return t;
+}
static inline void audit_mq_sendrecv(mqd_t mqdes, size_t msg_len,
unsigned int msg_prio,
const struct timespec64 *abs_timeout)
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index cd57053b4a69..3b7dcb47f5af 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -2511,6 +2511,22 @@ void __audit_inode_child(struct inode *parent,
}
EXPORT_SYMBOL_GPL(__audit_inode_child);
+/**
+ * audit_get_ctime - get creation time of audit_context
+ *
+ * @ctx: audit_context for the task
+ *
+ * Returns an empty timespec64 if ctx is NULL.
+ */
+struct timespec64 audit_get_ctime(const struct audit_context *ctx)
+{
+ struct timespec64 t = {};
+
+ if (ctx)
+ return ctx->ctime;
+ return t;
+}
+
/**
* auditsc_get_stamp - get local copies of audit_context values
* @ctx: audit_context for the task
@@ -2526,9 +2542,8 @@ int auditsc_get_stamp(struct audit_context *ctx,
return 0;
if (!ctx->serial)
ctx->serial = audit_serial();
- t->tv_sec = ctx->ctime.tv_sec;
- t->tv_nsec = ctx->ctime.tv_nsec;
- *serial = ctx->serial;
+ *t = audit_get_ctime(ctx);
+ *serial = ctx->serial;
if (!ctx->prio) {
ctx->prio = 1;
ctx->current_state = AUDIT_STATE_RECORD;
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 10/23] landlock: Log domain properties and release
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (8 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 09/23] audit: Add a new audit_get_ctime() helper Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2025-01-05 1:23 ` Paul Moore
2024-11-22 14:33 ` [PATCH v3 11/23] landlock: Log mount-related denials Mickaël Salaün
` (13 subsequent siblings)
23 siblings, 1 reply; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
TODO: Update sample with audit's creation time and now-removed parent
hierarchy
Asynchronously log domain information when it first denies an access.
This minimize the amount of generated logs, which makes it possible to
always log denials since they should not happen (except with the new
LANDLOCK_RESTRICT_SELF_LOGLESS flag). These records are identified by
AUDIT_LANDLOCK_DOM_INFO.
Log domain deletion with the AUDIT_LANDLOCK_DOM_DROP record type when
a domain was previously logged. This makes it possible for user space
to free potential resources when a domain ID will never show again.
The logged domain properties include:
- the domain ID
- creation time
- its creator's PID
- its creator's UID
- its creator's executable path (exe)
- its creator's command line (comm)
This require each domain to save some task properties at creation time:
time, PID, credentials, exe path, and comm.
It should be noted that we cannot use audit event's serial numbers
because there may not be any related event. However, it is still useful
to use the same potential timestamp instead of a close one. What really
matter is how old the related Landlock domain is, not the uniquiness of
the creation time. If audit is not enabled, Landlock creates its own
timestamp. This timestamp will be exposed to user space with a future
unprivileged interface.
Audit event sample for a first denial:
type=LL_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
type=LL_DOM_INFO msg=audit(1732186800.349:44): domain=195ba459b creation=1732186800.345 pid=300 uid=0 exe="/root/sandboxer" comm="sandboxer"
type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
Audit event sample for logged domains deletion:
type=LL_DOM_DROP msg=audit(1732186800.393:45): domain=195ba459b
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-11-mic@digikod.net
---
Questions:
- Should we also log the creator's loginuid?
- Should we also log the creator's sessionid?
Changes since v2:
- Fix docstring.
- Fix log_status check in log_hierarchy() to also log
LANDLOCK_LOG_DISABLED.
- Add audit's creation time to domain's properties.
- Use hexadecimal notation for domain IDs.
- Remove domain's parent records: parent domains are not really useful
in the logs. They will be available with the upcoming introspection
feature though.
- Extend commit message with audit's timestamp explanation.
Changes since v1:
- Add a ruleset's version for atomic logs.
- Rebased on the TCP patch series.
- Rename operation using "_" instead of "-".
- Rename AUDIT_LANDLOCK to AUDIT_LANDLOCK_RULESET.
- Only log when audit is enabled, but always set domain IDs.
- Don't log task's PID/TID with log_task() because it would be redundant
with the SYSCALL record.
- Remove race condition when logging ruleset creation and logging
ruleset modification while the related file descriptor was already
registered but the ruleset creation not logged yet.
- Fix domain drop logs.
- Move the domain drop record from the previous patch into this one.
- Do not log domain creation but log first domain use instead.
- Save task's properties that sandbox themselves.
---
include/uapi/linux/audit.h | 2 +
security/landlock/audit.c | 74 ++++++++++++++++++++++++++++++++++++
security/landlock/audit.h | 7 ++++
security/landlock/domain.c | 41 ++++++++++++++++++++
security/landlock/domain.h | 43 +++++++++++++++++++++
security/landlock/ruleset.c | 7 ++++
security/landlock/ruleset.h | 1 +
security/landlock/syscalls.c | 1 +
8 files changed, 176 insertions(+)
diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index 60c909c396c0..a72f7b3403be 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -147,6 +147,8 @@
#define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */
#define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */
#define AUDIT_LANDLOCK_DENY 1423 /* Landlock denial */
+#define AUDIT_LANDLOCK_DOM_INFO 1424 /* Landlock domain properties */
+#define AUDIT_LANDLOCK_DOM_DROP 1425 /* Landlock domain release */
#define AUDIT_FIRST_KERN_ANOM_MSG 1700
#define AUDIT_LAST_KERN_ANOM_MSG 1799
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index eab6b3a8a231..2d0a96797dd4 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -8,8 +8,11 @@
#include <kunit/test.h>
#include <linux/audit.h>
#include <linux/lsm_audit.h>
+#include <linux/pid.h>
+#include <linux/uidgid.h>
#include "audit.h"
+#include "cred.h"
#include "domain.h"
#include "ruleset.h"
@@ -30,6 +33,41 @@ static void log_blockers(struct audit_buffer *const ab,
audit_log_format(ab, "%s", get_blocker(type));
}
+static void log_node(struct landlock_hierarchy *const node)
+{
+ struct audit_buffer *ab;
+
+ if (WARN_ON_ONCE(!node))
+ return;
+
+ /* Ignores already logged domains. */
+ if (READ_ONCE(node->log_status) == LANDLOCK_LOG_RECORDED)
+ return;
+
+ ab = audit_log_start(audit_context(), GFP_ATOMIC,
+ AUDIT_LANDLOCK_DOM_INFO);
+ if (!ab)
+ return;
+
+ WARN_ON_ONCE(node->id == 0);
+ audit_log_format(ab, "domain=%llx creation=%llu.%03lu pid=%d uid=%u",
+ node->id,
+ /* See audit_log_start() */
+ (unsigned long long)node->creation.tv_sec,
+ node->creation.tv_nsec / 1000000, pid_nr(node->pid),
+ from_kuid(&init_user_ns, node->cred->uid));
+ audit_log_d_path(ab, " exe=", &node->exe);
+ audit_log_format(ab, " comm=");
+ audit_log_untrustedstring(ab, node->comm);
+ audit_log_end(ab);
+
+ /*
+ * There may be race condition leading to logging of the same domain
+ * several times but that is OK.
+ */
+ WRITE_ONCE(node->log_status, LANDLOCK_LOG_RECORDED);
+}
+
static struct landlock_hierarchy *
get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
{
@@ -116,6 +154,42 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
log_blockers(ab, request->type);
audit_log_lsm_data(ab, &request->audit);
audit_log_end(ab);
+
+ /* Logs this domain if it is the first time. */
+ log_node(youngest_denied);
+}
+
+/**
+ * landlock_log_drop_domain - Create an audit record when a domain is deleted
+ *
+ * Only domains which previously appeared in the audit logs are logged again.
+ * This is useful to know when a domain will never show again in the audit log.
+ *
+ * This record is not directly tied to a syscall entry.
+ *
+ * Called by the cred_free() hook, in an uninterruptible context.
+ */
+void landlock_log_drop_domain(const struct landlock_ruleset *const domain)
+{
+ struct audit_buffer *ab;
+
+ if (WARN_ON_ONCE(!domain->hierarchy))
+ return;
+
+ if (!audit_enabled)
+ return;
+
+ /* Ignores domains that were not logged. */
+ if (READ_ONCE(domain->hierarchy->log_status) != LANDLOCK_LOG_RECORDED)
+ return;
+
+ ab = audit_log_start(audit_context(), GFP_ATOMIC,
+ AUDIT_LANDLOCK_DOM_DROP);
+ if (!ab)
+ return;
+
+ audit_log_format(ab, "domain=%llx", domain->hierarchy->id);
+ audit_log_end(ab);
}
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index f33095afcd2f..7a1b1652f21b 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -36,11 +36,18 @@ struct landlock_request {
#ifdef CONFIG_AUDIT
+void landlock_log_drop_domain(const struct landlock_ruleset *const domain);
+
void landlock_log_denial(const struct landlock_ruleset *const domain,
const struct landlock_request *const request);
#else /* CONFIG_AUDIT */
+static inline void
+landlock_log_drop_domain(const struct landlock_ruleset *const domain)
+{
+}
+
static inline void
landlock_log_denial(const struct landlock_ruleset *const domain,
const struct landlock_request *const request)
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 965e4dc21975..69b15c059583 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -5,17 +5,58 @@
* Copyright © 2024 Microsoft Corporation
*/
+#include <linux/audit.h>
+#include <linux/cred.h>
+#include <linux/file.h>
+#include <linux/mm.h>
+#include <linux/path.h>
+#include <linux/pid.h>
+#include <linux/sched.h>
+#include <linux/timekeeping.h>
+
#include "domain.h"
#include "id.h"
+static struct path get_current_exe(void)
+{
+ struct path exe_path = {};
+ struct file *exe_file;
+ struct mm_struct *mm = current->mm;
+
+ if (!mm)
+ return exe_path;
+
+ exe_file = get_mm_exe_file(mm);
+ if (!exe_file)
+ return exe_path;
+
+ exe_path = exe_file->f_path;
+ path_get(&exe_path);
+ fput(exe_file);
+ return exe_path;
+}
+
/**
* landlock_init_current_hierarchy - Partially initialize landlock_hierarchy
*
* @hierarchy: The hierarchy to initialize.
*
+ * The current task is referenced as the domain creator. The subjective
+ * credentials must not be in an overridden state.
+ *
* @hierarchy->parent and @hierarchy->usage should already be set.
*/
void landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
{
+ hierarchy->creation = audit_get_ctime(audit_context());
+ if (!hierarchy->creation.tv_sec)
+ ktime_get_coarse_real_ts64(&hierarchy->creation);
+
+ hierarchy->log_status = LANDLOCK_LOG_PENDING;
hierarchy->id = landlock_get_id(1);
+ WARN_ON_ONCE(current_cred() != current_real_cred());
+ hierarchy->cred = get_current_cred();
+ hierarchy->pid = get_pid(task_pid(current));
+ hierarchy->exe = get_current_exe();
+ get_task_comm(hierarchy->comm, current);
}
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index f82d831ca0a7..4a2864a66047 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -10,8 +10,18 @@
#ifndef _SECURITY_LANDLOCK_DOMAIN_H
#define _SECURITY_LANDLOCK_DOMAIN_H
+#include <linux/cred.h>
#include <linux/mm.h>
+#include <linux/path.h>
+#include <linux/pid.h>
#include <linux/refcount.h>
+#include <linux/sched.h>
+#include <linux/time64.h>
+
+enum landlock_log_status {
+ LANDLOCK_LOG_PENDING = 0,
+ LANDLOCK_LOG_RECORDED,
+};
/**
* struct landlock_hierarchy - Node in a domain hierarchy
@@ -29,10 +39,37 @@ struct landlock_hierarchy {
refcount_t usage;
#ifdef CONFIG_AUDIT
+ /**
+ * @log_status: Whether this domain should be logged or not. Because
+ * concurrent log entries may be created at the same time, it is still
+ * possible to have several domain records of the same domain.
+ */
+ enum landlock_log_status log_status;
/**
* @id: Landlock domain ID, sets once at domain creation time.
*/
u64 id;
+ /**
+ * @creation: Time of the domain creation (i.e. syscall entry as used
+ * in audit context).
+ */
+ struct timespec64 creation;
+ /**
+ * @cred: Credential of the domain's creator.
+ */
+ const struct cred *cred;
+ /**
+ * @pid: Creator's PID.
+ */
+ struct pid *pid;
+ /**
+ * @exe: Creator's exe path.
+ */
+ struct path exe;
+ /**
+ * @comm: Command line of the domain's creator at creation time.
+ */
+ char comm[TASK_COMM_LEN];
#endif /* CONFIG_AUDIT */
};
@@ -48,6 +85,12 @@ 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;
+#ifdef CONFIG_AUDIT
+ put_cred(hierarchy->cred);
+ put_pid(hierarchy->pid);
+ path_put(&hierarchy->exe);
+#endif /* CONFIG_AUDIT */
+
hierarchy = hierarchy->parent;
kfree(freeme);
}
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 7a88fd9b2473..4619c7f63d05 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -508,6 +508,9 @@ static void free_ruleset_work(struct work_struct *const work)
void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
{
if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
+ /* Logs with the current context. */
+ landlock_log_drop_domain(ruleset);
+
INIT_WORK(&ruleset->work_free, free_ruleset_work);
schedule_work(&ruleset->work_free);
}
@@ -519,6 +522,10 @@ void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
* @parent: Parent domain.
* @ruleset: New ruleset to be merged.
*
+ * The current task is referenced as the domain creator. The subjective
+ * credentials must not be in an overridden state (see
+ * landlock_init_current_hierarchy).
+ *
* Returns the intersection of @parent and @ruleset, or returns @parent if
* @ruleset is empty, or returns a duplicate of @ruleset if @parent is empty.
*/
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 39169b6860e3..73636f214aac 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -12,6 +12,7 @@
#include <linux/mutex.h>
#include <linux/rbtree.h>
#include <linux/refcount.h>
+#include <linux/sched.h>
#include <linux/workqueue.h>
#include "access.h"
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index c097d356fa45..335067e36feb 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -26,6 +26,7 @@
#include <linux/uaccess.h>
#include <uapi/linux/landlock.h>
+#include "audit.h"
#include "cred.h"
#include "fs.h"
#include "limits.h"
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 11/23] landlock: Log mount-related denials
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (9 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 10/23] landlock: Log domain properties and release Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 12/23] landlock: Align partial refer access checks with final ones Mickaël Salaün
` (12 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Add audit support for sb_mount, move_mount, sb_umount, sb_remount, and
sb_pivot_root hooks.
Add and use a new landlock_match_layer_level() helper.
Audit event sample:
type=LL_DENY [...]: domain=195ba459b blockers=fs_change_layout name="/" dev="tmpfs" ino=1
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-12-mic@digikod.net
---
Changes since v2:
- Log the domain that denied the action because not all layers block FS
layout changes.
- Fix landlock_match_layer_level().
Changes since v1:
- Rebased on the TCP patch series.
- Don't log missing permissions, only domain layer, and then remove the
permission word (suggested by Günther)
---
security/landlock/audit.c | 3 ++
security/landlock/audit.h | 1 +
security/landlock/fs.c | 64 ++++++++++++++++++++++++++++++++++---
security/landlock/ruleset.h | 31 ++++++++++++++++++
4 files changed, 94 insertions(+), 5 deletions(-)
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 2d0a96797dd4..a07b210ca524 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -21,6 +21,9 @@ static const char *get_blocker(const enum landlock_request_type type)
switch (type) {
case LANDLOCK_REQUEST_PTRACE:
return "ptrace";
+
+ case LANDLOCK_REQUEST_FS_CHANGE_LAYOUT:
+ return "fs_change_layout";
}
WARN_ON_ONCE(1);
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 7a1b1652f21b..6f5ad04b83c2 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -15,6 +15,7 @@
enum landlock_request_type {
LANDLOCK_REQUEST_PTRACE = 1,
+ LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
};
/*
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 9779170d9199..171012efb559 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -23,6 +23,7 @@
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/list.h>
+#include <linux/lsm_audit.h>
#include <linux/lsm_hooks.h>
#include <linux/mount.h>
#include <linux/namei.h>
@@ -37,6 +38,7 @@
#include <uapi/linux/landlock.h>
#include "access.h"
+#include "audit.h"
#include "common.h"
#include "cred.h"
#include "fs.h"
@@ -1301,6 +1303,38 @@ static void hook_sb_delete(struct super_block *const sb)
!atomic_long_read(&landlock_superblock(sb)->inode_refs));
}
+static void
+log_fs_change_layout_path(const struct landlock_ruleset *const domain,
+ const struct path *const path)
+{
+ const struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
+ .audit = {
+ .type = LSM_AUDIT_DATA_PATH,
+ .u.path = *path,
+ },
+ .layer_plus_one = landlock_match_layer_level(domain, any_fs) + 1,
+ };
+
+ landlock_log_denial(domain, &request);
+}
+
+static void
+log_fs_change_layout_dentry(const struct landlock_ruleset *const domain,
+ struct dentry *const dentry)
+{
+ const struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
+ .audit = {
+ .type = LSM_AUDIT_DATA_DENTRY,
+ .u.dentry = dentry,
+ },
+ .layer_plus_one = landlock_match_layer_level(domain, any_fs) + 1,
+ };
+
+ landlock_log_denial(domain, &request);
+}
+
/*
* Because a Landlock security policy is defined according to the filesystem
* topology (i.e. the mount namespace), changing it may grant access to files
@@ -1323,16 +1357,24 @@ static int hook_sb_mount(const char *const dev_name,
const struct path *const path, const char *const type,
const unsigned long flags, void *const data)
{
- if (!get_current_fs_domain())
+ const struct landlock_ruleset *const dom = get_current_fs_domain();
+
+ if (!dom)
return 0;
+
+ log_fs_change_layout_path(dom, path);
return -EPERM;
}
static int hook_move_mount(const struct path *const from_path,
const struct path *const to_path)
{
- if (!get_current_fs_domain())
+ const struct landlock_ruleset *const dom = get_current_fs_domain();
+
+ if (!dom)
return 0;
+
+ log_fs_change_layout_path(dom, to_path);
return -EPERM;
}
@@ -1342,15 +1384,23 @@ static int hook_move_mount(const struct path *const from_path,
*/
static int hook_sb_umount(struct vfsmount *const mnt, const int flags)
{
- if (!get_current_fs_domain())
+ const struct landlock_ruleset *const dom = get_current_fs_domain();
+
+ if (!dom)
return 0;
+
+ log_fs_change_layout_dentry(dom, mnt->mnt_root);
return -EPERM;
}
static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
{
- if (!get_current_fs_domain())
+ const struct landlock_ruleset *const dom = get_current_fs_domain();
+
+ if (!dom)
return 0;
+
+ log_fs_change_layout_dentry(dom, sb->s_root);
return -EPERM;
}
@@ -1365,8 +1415,12 @@ static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
static int hook_sb_pivotroot(const struct path *const old_path,
const struct path *const new_path)
{
- if (!get_current_fs_domain())
+ const struct landlock_ruleset *const dom = get_current_fs_domain();
+
+ if (!dom)
return 0;
+
+ log_fs_change_layout_path(dom, new_path);
return -EPERM;
}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 73636f214aac..944126a92aec 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -268,6 +268,37 @@ landlock_get_applicable_domain(const struct landlock_ruleset *const domain,
return NULL;
}
+/**
+ * landlock_match_layer_level - Return the layer level restricting @masks
+ *
+ * @domain: Landlock ruleset (used as a domain)
+ * @masks: access masks
+ *
+ * Returns: the number of the layer restricting/handling any right of @access,
+ * or return 0 (i.e. first layer) otherwise.
+ */
+static inline size_t
+landlock_match_layer_level(const struct landlock_ruleset *const domain,
+ const struct access_masks masks)
+{
+ const union access_masks_all masks_all = {
+ .masks = masks,
+ };
+ ssize_t layer_level;
+
+ for (layer_level = domain->num_layers; layer_level >= 0;
+ layer_level--) {
+ union access_masks_all layer = {
+ .masks = domain->access_masks[layer_level],
+ };
+
+ if (masks_all.all & layer.all)
+ return layer_level;
+ }
+
+ return 0;
+}
+
static inline void
landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
const access_mask_t fs_access_mask,
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 12/23] landlock: Align partial refer access checks with final ones
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (10 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 11/23] landlock: Log mount-related denials Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 13/23] selftests/landlock: Add test to check partial access in a mount tree Mickaël Salaün
` (11 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Fix a logical issue that could have been visible if the source or the
destination of a rename/link action was allowed for either the source or
the destination but not both. However, this logical bug is unreachable
because either:
- the rename/link action is allowed by the access rights tied to the
same mount point (without relying on access rights in a parent mount
point) and the access request is allowed (i.e. allow_parent1 and
allow_parent2 are true in current_check_refer_path),
- or a common rule in a parent mount point updates the access check for
the source and the destination (cf. is_access_to_paths_allowed).
See the following layout1.refer_part_mount_tree_is_allowed test that
work with and without this fix.
This fix does not impact current code but it is required for the audit
support.
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-13-mic@digikod.net
---
Changes since v2:
- New patch.
---
security/landlock/fs.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 171012efb559..ddadc465581e 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -567,6 +567,12 @@ static void test_no_more_access(struct kunit *const test)
#undef NMA_TRUE
#undef NMA_FALSE
+static bool is_layer_masks_allowed(
+ layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+{
+ return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
+}
+
/*
* Removes @layer_masks accesses that are not requested.
*
@@ -584,7 +590,8 @@ scope_to_request(const access_mask_t access_request,
for_each_clear_bit(access_bit, &access_req, ARRAY_SIZE(*layer_masks))
(*layer_masks)[access_bit] = 0;
- return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
+
+ return is_layer_masks_allowed(layer_masks);
}
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
@@ -773,9 +780,14 @@ static bool is_access_to_paths_allowed(
if (WARN_ON_ONCE(domain->num_layers < 1 || !layer_masks_parent1))
return false;
+ allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
+
if (unlikely(layer_masks_parent2)) {
if (WARN_ON_ONCE(!dentry_child1))
return false;
+
+ allowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);
+
/*
* For a double request, first check for potential privilege
* escalation by looking at domain handled accesses (which are
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 13/23] selftests/landlock: Add test to check partial access in a mount tree
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (11 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 12/23] landlock: Align partial refer access checks with final ones Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 14/23] landlock: Optimize file path walks and prepare for audit support Mickaël Salaün
` (10 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Add layout1.refer_part_mount_tree_is_allowed to test the masked logical
issue regarding collect_domain_accesses() calls followed by the
is_access_to_paths_allowed() check in current_check_refer_path(). See
previous commit.
This test should work without the previous fix as well, but it enables
us to make sure future changes will not have impact regarding this
behavior.
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-14-mic@digikod.net
---
Changes since v2:
- New patch.
---
tools/testing/selftests/landlock/fs_test.c | 54 ++++++++++++++++++++--
1 file changed, 50 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 6788762188fe..42ce1e79ba82 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -85,6 +85,9 @@ static const char file1_s3d1[] = TMP_DIR "/s3d1/f1";
/* dir_s3d2 is a mount point. */
static const char dir_s3d2[] = TMP_DIR "/s3d1/s3d2";
static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3";
+static const char file1_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3/f1";
+static const char dir_s3d4[] = TMP_DIR "/s3d1/s3d2/s3d4";
+static const char file1_s3d4[] = TMP_DIR "/s3d1/s3d2/s3d4/f1";
/*
* layout1 hierarchy:
@@ -108,8 +111,11 @@ static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3";
* │ └── f2
* └── s3d1
* ├── f1
- * └── s3d2
- * └── s3d3
+ * └── s3d2 [mount point]
+ * ├── s3d3
+ * │ └── f1
+ * └── s3d4
+ * └── f1
*/
static bool fgrep(FILE *const inf, const char *const str)
@@ -358,7 +364,8 @@ static void create_layout1(struct __test_metadata *const _metadata)
ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s3d2));
clear_cap(_metadata, CAP_SYS_ADMIN);
- ASSERT_EQ(0, mkdir(dir_s3d3, 0700));
+ create_file(_metadata, file1_s3d3);
+ create_file(_metadata, file1_s3d4);
}
static void remove_layout1(struct __test_metadata *const _metadata)
@@ -378,7 +385,8 @@ static void remove_layout1(struct __test_metadata *const _metadata)
EXPECT_EQ(0, remove_path(dir_s2d2));
EXPECT_EQ(0, remove_path(file1_s3d1));
- EXPECT_EQ(0, remove_path(dir_s3d3));
+ EXPECT_EQ(0, remove_path(file1_s3d3));
+ EXPECT_EQ(0, remove_path(file1_s3d4));
set_cap(_metadata, CAP_SYS_ADMIN);
umount(dir_s3d2);
clear_cap(_metadata, CAP_SYS_ADMIN);
@@ -2444,6 +2452,44 @@ TEST_F_FORK(layout1, refer_mount_root_deny)
EXPECT_EQ(0, close(root_fd));
}
+TEST_F_FORK(layout1, refer_part_mount_tree_is_allowed)
+{
+ const struct rule layer1[] = {
+ {
+ /* Parent mount point. */
+ .path = dir_s3d1,
+ .access = LANDLOCK_ACCESS_FS_REFER |
+ LANDLOCK_ACCESS_FS_MAKE_REG,
+ },
+ {
+ /*
+ * Removing the source file is allowed because its
+ * access rights are already a superset of the
+ * destination.
+ */
+ .path = dir_s3d4,
+ .access = LANDLOCK_ACCESS_FS_REFER |
+ LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ },
+ {},
+ };
+ int ruleset_fd;
+
+ ASSERT_EQ(0, unlink(file1_s3d3));
+ ruleset_fd = create_ruleset(_metadata,
+ LANDLOCK_ACCESS_FS_REFER |
+ LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ layer1);
+
+ ASSERT_LE(0, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ ASSERT_EQ(0, close(ruleset_fd));
+
+ ASSERT_EQ(0, rename(file1_s3d4, file1_s3d3));
+}
+
TEST_F_FORK(layout1, reparent_link)
{
const struct rule layer1[] = {
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 14/23] landlock: Optimize file path walks and prepare for audit support
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (12 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 13/23] selftests/landlock: Add test to check partial access in a mount tree Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 15/23] landlock: Log file-related denials Mickaël Salaün
` (9 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Always synchronize access_masked_parent* with access_request_parent*
according to allowed_parent*. This is required for audit support to be
able to get back to the reason of denial.
In a rename/link action, instead of always checking a rule two times for
the same parent directory of the source and the destination files, only
check it when an action on a child was not already allowed. This also
enables us to keep consistent allowed_parent* status, which is required
to get back to the reason of denial.
For internal mount points, only upgrade allowed_parent* to true but do
not wrongfully set both of them to false otherwise. This is also
required to get back to the reason of denial.
This does not impact the current behavior but slightly optimize code and
prepare for audit support that needs to know the exact reason why an
access was denied.
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-15-mic@digikod.net
---
Changes since v2:
- New patch.
---
security/landlock/fs.c | 44 ++++++++++++++++++++++++++----------------
1 file changed, 27 insertions(+), 17 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index ddadc465581e..01f9d5e78218 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -854,15 +854,6 @@ static bool is_access_to_paths_allowed(
child1_is_directory, layer_masks_parent2,
layer_masks_child2,
child2_is_directory))) {
- allowed_parent1 = scope_to_request(
- access_request_parent1, layer_masks_parent1);
- allowed_parent2 = scope_to_request(
- access_request_parent2, layer_masks_parent2);
-
- /* Stops when all accesses are granted. */
- if (allowed_parent1 && allowed_parent2)
- break;
-
/*
* Now, downgrades the remaining checks from domain
* handled accesses to requested accesses.
@@ -870,15 +861,32 @@ static bool is_access_to_paths_allowed(
is_dom_check = false;
access_masked_parent1 = access_request_parent1;
access_masked_parent2 = access_request_parent2;
+
+ allowed_parent1 =
+ allowed_parent1 ||
+ scope_to_request(access_masked_parent1,
+ layer_masks_parent1);
+ allowed_parent2 =
+ allowed_parent2 ||
+ scope_to_request(access_masked_parent2,
+ layer_masks_parent2);
+
+ /* Stops when all accesses are granted. */
+ if (allowed_parent1 && allowed_parent2)
+ break;
}
rule = find_rule(domain, walker_path.dentry);
- allowed_parent1 = landlock_unmask_layers(
- rule, access_masked_parent1, layer_masks_parent1,
- ARRAY_SIZE(*layer_masks_parent1));
- allowed_parent2 = landlock_unmask_layers(
- rule, access_masked_parent2, layer_masks_parent2,
- ARRAY_SIZE(*layer_masks_parent2));
+ allowed_parent1 = allowed_parent1 ||
+ landlock_unmask_layers(
+ rule, access_masked_parent1,
+ layer_masks_parent1,
+ ARRAY_SIZE(*layer_masks_parent1));
+ allowed_parent2 = allowed_parent2 ||
+ landlock_unmask_layers(
+ rule, access_masked_parent2,
+ layer_masks_parent2,
+ ARRAY_SIZE(*layer_masks_parent2));
/* Stops when a rule from each layer grants access. */
if (allowed_parent1 && allowed_parent2)
@@ -902,8 +910,10 @@ static bool is_access_to_paths_allowed(
* access to internal filesystems (e.g. nsfs, which is
* reachable through /proc/<pid>/ns/<namespace>).
*/
- allowed_parent1 = allowed_parent2 =
- !!(walker_path.mnt->mnt_flags & MNT_INTERNAL);
+ if (walker_path.mnt->mnt_flags & MNT_INTERNAL) {
+ allowed_parent1 = true;
+ allowed_parent2 = true;
+ }
break;
}
parent_dentry = dget_parent(walker_path.dentry);
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 15/23] landlock: Log file-related denials
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (13 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 14/23] landlock: Optimize file path walks and prepare for audit support Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 16/23] landlock: Log truncate and ioctl denials Mickaël Salaün
` (8 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Add audit support for path_mkdir, path_mknod, path_symlink, path_unlink,
path_rmdir, path_truncate, path_link, path_rename, and file_open hooks.
Audit event sample for a link action:
type=LL_DENY [...]: domain=195ba459b blockers=fs_refer path="/usr/bin" dev="vda2" ino=351
type=LL_DENY [...]: domain=195ba459b blockers=fs_make_reg,fs_refer path="/usr/local" dev="vda2" ino=365
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-16-mic@digikod.net
---
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 | 65 +++++++++++---
3 files changed, 235 insertions(+), 17 deletions(-)
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index a07b210ca524..8a7657d12478 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -7,23 +7,56 @@
#include <kunit/test.h>
#include <linux/audit.h>
+#include <linux/bitops.h>
#include <linux/lsm_audit.h>
#include <linux/pid.h>
#include <linux/uidgid.h>
+#include <uapi/linux/landlock.h>
#include "audit.h"
+#include "common.h"
#include "cred.h"
#include "domain.h"
#include "ruleset.h"
-static const char *get_blocker(const enum landlock_request_type type)
+static const char *const fs_access_strings[] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs_execute",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = "fs_write_file",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = "fs_read_file",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = "fs_read_dir",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = "fs_remove_dir",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_FILE)] = "fs_remove_file",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_CHAR)] = "fs_make_char",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_DIR)] = "fs_make_dir",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = "fs_make_reg",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SOCK)] = "fs_make_sock",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_FIFO)] = "fs_make_fifo",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_BLOCK)] = "fs_make_block",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SYM)] = "fs_make_sym",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs_refer",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs_truncate",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs_ioctl_dev",
+};
+
+static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
+
+static __attribute_const__ const char *
+get_blocker(const enum landlock_request_type type,
+ const unsigned long access_bit)
{
switch (type) {
case LANDLOCK_REQUEST_PTRACE:
+ WARN_ON_ONCE(access_bit != -1);
return "ptrace";
case LANDLOCK_REQUEST_FS_CHANGE_LAYOUT:
+ WARN_ON_ONCE(access_bit != -1);
return "fs_change_layout";
+
+ case LANDLOCK_REQUEST_FS_ACCESS:
+ if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings)))
+ return "unknown";
+ return fs_access_strings[access_bit];
}
WARN_ON_ONCE(1);
@@ -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 access_mask = access;
+ unsigned long access_bit;
+ bool is_first = true;
+
+ for_each_set_bit(access_bit, &access_mask, BITS_PER_TYPE(access)) {
+ audit_log_format(ab, "%s%s", is_first ? "" : ",",
+ get_blocker(type, access_bit));
+ is_first = false;
+ }
+ if (is_first)
+ audit_log_format(ab, "%s", get_blocker(type, -1));
}
static void log_node(struct landlock_hierarchy *const node)
@@ -118,9 +162,110 @@ static void test_get_hierarchy(struct kunit *const test)
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+static size_t get_denied_layer(const struct landlock_ruleset *const domain,
+ access_mask_t *const access_request,
+ const layer_mask_t (*const layer_masks)[],
+ const size_t layer_masks_size)
+{
+ const unsigned long access_req = *access_request;
+ unsigned long access_bit;
+ access_mask_t missing = 0;
+ long youngest_layer = -1;
+
+ for_each_set_bit(access_bit, &access_req, layer_masks_size) {
+ const access_mask_t mask = (*layer_masks)[access_bit];
+ long layer;
+
+ if (!mask)
+ continue;
+
+ /* __fls(1) == 0 */
+ layer = __fls(mask);
+ if (layer > youngest_layer) {
+ youngest_layer = layer;
+ missing = BIT(access_bit);
+ } else if (layer == youngest_layer) {
+ missing |= BIT(access_bit);
+ }
+ }
+
+ *access_request = missing;
+ if (youngest_layer == -1)
+ return domain->num_layers - 1;
+
+ return youngest_layer;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_denied_layer(struct kunit *const test)
+{
+ const struct landlock_ruleset dom = {
+ .num_layers = 5,
+ };
+ const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT(0),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT(1),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = BIT(1) | BIT(0),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = BIT(2),
+ };
+ access_mask_t access;
+
+ access = LANDLOCK_ACCESS_FS_EXECUTE;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE);
+
+ access = LANDLOCK_ACCESS_FS_READ_FILE;
+ KUNIT_EXPECT_EQ(test, 1,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE);
+
+ access = LANDLOCK_ACCESS_FS_READ_DIR;
+ KUNIT_EXPECT_EQ(test, 1,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
+
+ access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
+ KUNIT_EXPECT_EQ(test, 1,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access,
+ LANDLOCK_ACCESS_FS_READ_FILE |
+ LANDLOCK_ACCESS_FS_READ_DIR);
+
+ access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR;
+ KUNIT_EXPECT_EQ(test, 1,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
+
+ access = LANDLOCK_ACCESS_FS_WRITE_FILE;
+ KUNIT_EXPECT_EQ(test, 4,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access, 0);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
static bool is_valid_request(const struct landlock_request *const request)
{
- if (WARN_ON_ONCE(!request->layer_plus_one))
+ if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
+ return false;
+
+ if (request->access) {
+ if (WARN_ON_ONCE(!request->layer_masks))
+ return false;
+ } else {
+ if (WARN_ON_ONCE(request->layer_masks))
+ return false;
+ }
+
+ if (WARN_ON_ONCE(!!request->layer_masks ^ !!request->layer_masks_size))
return false;
return true;
@@ -137,6 +282,7 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
{
struct audit_buffer *ab;
struct landlock_hierarchy *youngest_denied;
+ access_mask_t missing;
if (WARN_ON_ONCE(!domain || !domain->hierarchy || !request))
return;
@@ -152,9 +298,28 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
if (!ab)
return;
- youngest_denied = get_hierarchy(domain, request->layer_plus_one - 1);
+ missing = request->access;
+ if (missing) {
+ size_t youngest_layer;
+
+ /* Gets the nearest domain that denies the request. */
+ if (request->layer_masks) {
+ youngest_layer = get_denied_layer(
+ domain, &missing, request->layer_masks,
+ request->layer_masks_size);
+ } else {
+ /* This will change with the next commit. */
+ WARN_ON_ONCE(1);
+ youngest_layer = domain->num_layers;
+ }
+ youngest_denied = get_hierarchy(domain, youngest_layer);
+ } else {
+ youngest_denied =
+ get_hierarchy(domain, request->layer_plus_one - 1);
+ }
+
audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
- log_blockers(ab, request->type);
+ log_blockers(ab, request->type, missing);
audit_log_lsm_data(ab, &request->audit);
audit_log_end(ab);
@@ -200,6 +365,7 @@ void landlock_log_drop_domain(const struct landlock_ruleset *const domain)
static struct kunit_case test_cases[] = {
/* clang-format off */
KUNIT_CASE(test_get_hierarchy),
+ KUNIT_CASE(test_get_denied_layer),
{}
/* clang-format on */
};
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 6f5ad04b83c2..25fc8333cddc 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -11,11 +11,13 @@
#include <linux/audit.h>
#include <linux/lsm_audit.h>
+#include "access.h"
#include "ruleset.h"
enum landlock_request_type {
LANDLOCK_REQUEST_PTRACE = 1,
LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
+ LANDLOCK_REQUEST_FS_ACCESS,
};
/*
@@ -33,6 +35,13 @@ struct landlock_request {
* extra one is useful to detect uninitialized field.
*/
size_t layer_plus_one;
+
+ /* Required field for configurable access control. */
+ access_mask_t access;
+
+ /* Required fields for requests with layer masks. */
+ const layer_mask_t (*layer_masks)[];
+ size_t layer_masks_size;
};
#ifdef CONFIG_AUDIT
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 01f9d5e78218..f38c7a60b9bf 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -730,6 +730,7 @@ static void test_is_eacces_with_write(struct kunit *const test)
* those identified by @access_request_parent1). This matrix can
* initially refer to domain layer masks and, when the accesses for the
* destination and source are the same, to requested layer masks.
+ * @log_request_parent1: Audit request to fill if the related access is denied.
* @dentry_child1: Dentry to the initial child of the parent1 path. This
* pointer must be NULL for non-refer actions (i.e. not link nor rename).
* @access_request_parent2: Similar to @access_request_parent1 but for a
@@ -738,6 +739,7 @@ static void test_is_eacces_with_write(struct kunit *const test)
* the source. Must be set to 0 when using a simple path request.
* @layer_masks_parent2: Similar to @layer_masks_parent1 but for a refer
* action. This must be NULL otherwise.
+ * @log_request_parent2: Audit request to fill if the related access is denied.
* @dentry_child2: Dentry to the initial child of the parent2 path. This
* pointer is only set for RENAME_EXCHANGE actions and must be NULL
* otherwise.
@@ -757,10 +759,12 @@ static bool is_access_to_paths_allowed(
const struct path *const path,
const access_mask_t access_request_parent1,
layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS],
- const struct dentry *const dentry_child1,
+ struct landlock_request *const log_request_parent1,
+ struct dentry *const dentry_child1,
const access_mask_t access_request_parent2,
layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS],
- const struct dentry *const dentry_child2)
+ struct landlock_request *const log_request_parent2,
+ struct dentry *const dentry_child2)
{
bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
child1_is_directory = true, child2_is_directory = true;
@@ -922,6 +926,25 @@ static bool is_access_to_paths_allowed(
}
path_put(&walker_path);
+ if (!allowed_parent1) {
+ log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS,
+ log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH,
+ log_request_parent1->audit.u.path = *path;
+ log_request_parent1->access = access_masked_parent1;
+ log_request_parent1->layer_masks = layer_masks_parent1;
+ log_request_parent1->layer_masks_size =
+ ARRAY_SIZE(*layer_masks_parent1);
+ }
+
+ if (!allowed_parent2) {
+ log_request_parent2->type = LANDLOCK_REQUEST_FS_ACCESS,
+ log_request_parent2->audit.type = LSM_AUDIT_DATA_PATH,
+ log_request_parent2->audit.u.path = *path;
+ log_request_parent2->access = access_masked_parent2;
+ log_request_parent2->layer_masks = layer_masks_parent2;
+ log_request_parent2->layer_masks_size =
+ ARRAY_SIZE(*layer_masks_parent2);
+ }
return allowed_parent1 && allowed_parent2;
}
@@ -930,6 +953,7 @@ static int current_check_access_path(const struct path *const path,
{
const struct landlock_ruleset *const dom = get_current_fs_domain();
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
+ struct landlock_request request = {};
if (!dom)
return 0;
@@ -937,9 +961,10 @@ static int current_check_access_path(const struct path *const path,
access_request = landlock_init_layer_masks(
dom, access_request, &layer_masks, LANDLOCK_KEY_INODE);
if (is_access_to_paths_allowed(dom, path, access_request, &layer_masks,
- NULL, 0, NULL, NULL))
+ &request, NULL, 0, NULL, NULL, NULL))
return 0;
+ landlock_log_denial(dom, &request);
return -EACCES;
}
@@ -1108,6 +1133,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
struct dentry *old_parent;
layer_mask_t layer_masks_parent1[LANDLOCK_NUM_ACCESS_FS] = {},
layer_masks_parent2[LANDLOCK_NUM_ACCESS_FS] = {};
+ struct landlock_request request1 = {}, request2 = {};
if (!dom)
return 0;
@@ -1139,10 +1165,13 @@ static int current_check_refer_path(struct dentry *const old_dentry,
access_request_parent1 = landlock_init_layer_masks(
dom, access_request_parent1 | access_request_parent2,
&layer_masks_parent1, LANDLOCK_KEY_INODE);
- if (is_access_to_paths_allowed(
- dom, new_dir, access_request_parent1,
- &layer_masks_parent1, NULL, 0, NULL, NULL))
+ if (is_access_to_paths_allowed(dom, new_dir,
+ access_request_parent1,
+ &layer_masks_parent1, &request1,
+ NULL, 0, NULL, NULL, NULL))
return 0;
+
+ landlock_log_denial(dom, &request1);
return -EACCES;
}
@@ -1177,12 +1206,22 @@ static int current_check_refer_path(struct dentry *const old_dentry,
* parent access rights. This will be useful to compare with the
* destination parent access rights.
*/
- if (is_access_to_paths_allowed(
- dom, &mnt_dir, access_request_parent1, &layer_masks_parent1,
- old_dentry, access_request_parent2, &layer_masks_parent2,
- exchange ? new_dentry : NULL))
+ if (is_access_to_paths_allowed(dom, &mnt_dir, access_request_parent1,
+ &layer_masks_parent1, &request1,
+ old_dentry, access_request_parent2,
+ &layer_masks_parent2, &request2,
+ exchange ? new_dentry : NULL))
return 0;
+ if (request1.access) {
+ request1.audit.u.path.dentry = old_parent;
+ landlock_log_denial(dom, &request1);
+ }
+ if (request2.access) {
+ request2.audit.u.path.dentry = new_dir->dentry;
+ landlock_log_denial(dom, &request2);
+ }
+
/*
* This prioritizes EACCES over EXDEV for all actions, including
* renames with RENAME_EXCHANGE.
@@ -1562,6 +1601,7 @@ static int hook_file_open(struct file *const file)
const struct landlock_ruleset *const dom =
landlock_get_applicable_domain(
landlock_cred(file->f_cred)->domain, any_fs);
+ struct landlock_request request = {};
if (!dom)
return 0;
@@ -1587,7 +1627,7 @@ static int hook_file_open(struct file *const file)
dom, &file->f_path,
landlock_init_layer_masks(dom, full_access_request,
&layer_masks, LANDLOCK_KEY_INODE),
- &layer_masks, NULL, 0, NULL, NULL)) {
+ &layer_masks, &request, NULL, 0, NULL, NULL, NULL)) {
allowed_access = full_access_request;
} else {
unsigned long access_bit;
@@ -1617,6 +1657,9 @@ static int hook_file_open(struct file *const file)
if ((open_access_request & allowed_access) == open_access_request)
return 0;
+ /* Sets access to reflect the actual request. */
+ request.access = open_access_request;
+ landlock_log_denial(dom, &request);
return -EACCES;
}
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 16/23] landlock: Log truncate and ioctl denials
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (14 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 15/23] landlock: Log file-related denials Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 17/23] landlock: Log TCP bind and connect denials Mickaël Salaün
` (7 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Add audit support to the file_truncate and file_ioctl hooks.
Add a deny_masks_t type and related helpers to store the domain's layer
level per optional access rights (i.e. FS_TRUNCATE and FS_IOCTL_DEV)
when opening a file, which cannot be inferred later.
Add KUnit tests.
Audit event sample:
type=LL_DENY [...]: domain=195ba459b blockers=fs_ioctl_dev path="/dev/tty" dev="devtmpfs" ino=9
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-17-mic@digikod.net
---
Changes since v2:
- Fix !CONFIG_AUDIT build warning.
- Rename ACCESS_FS_OPTIONAL to _LANDLOCK_ACCESS_FS_OPTIONAL.
---
security/landlock/access.h | 23 +++++++
security/landlock/audit.c | 102 ++++++++++++++++++++++++++--
security/landlock/audit.h | 4 ++
security/landlock/domain.c | 133 +++++++++++++++++++++++++++++++++++++
security/landlock/domain.h | 8 +++
security/landlock/fs.c | 51 ++++++++++++++
security/landlock/fs.h | 9 +++
7 files changed, 325 insertions(+), 5 deletions(-)
diff --git a/security/landlock/access.h b/security/landlock/access.h
index 1e302775cc23..99125c7f9732 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 8a7657d12478..e51c8ea14aec 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -13,10 +13,12 @@
#include <linux/uidgid.h>
#include <uapi/linux/landlock.h>
+#include "access.h"
#include "audit.h"
#include "common.h"
#include "cred.h"
#include "domain.h"
+#include "fs.h"
#include "ruleset.h"
static const char *const fs_access_strings[] = {
@@ -252,22 +254,111 @@ static void test_get_denied_layer(struct kunit *const test)
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+static size_t
+get_level_from_deny_masks(access_mask_t *const access_request,
+ const access_mask_t all_existing_optional_access,
+ const deny_masks_t deny_masks)
+{
+ const unsigned long access_opt = all_existing_optional_access;
+ const unsigned long access_req = *access_request;
+ access_mask_t missing = 0;
+ size_t youngest_layer = 0;
+ size_t access_index = 0;
+ unsigned long access_bit;
+
+ /* This will require change with new object types. */
+ WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
+
+ for_each_set_bit(access_bit, &access_opt,
+ BITS_PER_TYPE(access_mask_t)) {
+ if (access_req & BIT(access_bit)) {
+ const size_t layer =
+ (deny_masks >> (access_index * 4)) &
+ (LANDLOCK_MAX_NUM_LAYERS - 1);
+
+ if (layer > youngest_layer) {
+ youngest_layer = layer;
+ missing = BIT(access_bit);
+ } else if (layer == youngest_layer) {
+ missing |= BIT(access_bit);
+ }
+ }
+ access_index++;
+ }
+
+ *access_request = missing;
+ return youngest_layer;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_level_from_deny_masks(struct kunit *const test)
+{
+ deny_masks_t deny_mask;
+ access_mask_t access;
+
+ /* truncate:0 ioctl_dev:2 */
+ deny_mask = 0x20;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_level_from_deny_masks(&access,
+ _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_level_from_deny_masks(&access,
+ _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+
+ /* truncate:15 ioctl_dev:15 */
+ deny_mask = 0xff;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_level_from_deny_masks(&access,
+ _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_level_from_deny_masks(&access,
+ _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask));
+ KUNIT_EXPECT_EQ(test, access,
+ LANDLOCK_ACCESS_FS_TRUNCATE |
+ LANDLOCK_ACCESS_FS_IOCTL_DEV);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
static bool is_valid_request(const struct landlock_request *const request)
{
if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
return false;
if (request->access) {
- if (WARN_ON_ONCE(!request->layer_masks))
+ if (WARN_ON_ONCE(!(!!request->layer_masks ^
+ !!request->all_existing_optional_access)))
return false;
} else {
- if (WARN_ON_ONCE(request->layer_masks))
+ if (WARN_ON_ONCE(request->layer_masks ||
+ request->all_existing_optional_access))
return false;
}
if (WARN_ON_ONCE(!!request->layer_masks ^ !!request->layer_masks_size))
return false;
+ if (request->deny_masks) {
+ if (WARN_ON_ONCE(!request->all_existing_optional_access))
+ return false;
+ }
+
return true;
}
@@ -308,9 +399,9 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
domain, &missing, request->layer_masks,
request->layer_masks_size);
} else {
- /* This will change with the next commit. */
- WARN_ON_ONCE(1);
- youngest_layer = domain->num_layers;
+ youngest_layer = get_level_from_deny_masks(
+ &missing, request->all_existing_optional_access,
+ request->deny_masks);
}
youngest_denied = get_hierarchy(domain, youngest_layer);
} else {
@@ -366,6 +457,7 @@ static struct kunit_case test_cases[] = {
/* clang-format off */
KUNIT_CASE(test_get_hierarchy),
KUNIT_CASE(test_get_denied_layer),
+ KUNIT_CASE(test_get_level_from_deny_masks),
{}
/* clang-format on */
};
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 25fc8333cddc..320394fd6b84 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -42,6 +42,10 @@ struct landlock_request {
/* Required fields for requests with layer masks. */
const layer_mask_t (*layer_masks)[];
size_t layer_masks_size;
+
+ /* Required fields for requests with deny masks. */
+ const access_mask_t all_existing_optional_access;
+ deny_masks_t deny_masks;
};
#ifdef CONFIG_AUDIT
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 69b15c059583..4b3b82c88815 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -5,7 +5,10 @@
* Copyright © 2024 Microsoft Corporation
*/
+#include <kunit/test.h>
#include <linux/audit.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
#include <linux/cred.h>
#include <linux/file.h>
#include <linux/mm.h>
@@ -14,6 +17,8 @@
#include <linux/sched.h>
#include <linux/timekeeping.h>
+#include "access.h"
+#include "common.h"
#include "domain.h"
#include "id.h"
@@ -60,3 +65,131 @@ void landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
hierarchy->exe = get_current_exe();
get_task_comm(hierarchy->comm, current);
}
+
+static deny_masks_t
+get_layer_deny_mask(const access_mask_t all_existing_optional_access,
+ const unsigned long access_bit, const size_t layer)
+{
+ unsigned long access_weight;
+
+ /* This may require change with new object types. */
+ WARN_ON_ONCE(all_existing_optional_access !=
+ _LANDLOCK_ACCESS_FS_OPTIONAL);
+
+ if (WARN_ON_ONCE(layer >= LANDLOCK_MAX_NUM_LAYERS))
+ return 0;
+
+ access_weight = hweight_long(all_existing_optional_access &
+ GENMASK(access_bit, 0));
+ if (WARN_ON_ONCE(access_weight < 1))
+ return 0;
+
+ return layer
+ << ((access_weight - 1) * HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1));
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_layer_deny_mask(struct kunit *const test)
+{
+ const unsigned long truncate = BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE);
+ const unsigned long ioctl_dev = BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV);
+
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ truncate, 0));
+ KUNIT_EXPECT_EQ(test, 0x3,
+ get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ truncate, 3));
+
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ ioctl_dev, 0));
+ KUNIT_EXPECT_EQ(test, 0xf0,
+ get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ ioctl_dev, 15));
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+deny_masks_t
+landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
+ const access_mask_t optional_access,
+ const layer_mask_t (*const layer_masks)[],
+ const size_t layer_masks_size)
+{
+ const unsigned long access_opt = optional_access;
+ unsigned long access_bit;
+ deny_masks_t deny_masks = 0;
+
+ /* This may require change with new object types. */
+ WARN_ON_ONCE(access_opt !=
+ (optional_access & all_existing_optional_access));
+
+ if (WARN_ON_ONCE(!layer_masks))
+ return 0;
+
+ if (WARN_ON_ONCE(!access_opt))
+ return 0;
+
+ for_each_set_bit(access_bit, &access_opt, layer_masks_size) {
+ const layer_mask_t mask = (*layer_masks)[access_bit];
+
+ if (!mask)
+ continue;
+
+ /* __fls(1) == 0 */
+ deny_masks |= get_layer_deny_mask(all_existing_optional_access,
+ access_bit, __fls(mask));
+ }
+ return deny_masks;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_landlock_get_deny_masks(struct kunit *const test)
+{
+ const layer_mask_t layers1[BITS_PER_TYPE(access_mask_t)] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0) |
+ BIT_ULL(9),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = BIT_ULL(1),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = BIT_ULL(2) |
+ BIT_ULL(0),
+ };
+
+ KUNIT_EXPECT_EQ(test, 0x1,
+ landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ LANDLOCK_ACCESS_FS_TRUNCATE,
+ &layers1, ARRAY_SIZE(layers1)));
+ KUNIT_EXPECT_EQ(test, 0x20,
+ landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ LANDLOCK_ACCESS_FS_IOCTL_DEV,
+ &layers1, ARRAY_SIZE(layers1)));
+ KUNIT_EXPECT_EQ(
+ test, 0x21,
+ landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ LANDLOCK_ACCESS_FS_TRUNCATE |
+ LANDLOCK_ACCESS_FS_IOCTL_DEV,
+ &layers1, ARRAY_SIZE(layers1)));
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static struct kunit_case test_cases[] = {
+ /* clang-format off */
+ KUNIT_CASE(test_get_layer_deny_mask),
+ KUNIT_CASE(test_landlock_get_deny_masks),
+ {}
+ /* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+ .name = "landlock_domain",
+ .test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 4a2864a66047..ffc8b1bb58be 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -18,6 +18,8 @@
#include <linux/sched.h>
#include <linux/time64.h>
+#include "access.h"
+
enum landlock_log_status {
LANDLOCK_LOG_PENDING = 0,
LANDLOCK_LOG_RECORDED,
@@ -98,6 +100,12 @@ static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
#ifdef CONFIG_AUDIT
void landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy);
+
+deny_masks_t
+landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
+ const access_mask_t optional_access,
+ const layer_mask_t (*const layer_masks)[],
+ size_t layer_masks_size);
#else /* CONFIG_AUDIT */
static inline void
landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index f38c7a60b9bf..6404961ecbc7 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -246,6 +246,17 @@ is_masked_device_ioctl_compat(const unsigned int cmd)
}
}
+static void
+update_request(struct landlock_request *const request,
+ const struct landlock_file_security *const file_security,
+ const access_mask_t access)
+{
+ request->access = access;
+#ifdef CONFIG_AUDIT
+ request->deny_masks = file_security->deny_masks;
+#endif /* CONFIG_AUDIT */
+}
+
/* Ruleset management */
static struct landlock_object *get_inode_object(struct inode *const inode)
@@ -1653,6 +1664,11 @@ static int hook_file_open(struct file *const file)
* file access rights in the opened struct file.
*/
landlock_file(file)->allowed_access = allowed_access;
+#ifdef CONFIG_AUDIT
+ landlock_file(file)->deny_masks = landlock_get_deny_masks(
+ _LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks,
+ ARRAY_SIZE(layer_masks));
+#endif /* CONFIG_AUDIT */
if ((open_access_request & allowed_access) == open_access_request)
return 0;
@@ -1665,6 +1681,15 @@ static int hook_file_open(struct file *const file)
static int hook_file_truncate(struct file *const file)
{
+ struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_FS_ACCESS,
+ .audit = {
+ .type = LSM_AUDIT_DATA_FILE,
+ .u.file = file,
+ },
+ .all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
+ };
+
/*
* Allows truncation if the truncate right was available at the time of
* opening the file, to get a consistent access check as for read, write
@@ -1677,12 +1702,24 @@ static int hook_file_truncate(struct file *const file)
*/
if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE)
return 0;
+
+ update_request(&request, landlock_file(file),
+ LANDLOCK_ACCESS_FS_TRUNCATE);
+ landlock_log_denial(landlock_cred(file->f_cred)->domain, &request);
return -EACCES;
}
static int hook_file_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
+ struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_FS_ACCESS,
+ .audit = {
+ .type = LSM_AUDIT_DATA_FILE,
+ .u.file = file,
+ },
+ .all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
+ };
access_mask_t allowed_access = landlock_file(file)->allowed_access;
/*
@@ -1700,12 +1737,23 @@ static int hook_file_ioctl(struct file *file, unsigned int cmd,
if (is_masked_device_ioctl(cmd))
return 0;
+ update_request(&request, landlock_file(file),
+ LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ landlock_log_denial(landlock_cred(file->f_cred)->domain, &request);
return -EACCES;
}
static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
unsigned long arg)
{
+ struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_FS_ACCESS,
+ .audit = {
+ .type = LSM_AUDIT_DATA_FILE,
+ .u.file = file,
+ },
+ .all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
+ };
access_mask_t allowed_access = landlock_file(file)->allowed_access;
/*
@@ -1723,6 +1771,9 @@ static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
if (is_masked_device_ioctl_compat(cmd))
return 0;
+ update_request(&request, landlock_file(file),
+ LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ landlock_log_denial(landlock_cred(file->f_cred)->domain, &request);
return -EACCES;
}
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index d445f411c26a..9f52c9b37898 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -53,6 +53,15 @@ struct landlock_file_security {
* needed to authorize later operations on the open file.
*/
access_mask_t allowed_access;
+
+#ifdef CONFIG_AUDIT
+ /**
+ * @deny_masks: Domain layer levels that deny an optional access (see
+ * _LANDLOCK_ACCESS_FS_OPTIONAL).
+ */
+ deny_masks_t deny_masks;
+#endif /* CONFIG_AUDIT */
+
/**
* @fown_domain: Domain of the task that set the PID that may receive a
* signal e.g., SIGURG when writing MSG_OOB to the related socket.
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 17/23] landlock: Log TCP bind and connect denials
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (15 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 16/23] landlock: Log truncate and ioctl denials Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2025-01-05 1:23 ` Paul Moore
2024-11-22 14:33 ` [PATCH v3 18/23] landlock: Log scoped denials Mickaël Salaün
` (6 subsequent siblings)
23 siblings, 1 reply; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Add audit support to socket_bind and socket_connect hooks.
Audit event sample:
type=LL_DENY [...]: domain=195ba459b blockers=net_connect_tcp daddr=127.0.0.1 dest=80
Cc: Günther Noack <gnoack@google.com>
Cc: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-18-mic@digikod.net
---
Changes since v2:
- Remove potentially superfluous IPv6 saddr log, spotted by Francis
Laniel.
- Cosmetic improvements.
---
security/landlock/audit.c | 12 +++++++++
security/landlock/audit.h | 1 +
security/landlock/net.c | 51 ++++++++++++++++++++++++++++++++++++---
3 files changed, 60 insertions(+), 4 deletions(-)
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index e51c8ea14aec..0b69b4c9ba1c 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -42,6 +42,13 @@ static const char *const fs_access_strings[] = {
static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
+static const char *const net_access_strings[] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net_bind_tcp",
+ [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net_connect_tcp",
+};
+
+static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);
+
static __attribute_const__ const char *
get_blocker(const enum landlock_request_type type,
const unsigned long access_bit)
@@ -59,6 +66,11 @@ get_blocker(const enum landlock_request_type type,
if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings)))
return "unknown";
return fs_access_strings[access_bit];
+
+ case LANDLOCK_REQUEST_NET_ACCESS:
+ if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(net_access_strings)))
+ return "unknown";
+ return net_access_strings[access_bit];
}
WARN_ON_ONCE(1);
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 320394fd6b84..1075b0c8401f 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -18,6 +18,7 @@ enum landlock_request_type {
LANDLOCK_REQUEST_PTRACE = 1,
LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
LANDLOCK_REQUEST_FS_ACCESS,
+ LANDLOCK_REQUEST_NET_ACCESS,
};
/*
diff --git a/security/landlock/net.c b/security/landlock/net.c
index d5dcc4407a19..45c80fa417c4 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -7,10 +7,12 @@
*/
#include <linux/in.h>
+#include <linux/lsm_audit.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <net/ipv6.h>
+#include "audit.h"
#include "common.h"
#include "cred.h"
#include "limits.h"
@@ -57,6 +59,10 @@ static int current_check_access_socket(struct socket *const sock,
const struct landlock_ruleset *const dom =
landlock_get_applicable_domain(landlock_get_current_domain(),
any_net);
+ struct lsm_network_audit audit_net = {};
+ struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_NET_ACCESS,
+ };
if (!dom)
return 0;
@@ -73,18 +79,48 @@ static int current_check_access_socket(struct socket *const sock,
switch (address->sa_family) {
case AF_UNSPEC:
- case AF_INET:
+ case AF_INET: {
+ const struct sockaddr_in *addr4;
+
if (addrlen < sizeof(struct sockaddr_in))
return -EINVAL;
- port = ((struct sockaddr_in *)address)->sin_port;
+
+ addr4 = (struct sockaddr_in *)address;
+ port = addr4->sin_port;
+
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+ audit_net.dport = port;
+ audit_net.v4info.daddr = addr4->sin_addr.s_addr;
+ } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+ audit_net.sport = port;
+ audit_net.v4info.saddr = addr4->sin_addr.s_addr;
+ } else {
+ WARN_ON_ONCE(1);
+ }
break;
+ }
#if IS_ENABLED(CONFIG_IPV6)
- case AF_INET6:
+ case AF_INET6: {
+ const struct sockaddr_in6 *addr6;
+
if (addrlen < SIN6_LEN_RFC2133)
return -EINVAL;
- port = ((struct sockaddr_in6 *)address)->sin6_port;
+
+ addr6 = (struct sockaddr_in6 *)address;
+ port = addr6->sin6_port;
+
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+ audit_net.dport = port;
+ audit_net.v6info.daddr = addr6->sin6_addr;
+ } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+ audit_net.sport = port;
+ audit_net.v6info.saddr = addr6->sin6_addr;
+ } else {
+ WARN_ON_ONCE(1);
+ }
break;
+ }
#endif /* IS_ENABLED(CONFIG_IPV6) */
default:
@@ -153,6 +189,13 @@ static int current_check_access_socket(struct socket *const sock,
ARRAY_SIZE(layer_masks)))
return 0;
+ audit_net.family = address->sa_family;
+ request.audit.type = LSM_AUDIT_DATA_NET;
+ request.audit.u.net = &audit_net;
+ request.access = access_request;
+ request.layer_masks = &layer_masks;
+ request.layer_masks_size = ARRAY_SIZE(layer_masks);
+ landlock_log_denial(dom, &request);
return -EACCES;
}
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 18/23] landlock: Log scoped denials
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (16 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 17/23] landlock: Log TCP bind and connect denials Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2025-01-05 1:23 ` Paul Moore
2024-11-22 14:33 ` [PATCH v3 19/23] landlock: Control log events with LANDLOCK_RESTRICT_SELF_LOGLESS Mickaël Salaün
` (5 subsequent siblings)
23 siblings, 1 reply; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Add audit support for unix_stream_connect, unix_may_send, task_kill, and
file_send_sigiotask hooks.
Audit event sample:
type=LL_DENY [...]: domain=195ba459b blockers=scope_abstract_unix_socket path=00666F6F
Cc: Günther Noack <gnoack@google.com>
Cc: Tahera Fahimi <fahimitahera@gmail.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-19-mic@digikod.net
---
Changes since v1:
- New patch.
---
security/landlock/audit.c | 8 ++++++
security/landlock/audit.h | 2 ++
security/landlock/task.c | 58 ++++++++++++++++++++++++++++++++++++---
3 files changed, 64 insertions(+), 4 deletions(-)
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 0b69b4c9ba1c..7e4acc746cd6 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -71,6 +71,14 @@ get_blocker(const enum landlock_request_type type,
if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(net_access_strings)))
return "unknown";
return net_access_strings[access_bit];
+
+ case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
+ WARN_ON_ONCE(access_bit != -1);
+ return "scope_abstract_unix_socket";
+
+ case LANDLOCK_REQUEST_SCOPE_SIGNAL:
+ WARN_ON_ONCE(access_bit != -1);
+ return "scope_signal";
}
WARN_ON_ONCE(1);
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 1075b0c8401f..1e0a9164bacc 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -19,6 +19,8 @@ enum landlock_request_type {
LANDLOCK_REQUEST_FS_CHANGE_LAYOUT,
LANDLOCK_REQUEST_FS_ACCESS,
LANDLOCK_REQUEST_NET_ACCESS,
+ LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
+ LANDLOCK_REQUEST_SCOPE_SIGNAL,
};
/*
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 1decd6f114e8..bdf7adc14e32 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -263,13 +263,27 @@ static int hook_unix_stream_connect(struct sock *const sock,
const struct landlock_ruleset *const dom =
landlock_get_applicable_domain(landlock_get_current_domain(),
unix_scope);
+ struct lsm_network_audit audit_net = {
+ .sk = other,
+ };
+ struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
+ .audit = {
+ .type = LSM_AUDIT_DATA_NET,
+ .u.net = &audit_net,
+ },
+ };
/* Quick return for non-landlocked tasks. */
if (!dom)
return 0;
- if (is_abstract_socket(other) && sock_is_scoped(other, dom))
+ if (is_abstract_socket(other) && sock_is_scoped(other, dom)) {
+ request.layer_plus_one =
+ landlock_match_layer_level(dom, unix_scope) + 1;
+ landlock_log_denial(dom, &request);
return -EPERM;
+ }
return 0;
}
@@ -280,6 +294,16 @@ static int hook_unix_may_send(struct socket *const sock,
const struct landlock_ruleset *const dom =
landlock_get_applicable_domain(landlock_get_current_domain(),
unix_scope);
+ struct lsm_network_audit audit_net = {
+ .sk = other->sk,
+ };
+ struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
+ .audit = {
+ .type = LSM_AUDIT_DATA_NET,
+ .u.net = &audit_net,
+ },
+ };
if (!dom)
return 0;
@@ -291,8 +315,12 @@ static int hook_unix_may_send(struct socket *const sock,
if (unix_peer(sock->sk) == other->sk)
return 0;
- if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom))
+ if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom)) {
+ request.layer_plus_one =
+ landlock_match_layer_level(dom, unix_scope) + 1;
+ landlock_log_denial(dom, &request);
return -EPERM;
+ }
return 0;
}
@@ -307,6 +335,13 @@ static int hook_task_kill(struct task_struct *const p,
{
bool is_scoped;
const struct landlock_ruleset *dom;
+ struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_SCOPE_SIGNAL,
+ .audit = {
+ .type = LSM_AUDIT_DATA_TASK,
+ .u.tsk = p,
+ },
+ };
if (cred) {
/* Dealing with USB IO. */
@@ -324,8 +359,12 @@ static int hook_task_kill(struct task_struct *const p,
is_scoped = domain_is_scoped(dom, landlock_get_task_domain(p),
LANDLOCK_SCOPE_SIGNAL);
rcu_read_unlock();
- if (is_scoped)
+ if (is_scoped) {
+ request.layer_plus_one =
+ landlock_match_layer_level(dom, signal_scope) + 1;
+ landlock_log_denial(dom, &request);
return -EPERM;
+ }
return 0;
}
@@ -334,6 +373,13 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
struct fown_struct *fown, int signum)
{
const struct landlock_ruleset *dom;
+ struct landlock_request request = {
+ .type = LANDLOCK_REQUEST_SCOPE_SIGNAL,
+ .audit = {
+ .type = LSM_AUDIT_DATA_TASK,
+ .u.tsk = tsk,
+ },
+ };
bool is_scoped = false;
/* Lock already held by send_sigio() and send_sigurg(). */
@@ -349,8 +395,12 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
is_scoped = domain_is_scoped(dom, landlock_get_task_domain(tsk),
LANDLOCK_SCOPE_SIGNAL);
rcu_read_unlock();
- if (is_scoped)
+ if (is_scoped) {
+ request.layer_plus_one =
+ landlock_match_layer_level(dom, signal_scope) + 1;
+ landlock_log_denial(dom, &request);
return -EPERM;
+ }
return 0;
}
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 19/23] landlock: Control log events with LANDLOCK_RESTRICT_SELF_LOGLESS
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (17 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 18/23] landlock: Log scoped denials Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 20/23] samples/landlock: Do not log denials from the sandboxer by default Mickaël Salaün
` (4 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Most of the time we want to log denied access because they should not
happen and such information helps diagnose issues. However, when
sandboxing processes that we know will try to access denied resources
(e.g. unknown, bogus, or malicious binary), we might want to not log
related access requests that might fill up logs.
To disable any log for a specific Landlock domain, add a
LANDLOCK_RESTRICT_SELF_LOGLESS optional flag to the
landlock_restrict_self() system call.
Because this flag is set for a specific Landlock domain, it makes it
possible to selectively mask some access requests that would be logged
by a parent domain, which might be handy for unprivileged processes to
limit logs. However, system administrators should still use the audit
filtering mechanism.
There is intentionally no audit nor sysctl configuration to re-enable
these logless domains. This is delegated to the user space program.
Increment the Landlock ABI version to reflect this interface change.
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Closes: https://github.com/landlock-lsm/linux/issues/3
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-20-mic@digikod.net
---
Changes since v2:
- Update ABI version test.
---
Documentation/userspace-api/landlock.rst | 2 +-
include/uapi/linux/landlock.h | 14 +++++++++++
security/landlock/audit.c | 13 ++++++----
security/landlock/domain.h | 1 +
security/landlock/syscalls.c | 25 ++++++++++++++++----
tools/testing/selftests/landlock/base_test.c | 2 +-
6 files changed, 45 insertions(+), 12 deletions(-)
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index d639c61cb472..bd0696e2ea41 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -8,7 +8,7 @@ Landlock: unprivileged access control
=====================================
:Author: Mickaël Salaün
-:Date: October 2024
+:Date: November 2024
The goal of Landlock is to enable restriction of ambient rights (e.g. global
filesystem or network access) for a set of processes. Because Landlock
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 33745642f787..3b31d373ef74 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -62,6 +62,20 @@ struct landlock_ruleset_attr {
#define LANDLOCK_CREATE_RULESET_VERSION (1U << 0)
/* clang-format on */
+/*
+ * sys_landlock_restrict_self() flags:
+ *
+ * - %LANDLOCK_RESTRICT_SELF_LOGLESS: Do not create any log related to the
+ * enforced restrictions. This should only be set by tools launching unknown
+ * or untrusted programs (e.g. a sandbox tool, container runtime, system
+ * service manager). Because programs sandboxing themselves should fix any
+ * denied access, they should not set this flag to be aware of potential
+ * issues reported by system's logs (i.e. audit).
+ */
+/* clang-format off */
+#define LANDLOCK_RESTRICT_SELF_LOGLESS (1U << 0)
+/* clang-format on */
+
/**
* enum landlock_rule_type - Landlock rule type
*
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 7e4acc746cd6..d299baedd4e4 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -404,11 +404,6 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
if (!audit_enabled)
return;
- ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
- AUDIT_LANDLOCK_DENY);
- if (!ab)
- return;
-
missing = request->access;
if (missing) {
size_t youngest_layer;
@@ -429,6 +424,14 @@ void landlock_log_denial(const struct landlock_ruleset *const domain,
get_hierarchy(domain, request->layer_plus_one - 1);
}
+ if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
+ return;
+
+ ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
+ AUDIT_LANDLOCK_DENY);
+ if (!ab)
+ return;
+
audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
log_blockers(ab, request->type, missing);
audit_log_lsm_data(ab, &request->audit);
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index ffc8b1bb58be..fe4b2de734d2 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -23,6 +23,7 @@
enum landlock_log_status {
LANDLOCK_LOG_PENDING = 0,
LANDLOCK_LOG_RECORDED,
+ LANDLOCK_LOG_DISABLED,
};
/**
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 335067e36feb..48c26ed8c099 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -151,7 +151,12 @@ static const struct file_operations ruleset_fops = {
.write = fop_dummy_write,
};
-#define LANDLOCK_ABI_VERSION 6
+/*
+ * The Landlock ABI version should be incremented for each new Landlock-related
+ * user space visible change (e.g. Landlock syscalls). Only increment this
+ * version once per Linux release.
+ */
+#define LANDLOCK_ABI_VERSION 7
/**
* sys_landlock_create_ruleset - Create a new ruleset
@@ -452,7 +457,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
* sys_landlock_restrict_self - Enforce a ruleset on the calling thread
*
* @ruleset_fd: File descriptor tied to the ruleset to merge with the target.
- * @flags: Must be 0.
+ * @flags: Supported value: %LANDLOCK_RESTRICT_SELF_LOGLESS.
*
* This system call enables to enforce a Landlock ruleset on the current
* thread. Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
@@ -478,6 +483,7 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
struct cred *new_cred;
struct landlock_cred_security *new_llcred;
int err;
+ bool is_logless = false;
if (!is_initialized())
return -EOPNOTSUPP;
@@ -490,9 +496,12 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
!ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
return -EPERM;
- /* No flag for now. */
- if (flags)
- return -EINVAL;
+ if (flags) {
+ if (flags == LANDLOCK_RESTRICT_SELF_LOGLESS)
+ is_logless = true;
+ else
+ return -EINVAL;
+ }
/* Gets and checks the ruleset. */
ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ);
@@ -517,6 +526,12 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
goto out_put_creds;
}
+ if (is_logless) {
+#ifdef CONFIG_AUDIT
+ new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
+#endif /* CONFIG_AUDIT */
+ }
+
/* Replaces the old (prepared) domain. */
landlock_put_ruleset(new_llcred->domain);
new_llcred->domain = new_dom;
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 1bc16fde2e8a..fbd687691b3c 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,7 +76,7 @@ TEST(abi_version)
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
};
- ASSERT_EQ(6, landlock_create_ruleset(NULL, 0,
+ ASSERT_EQ(7, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 20/23] samples/landlock: Do not log denials from the sandboxer by default
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (18 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 19/23] landlock: Control log events with LANDLOCK_RESTRICT_SELF_LOGLESS Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-12-20 14:36 ` Francis Laniel
2024-11-22 14:33 ` [PATCH v3 21/23] selftests/landlock: Extend tests for landlock_restrict_self()'s flags Mickaël Salaün
` (3 subsequent siblings)
23 siblings, 1 reply; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Do not pollute audit logs because of unknown sandboxed programs.
Indeed, the sandboxer's security policy might not be fitted to the set
of sandboxed processes that could be spawned (e.g. from a shell).
The LANDLOCK_RESTRICT_SELF_LOGLESS flag should be used for all similar
sandboxer tools by default. Only natively-sandboxed programs should not
use this flag.
For test purpose, parse the LL_FORCE_LOG environment variable to still
log denials.
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-21-mic@digikod.net
---
Changes since v2:
- New patch.
---
samples/landlock/sandboxer.c | 35 ++++++++++++++++++++++++++++++++---
1 file changed, 32 insertions(+), 3 deletions(-)
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 57565dfd74a2..49fd4fdb1dcf 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -58,6 +58,7 @@ static inline int landlock_restrict_self(const int ruleset_fd,
#define ENV_TCP_BIND_NAME "LL_TCP_BIND"
#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
#define ENV_SCOPED_NAME "LL_SCOPED"
+#define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
#define ENV_DELIMITER ":"
static int str2num(const char *numstr, __u64 *num_dst)
@@ -288,7 +289,7 @@ static bool check_ruleset_scope(const char *const env_var,
/* clang-format on */
-#define LANDLOCK_ABI_LAST 6
+#define LANDLOCK_ABI_LAST 7
#define XSTR(s) #s
#define STR(s) XSTR(s)
@@ -315,6 +316,9 @@ static const char help[] =
" - \"a\" to restrict opening abstract unix sockets\n"
" - \"s\" to restrict sending signals\n"
"\n"
+ "A sandboxer should not log denied access requests to avoid spamming logs, "
+ "but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
+ "\n"
"Example:\n"
ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
@@ -333,7 +337,7 @@ int main(const int argc, char *const argv[], char *const *const envp)
const char *cmd_path;
char *const *cmd_argv;
int ruleset_fd, abi;
- char *env_port_name;
+ char *env_port_name, *env_force_log;
__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
@@ -344,6 +348,8 @@ int main(const int argc, char *const argv[], char *const *const envp)
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL,
};
+ /* Do not pollute audit logs because of unknown sandboxed programs. */
+ int restrict_flags = LANDLOCK_RESTRICT_SELF_LOGLESS;
if (argc < 2) {
fprintf(stderr, help, argv[0]);
@@ -415,6 +421,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
/* Removes LANDLOCK_SCOPE_* for ABI < 6 */
ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL);
+ __attribute__((fallthrough));
+ case 6:
+ /* Removes LANDLOCK_RESTRICT_SELF_LOGLESS for ABI < 7 */
+ restrict_flags &= ~LANDLOCK_RESTRICT_SELF_LOGLESS;
+
+ /* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
fprintf(stderr,
"Hint: You should update the running kernel "
"to leverage Landlock features "
@@ -449,6 +461,23 @@ int main(const int argc, char *const argv[], char *const *const envp)
if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
return 1;
+ /* Enables optional logs. */
+ env_force_log = getenv(ENV_FORCE_LOG_NAME);
+ if (env_force_log) {
+ if (strcmp(env_force_log, "1") != 0) {
+ fprintf(stderr,
+ "Unknown value for " ENV_FORCE_LOG_NAME "\n");
+ return 1;
+ }
+ if (!(restrict_flags & LANDLOCK_RESTRICT_SELF_LOGLESS)) {
+ fprintf(stderr,
+ "Audit logs not supported by current kernel\n");
+ return 1;
+ }
+ restrict_flags &= ~LANDLOCK_RESTRICT_SELF_LOGLESS;
+ unsetenv(ENV_FORCE_LOG_NAME);
+ }
+
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) {
@@ -476,7 +505,7 @@ int main(const int argc, char *const argv[], char *const *const envp)
perror("Failed to restrict privileges");
goto err_close_ruleset;
}
- if (landlock_restrict_self(ruleset_fd, 0)) {
+ if (landlock_restrict_self(ruleset_fd, restrict_flags)) {
perror("Failed to enforce ruleset");
goto err_close_ruleset;
}
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 21/23] selftests/landlock: Extend tests for landlock_restrict_self()'s flags
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (19 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 20/23] samples/landlock: Do not log denials from the sandboxer by default Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 22/23] selftests/landlock: Add tests for audit Mickaël Salaün
` (2 subsequent siblings)
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Add the restrict_self_flags test suite to check that
LANDLOCK_RESTRICT_SELF_LOGLESS is valid but not the next bit. Some
checks are similar to restrict_self_checks_ordering's ones.
Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-22-mic@digikod.net
---
Changes since v2:
- New patch.
---
tools/testing/selftests/landlock/base_test.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index fbd687691b3c..4e0104f1efa0 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -233,6 +233,22 @@ TEST(restrict_self_checks_ordering)
ASSERT_EQ(0, close(ruleset_fd));
}
+TEST(restrict_self_flags)
+{
+ ASSERT_EQ(-1, landlock_restrict_self(-1, 0));
+ ASSERT_EQ(EBADF, errno);
+
+ ASSERT_EQ(-1,
+ landlock_restrict_self(-1, LANDLOCK_RESTRICT_SELF_LOGLESS));
+ ASSERT_EQ(EBADF, errno);
+
+ ASSERT_EQ(-1, landlock_restrict_self(-1, 1U << 1));
+ ASSERT_EQ(EINVAL, errno);
+
+ ASSERT_EQ(-1, landlock_restrict_self(-1, -1));
+ ASSERT_EQ(EINVAL, errno);
+}
+
TEST(ruleset_fd_io)
{
struct landlock_ruleset_attr ruleset_attr = {
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 22/23] selftests/landlock: Add tests for audit
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (20 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 21/23] selftests/landlock: Extend tests for landlock_restrict_self()'s flags Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 23/23] selftests/landlock: Add audit tests for ptrace Mickaël Salaün
2024-12-20 14:36 ` [PATCH v3 00/23] Landlock audit support Francis Laniel
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Add audit_test.c to check with and without
LANDLOCK_RESTRICT_SELF_LOGLESS against the three Landlock audit record
types: AUDIT_LANDLOCK_DENY, AUDIT_LANDLOCK_DOM_INFO, and
AUDIT_LANDLOCK_DOM_DROP.
Tests are run with audit filters to ensure the audit records come from
the running tests. Moreover, because there can only be one audit
process, tests would failed if run in parallel. Because of audit
limitations, tests can only be run in the initial namespace.
The audit test helpers were inspired by libaudit and
tools/testing/selftests/net/netfilter/audit_logread.c
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Phil Sutter <phil@nwl.cc>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-23-mic@digikod.net
---
Changes since v2:
- New patch.
---
tools/testing/selftests/landlock/audit.h | 308 ++++++++++++++++++
tools/testing/selftests/landlock/audit_test.c | 168 ++++++++++
tools/testing/selftests/landlock/common.h | 2 +
tools/testing/selftests/landlock/config | 1 +
4 files changed, 479 insertions(+)
create mode 100644 tools/testing/selftests/landlock/audit.h
create mode 100644 tools/testing/selftests/landlock/audit_test.c
diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h
new file mode 100644
index 000000000000..4977e8fac081
--- /dev/null
+++ b/tools/testing/selftests/landlock/audit.h
@@ -0,0 +1,308 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Landlock audit helpers
+ *
+ * Copyright © 2024 Microsoft Corporation
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <linux/audit.h>
+#include <linux/limits.h>
+#include <linux/netlink.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#define REGEX_LANDLOCK_PREFIX "^audit([0-9.:]\\+): domain=[0-9a-f]\\+"
+
+struct audit_state {
+ int fd;
+ size_t exe_len;
+ char exe[PATH_MAX];
+};
+
+struct audit_message {
+ struct nlmsghdr header;
+ union {
+ struct audit_status status;
+ struct audit_features features;
+ struct audit_rule_data rule;
+ struct nlmsgerr err;
+ char data[PATH_MAX + 200];
+ };
+};
+
+static int audit_send(const int fd, const struct audit_message *const msg)
+{
+ struct sockaddr_nl addr = {
+ .nl_family = AF_NETLINK,
+ };
+ int ret;
+
+ do {
+ ret = sendto(fd, msg, msg->header.nlmsg_len, 0,
+ (struct sockaddr *)&addr, sizeof(addr));
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret < 0)
+ return -errno;
+
+ if (ret != msg->header.nlmsg_len)
+ return -E2BIG;
+
+ return 0;
+}
+
+static int audit_recv(const int fd, struct audit_message *const msg)
+{
+ struct sockaddr_nl addr;
+ socklen_t addrlen = sizeof(addr);
+ int err;
+
+ do {
+ err = recvfrom(fd, msg, sizeof(*msg), 0,
+ (struct sockaddr *)&addr, &addrlen);
+ } while (err < 0 && errno == EINTR);
+
+ if (err < 0)
+ return -errno;
+
+ if (addrlen != sizeof(addr) || addr.nl_pid != 0)
+ return -EINVAL;
+
+ if (msg->header.nlmsg_type == NLMSG_ERROR && msg->err.error)
+ return msg->err.error;
+
+ return 0;
+}
+
+static int audit_request(const int fd,
+ const struct audit_message *const request,
+ struct audit_message *reply)
+{
+ struct audit_message msg;
+ int ret;
+
+ ret = audit_send(fd, request);
+ if (ret)
+ return ret;
+
+ if (!reply)
+ reply = &msg;
+
+ return audit_recv(fd, reply);
+}
+
+static int audit_filter_exe(const struct audit_state *const state,
+ const __u16 type)
+{
+ struct audit_message msg = {
+ .header = {
+ .nlmsg_len = NLMSG_SPACE(sizeof(msg.rule)) +
+ NLMSG_ALIGN(state->exe_len),
+ .nlmsg_type = type,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+ },
+ .rule = {
+ .flags = AUDIT_FILTER_EXCLUDE,
+ .action = AUDIT_NEVER,
+ .field_count = 1,
+ .fields[0] = AUDIT_EXE,
+ .fieldflags[0] = AUDIT_NOT_EQUAL,
+ .values[0] = state->exe_len,
+ .buflen = state->exe_len,
+ }
+ };
+
+ memcpy(msg.rule.buf, state->exe, state->exe_len);
+ return audit_request(state->fd, &msg, NULL);
+}
+
+static int audit_filter_drop(const struct audit_state *const state,
+ const __u16 type)
+{
+ struct audit_message msg = {
+ .header = {
+ .nlmsg_len = NLMSG_SPACE(sizeof(msg.rule)),
+ .nlmsg_type = type,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+ },
+ .rule = {
+ .flags = AUDIT_FILTER_EXCLUDE,
+ .action = AUDIT_NEVER,
+ .field_count = 1,
+ .fields[0] = AUDIT_MSGTYPE,
+ .fieldflags[0] = AUDIT_NOT_EQUAL,
+ .values[0] = AUDIT_LANDLOCK_DOM_DROP,
+ }
+ };
+
+ return audit_request(state->fd, &msg, NULL);
+}
+
+static int audit_set_status(int fd, __u32 key, __u32 val)
+{
+ const struct audit_message msg = {
+ .header = {
+ .nlmsg_len = NLMSG_SPACE(sizeof(msg.status)),
+ .nlmsg_type = AUDIT_SET,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+ },
+ .status = {
+ .mask = key,
+ .enabled = key == AUDIT_STATUS_ENABLED ? val : 0,
+ .pid = key == AUDIT_STATUS_PID ? val : 0,
+ }
+ };
+
+ return audit_request(fd, &msg, NULL);
+}
+
+static int audit_match_record(struct audit_state *const state, const __u16 type,
+ const char *const pattern)
+{
+ struct audit_message msg;
+ int ret, err = 0;
+ bool matches_record = !type;
+ regex_t regex;
+
+ ret = regcomp(®ex, pattern, 0);
+ if (ret)
+ return -EINVAL;
+
+ do {
+ memset(&msg, 0, sizeof(msg));
+ err = audit_recv(state->fd, &msg);
+ if (err)
+ goto out;
+
+ if (msg.header.nlmsg_type == type)
+ matches_record = true;
+ } while (!matches_record);
+
+ ret = regexec(®ex, msg.data, 0, NULL, 0);
+ if (ret) {
+ printf("DATA: %s\n", msg.data);
+ printf("ERROR: no match for pattern: %s\n", pattern);
+ err = -ENOENT;
+ }
+
+out:
+ regfree(®ex);
+ return err;
+}
+
+struct audit_records {
+ size_t deny;
+ size_t info;
+ size_t drop;
+};
+
+static void audit_count_records(struct audit_state *const state,
+ struct audit_records *records)
+{
+ struct audit_message msg;
+
+ records->deny = 0;
+ records->info = 0;
+ records->drop = 0;
+
+ do {
+ memset(&msg, 0, sizeof(msg));
+ if (audit_recv(state->fd, &msg))
+ return;
+
+ switch (msg.header.nlmsg_type) {
+ case AUDIT_LANDLOCK_DENY:
+ records->deny++;
+ break;
+ case AUDIT_LANDLOCK_DOM_INFO:
+ records->info++;
+ break;
+ case AUDIT_LANDLOCK_DOM_DROP:
+ records->drop++;
+ break;
+ }
+ } while (true);
+}
+
+static int audit_init_state(struct audit_state *state)
+{
+ int err;
+
+ state->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
+ if (state->fd < 0)
+ return -errno;
+
+ err = audit_set_status(state->fd, AUDIT_STATUS_ENABLED, 1);
+ if (err)
+ return err;
+
+ err = audit_set_status(state->fd, AUDIT_STATUS_PID, getpid());
+ if (err)
+ return err;
+
+ state->exe_len =
+ readlink("/proc/self/exe", state->exe, sizeof(state->exe) - 1);
+ if (state->exe_len < 0)
+ return -errno;
+
+ return 0;
+}
+
+static int audit_cleanup(struct audit_state *state)
+{
+ struct audit_state state_clean;
+
+ if (!state) {
+ int err;
+
+ /* Requires when called from FIXTURE_TEARDOWN_PARENT(). */
+ state = &state_clean;
+ err = audit_init_state(state);
+ if (err)
+ return err;
+ }
+
+ /* Filters might not be in place. */
+ audit_filter_exe(state, AUDIT_DEL_RULE);
+ audit_filter_drop(state, AUDIT_DEL_RULE);
+
+ /*
+ * Because audit_cleanup() might not be called by the test auditd
+ * process, it might not be possible to explicitly set it. Anyway,
+ * AUDIT_STATUS_ENABLED will implicitly be set to 0 when the auditd
+ * process will exit.
+ */
+ return close(state->fd);
+}
+
+static int audit_init(struct audit_state *state)
+{
+ const struct timeval tv = {
+ .tv_usec = 1,
+ };
+ int err;
+
+ err = audit_init_state(state);
+ if (err)
+ return err;
+
+ // TODO: Make sure there is no rule already filtering.
+
+ err = audit_filter_exe(state, AUDIT_ADD_RULE);
+ if (err)
+ return err;
+
+ /* Sets a timeout for negative tests. */
+ err = setsockopt(state->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+ if (err)
+ return -errno;
+
+ return 0;
+}
diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
new file mode 100644
index 000000000000..f09273dc164a
--- /dev/null
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - Audit
+ *
+ * Copyright © 2024 Microsoft Corporation
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <linux/landlock.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "audit.h"
+#include "common.h"
+
+static int matches_log_umount(struct __test_metadata *const _metadata,
+ struct audit_state *const state)
+{
+ // TODO: return -errno with AUDIT_SYSCALL
+ return !audit_match_record(state, AUDIT_LANDLOCK_DENY,
+ REGEX_LANDLOCK_PREFIX " blockers=.*");
+}
+
+FIXTURE(audit)
+{
+ struct audit_state state;
+};
+
+FIXTURE_VARIANT(audit)
+{
+ const int restrict_flags;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit, default) {};
+/* clang-format on */
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit, logless) {
+ /* clang-format on */
+ .restrict_flags = LANDLOCK_RESTRICT_SELF_LOGLESS,
+};
+
+FIXTURE_SETUP(audit)
+{
+ int err;
+
+ disable_caps(_metadata);
+ set_cap(_metadata, CAP_AUDIT_CONTROL);
+ err = audit_init(&self->state);
+ EXPECT_EQ(0, err)
+ {
+ const char *error_msg;
+
+ if (err == -EEXIST)
+ error_msg = "socket already in use (e.g. auditd)";
+ /* kill "$(auditctl -s | sed -ne 's/^pid \([0-9]\+\)$/\1/p')" */
+ else
+ error_msg = strerror(-err);
+ TH_LOG("Failed to initialize audit: %s", error_msg);
+ }
+ clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+FIXTURE_TEARDOWN(audit)
+{
+ set_cap(_metadata, CAP_AUDIT_CONTROL);
+ EXPECT_EQ(0, audit_cleanup(&self->state));
+}
+
+TEST_F(audit, fs_deny)
+{
+ int status, ret;
+ pid_t child;
+ struct audit_records records;
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
+ };
+ int ruleset_fd;
+
+ /* Add filesystem restrictions. */
+ ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+ sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+ ASSERT_EQ(0, landlock_restrict_self(ruleset_fd,
+ variant->restrict_flags));
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ /* First umount checks to test log entries. */
+ set_cap(_metadata, CAP_SYS_ADMIN);
+ EXPECT_EQ(-1, umount("/"));
+ EXPECT_EQ(EPERM, errno);
+ clear_cap(_metadata, CAP_SYS_ADMIN);
+
+ if (variant->restrict_flags & LANDLOCK_RESTRICT_SELF_LOGLESS) {
+ EXPECT_EQ(0,
+ matches_log_umount(_metadata, &self->state));
+ } else {
+ EXPECT_EQ(1,
+ matches_log_umount(_metadata, &self->state));
+
+ /* Checks domain information records. */
+ ret = audit_match_record(
+ &self->state, AUDIT_LANDLOCK_DOM_INFO,
+ REGEX_LANDLOCK_PREFIX
+ " creation=[0-9.]\\+ pid=[0-9]\\+ uid=[0-9]\\+ exe=\"[^\"]\\+\" comm=\"audit_test\"$");
+ EXPECT_EQ(0, ret);
+ }
+
+ /* Second umount checks to test audit_count_records(). */
+ set_cap(_metadata, CAP_SYS_ADMIN);
+ EXPECT_EQ(-1, umount("/"));
+ EXPECT_EQ(EPERM, errno);
+ clear_cap(_metadata, CAP_SYS_ADMIN);
+
+ /* Makes sure there is no superfluous logged records. */
+ audit_count_records(&self->state, &records);
+ if (variant->restrict_flags & LANDLOCK_RESTRICT_SELF_LOGLESS) {
+ EXPECT_EQ(0, records.deny);
+ } else {
+ EXPECT_EQ(1, records.deny);
+ }
+ EXPECT_EQ(0, records.info);
+ EXPECT_EQ(0, records.drop);
+
+ /* Updates filter rules to match the drop record. */
+ set_cap(_metadata, CAP_AUDIT_CONTROL);
+ EXPECT_EQ(0, audit_filter_drop(&self->state, AUDIT_ADD_RULE));
+ EXPECT_EQ(0, audit_filter_exe(&self->state, AUDIT_DEL_RULE));
+ clear_cap(_metadata, CAP_AUDIT_CONTROL);
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ _metadata->exit_code = KSFT_FAIL;
+
+ /* FIXME: How to match the domain drop without race condition? */
+#if 0
+ ret = audit_match_record(&self->state, AUDIT_LANDLOCK_DOM_DROP,
+ REGEX_LANDLOCK_PREFIX "$");
+ if (variant->restrict_flags & LANDLOCK_RESTRICT_SELF_LOGLESS) {
+ EXPECT_EQ(-EAGAIN, ret);
+ } else {
+ EXPECT_EQ(0, ret);
+ }
+
+ /* Makes sure there is no superfluous logged records. */
+ audit_count_records(&self->state, &records);
+ EXPECT_EQ(0, records.deny);
+ EXPECT_EQ(0, records.info);
+ EXPECT_EQ(0, records.drop);
+#endif
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 61056fa074bb..07770c3eb190 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -12,6 +12,7 @@
#include <linux/landlock.h>
#include <linux/securebits.h>
#include <sys/capability.h>
+#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
@@ -64,6 +65,7 @@ static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
/* Only these three capabilities are useful for the tests. */
const cap_value_t caps[] = {
/* clang-format off */
+ CAP_AUDIT_CONTROL,
CAP_DAC_OVERRIDE,
CAP_MKNOD,
CAP_NET_ADMIN,
diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
index 29af19c4e9f9..3d4eb994d62b 100644
--- a/tools/testing/selftests/landlock/config
+++ b/tools/testing/selftests/landlock/config
@@ -1,3 +1,4 @@
+CONFIG_AUDIT=y
CONFIG_CGROUPS=y
CONFIG_CGROUP_SCHED=y
CONFIG_INET=y
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v3 23/23] selftests/landlock: Add audit tests for ptrace
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (21 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 22/23] selftests/landlock: Add tests for audit Mickaël Salaün
@ 2024-11-22 14:33 ` Mickaël Salaün
2024-12-20 14:36 ` [PATCH v3 00/23] Landlock audit support Francis Laniel
23 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-11-22 14:33 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Add tests for all ptrace actions. This improve all the ptrace tests by
making sure that the restrictions comes from Landlock, and with the
expected objects. These are like enhanced errno checks.
Test coverage for security/landlock is 93.3% of 1604 lines according to
gcc/gcov-14.
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241122143353.59367-24-mic@digikod.net
---
Changes since v2:
- New patch.
---
.../testing/selftests/landlock/ptrace_test.c | 62 +++++++++++++++++--
1 file changed, 58 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c
index a19db4d0b3bd..592927059cc3 100644
--- a/tools/testing/selftests/landlock/ptrace_test.c
+++ b/tools/testing/selftests/landlock/ptrace_test.c
@@ -4,6 +4,7 @@
*
* Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2019-2020 ANSSI
+ * Copyright © 2024 Microsoft Corporation
*/
#define _GNU_SOURCE
@@ -17,6 +18,7 @@
#include <sys/wait.h>
#include <unistd.h>
+#include "audit.h"
#include "common.h"
/* Copied from security/yama/yama_lsm.c */
@@ -85,9 +87,27 @@ static int get_yama_ptrace_scope(void)
return ret;
}
-/* clang-format off */
-FIXTURE(hierarchy) {};
-/* clang-format on */
+static int matches_log_ptrace(struct __test_metadata *const _metadata,
+ struct audit_state *const state, 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;
+
+ // TODO: return -errno with AUDIT_SYSCALL
+ return !audit_match_record(state, AUDIT_LANDLOCK_DENY, log_match);
+}
+
+FIXTURE(hierarchy)
+{
+ struct audit_state state;
+};
FIXTURE_VARIANT(hierarchy)
{
@@ -245,10 +265,15 @@ FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) {
FIXTURE_SETUP(hierarchy)
{
+ disable_caps(_metadata);
+ set_cap(_metadata, CAP_AUDIT_CONTROL);
+ EXPECT_EQ(0, audit_init(&self->state));
+ clear_cap(_metadata, CAP_AUDIT_CONTROL);
}
-FIXTURE_TEARDOWN(hierarchy)
+FIXTURE_TEARDOWN_PARENT(hierarchy)
{
+ EXPECT_EQ(0, audit_cleanup(NULL));
}
/* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
@@ -261,6 +286,7 @@ TEST_F(hierarchy, trace)
char buf_parent;
long ret;
bool can_read_child, can_trace_child, can_read_parent, can_trace_parent;
+ struct audit_records records;
yama_ptrace_scope = get_yama_ptrace_scope();
ASSERT_LE(0, yama_ptrace_scope);
@@ -336,17 +362,26 @@ TEST_F(hierarchy, trace)
err_proc_read = test_ptrace_read(parent);
if (can_read_parent) {
EXPECT_EQ(0, err_proc_read);
+ EXPECT_EQ(0, matches_log_ptrace(_metadata, &self->state,
+ parent));
} else {
EXPECT_EQ(EACCES, err_proc_read);
+ EXPECT_EQ(1, matches_log_ptrace(_metadata, &self->state,
+ parent));
}
/* Tests PTRACE_ATTACH on the parent. */
ret = ptrace(PTRACE_ATTACH, parent, NULL, 0);
if (can_trace_parent) {
EXPECT_EQ(0, ret);
+ EXPECT_EQ(0, matches_log_ptrace(_metadata, &self->state,
+ parent));
} else {
EXPECT_EQ(-1, ret);
EXPECT_EQ(EPERM, errno);
+ EXPECT_EQ(!can_read_parent,
+ matches_log_ptrace(_metadata, &self->state,
+ parent));
}
if (ret == 0) {
ASSERT_EQ(parent, waitpid(parent, &status, 0));
@@ -358,9 +393,15 @@ TEST_F(hierarchy, trace)
ret = ptrace(PTRACE_TRACEME);
if (can_trace_child) {
EXPECT_EQ(0, ret);
+ EXPECT_EQ(0, matches_log_ptrace(_metadata, &self->state,
+ parent));
} else {
EXPECT_EQ(-1, ret);
EXPECT_EQ(EPERM, errno);
+ /* We should indeed see the parent process. */
+ EXPECT_EQ(!can_read_child,
+ matches_log_ptrace(_metadata, &self->state,
+ parent));
}
/*
@@ -408,17 +449,25 @@ TEST_F(hierarchy, trace)
err_proc_read = test_ptrace_read(child);
if (can_read_child) {
EXPECT_EQ(0, err_proc_read);
+ EXPECT_EQ(0,
+ matches_log_ptrace(_metadata, &self->state, child));
} else {
EXPECT_EQ(EACCES, err_proc_read);
+ EXPECT_EQ(1,
+ matches_log_ptrace(_metadata, &self->state, child));
}
/* Tests PTRACE_ATTACH on the child. */
ret = ptrace(PTRACE_ATTACH, child, NULL, 0);
if (can_trace_child) {
EXPECT_EQ(0, ret);
+ EXPECT_EQ(0,
+ matches_log_ptrace(_metadata, &self->state, child));
} else {
EXPECT_EQ(-1, ret);
EXPECT_EQ(EPERM, errno);
+ EXPECT_EQ(!can_read_child,
+ matches_log_ptrace(_metadata, &self->state, child));
}
if (ret == 0) {
@@ -434,6 +483,11 @@ TEST_F(hierarchy, trace)
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
WEXITSTATUS(status) != EXIT_SUCCESS)
_metadata->exit_code = KSFT_FAIL;
+
+ /* Makes sure there is no superfluous logged records. */
+ audit_count_records(&self->state, &records);
+ EXPECT_EQ(0, records.deny);
+ EXPECT_EQ(0, records.info);
}
TEST_HARNESS_MAIN
--
2.47.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* Re: [PATCH v3 08/23] landlock: Log ptrace denials
2024-11-22 14:33 ` [PATCH v3 08/23] landlock: Log ptrace denials Mickaël Salaün
@ 2024-12-20 14:36 ` Francis Laniel
2024-12-24 14:48 ` Mickaël Salaün
2025-01-05 1:23 ` [PATCH v3 8/23] " Paul Moore
1 sibling, 1 reply; 46+ messages in thread
From: Francis Laniel @ 2024-12-20 14:36 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn,
Mickaël Salaün
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Le vendredi 22 novembre 2024, 15:33:38 CET Mickaël Salaün a écrit :
> Add audit support to ptrace_access_check and ptrace_traceme hooks.
>
> Add a new AUDIT_LANDLOCK_DENY record type dedicated to any Landlock
> denials.
>
> Log the domain ID restricting the action, the domain's blockers that are
> missing to allow the requested access, and the target task.
>
> The blockers are implicit restrictions (e.g. ptrace), or explicit access
> rights (e.g. filesystem), or explicit scopes (e.g. signal).
>
> For the ptrace_access_check case, we log the current/parent domain and
> the child task. For the ptrace_traceme case, we log the parent domain
> and the parent task. Indeed, the requester is the current task, but the
> action would be performed by the parent task.
>
> The quick return for non-landlocked tasks is moved from task_ptrace() to
> each LSM hooks.
>
> Audit event sample:
>
> type=LL_DENY msg=audit(1732186800.349:44): domain=195ba459b
> blockers=ptrace opid=1 ocomm="systemd" type=SYSCALL
> msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...]
> pid=300 auid=0
>
> Cc: Günther Noack <gnoack@google.com>
> Cc: Paul Moore <paul@paul-moore.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241122143353.59367-9-mic@digikod.net
> ---
>
> 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.
>
> Changes since v1:
> - Move most audit code to this patch.
> - Rebase on the TCP patch series.
> - Don't log missing access right: simplify and make it generic for rule
> types.
> - Don't log errno and then don't wrap the error with
> landlock_log_request(), as suggested by Jeff.
> - Add a WARN_ON_ONCE() check to never dereference null pointers.
> - Only log when audit is enabled.
> - Don't log task's PID/TID with log_task() because it would be redundant
> with the SYSCALL record.
> - Move the "op" in front and rename "domain" to "denying_domain" to make
> it more consistent with other entries.
> - Don't update the request with the domain ID but add an helper to get
> it from the layer masks (and in a following commit with a struct
> file).
> - Revamp get_domain_id_from_layer_masks() into
> get_level_from_layer_masks().
> - For ptrace_traceme, log the parent domain instead of the current one.
> - Add documentation.
> - Rename AUDIT_LANDLOCK_DENIAL to AUDIT_LANDLOCK_DENY.
> - Only log the domain ID and the target task.
> - Log "blockers", which are either implicit restrictions (e.g. ptrace)
> or explicit access rights (e.g. filesystem), or scopes (e.g. signal).
> - Don't log LSM hook names/operations.
> - Pick an audit event ID folling the IPE ones.
> - Add KUnit tests.
> ---
> include/uapi/linux/audit.h | 3 +-
> security/landlock/Makefile | 2 +-
> security/landlock/audit.c | 137 ++++++++++++++++++++++++++++++++++++
> security/landlock/audit.h | 52 ++++++++++++++
> security/landlock/domain.c | 21 ++++++
> security/landlock/domain.h | 17 +++++
> security/landlock/ruleset.c | 3 +
> security/landlock/task.c | 91 ++++++++++++++++++------
> 8 files changed, 302 insertions(+), 24 deletions(-)
> create mode 100644 security/landlock/audit.c
> create mode 100644 security/landlock/audit.h
> create mode 100644 security/landlock/domain.c
>
> diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> index 75e21a135483..60c909c396c0 100644
> --- a/include/uapi/linux/audit.h
> +++ b/include/uapi/linux/audit.h
> @@ -33,7 +33,7 @@
> * 1100 - 1199 user space trusted application messages
> * 1200 - 1299 messages internal to the audit daemon
> * 1300 - 1399 audit event messages
> - * 1400 - 1499 SE Linux use
> + * 1400 - 1499 access control messages
> * 1500 - 1599 kernel LSPP events
> * 1600 - 1699 kernel crypto events
> * 1700 - 1799 kernel anomaly records
> @@ -146,6 +146,7 @@
> #define AUDIT_IPE_ACCESS 1420 /* IPE denial or grant */
> #define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */
> #define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */
> +#define AUDIT_LANDLOCK_DENY 1423 /* Landlock denial */
>
> #define AUDIT_FIRST_KERN_ANOM_MSG 1700
> #define AUDIT_LAST_KERN_ANOM_MSG 1799
> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
> index e1777abbc413..31512ee4b041 100644
> --- a/security/landlock/Makefile
> +++ b/security/landlock/Makefile
> @@ -5,4 +5,4 @@ landlock-y := setup.o syscalls.o object.o ruleset.o \
>
> landlock-$(CONFIG_INET) += net.o
>
> -landlock-$(CONFIG_AUDIT) += id.o
> +landlock-$(CONFIG_AUDIT) += id.o domain.o audit.o
> diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> new file mode 100644
> index 000000000000..eab6b3a8a231
> --- /dev/null
> +++ b/security/landlock/audit.c
> @@ -0,0 +1,137 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Landlock LSM - Audit helpers
> + *
> + * Copyright © 2023-2024 Microsoft Corporation
> + */
> +
> +#include <kunit/test.h>
> +#include <linux/audit.h>
> +#include <linux/lsm_audit.h>
> +
> +#include "audit.h"
> +#include "domain.h"
> +#include "ruleset.h"
> +
> +static const char *get_blocker(const enum landlock_request_type type)
> +{
> + switch (type) {
> + case LANDLOCK_REQUEST_PTRACE:
> + return "ptrace";
> + }
> +
> + WARN_ON_ONCE(1);
> + return "unknown";
> +}
> +
> +static void log_blockers(struct audit_buffer *const ab,
> + const enum landlock_request_type type)
> +{
> + audit_log_format(ab, "%s", get_blocker(type));
> +}
> +
> +static struct landlock_hierarchy *
> +get_hierarchy(const struct landlock_ruleset *const domain, const size_t
> layer) +{
> + struct landlock_hierarchy *node = domain->hierarchy;
> + ssize_t i;
> +
> + if (WARN_ON_ONCE(layer >= domain->num_layers))
> + return node;
> +
> + for (i = domain->num_layers - 1; i > layer; i--) {
> + if (WARN_ON_ONCE(!node->parent))
> + break;
> +
> + node = node->parent;
> + }
> +
> + return node;
> +}
> +
> +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
> +
> +static void test_get_hierarchy(struct kunit *const test)
> +{
> + struct landlock_hierarchy dom0_node = {
> + .id = 10,
> + };
> + struct landlock_hierarchy dom1_node = {
> + .parent = &dom0_node,
> + .id = 20,
> + };
> + struct landlock_hierarchy dom2_node = {
> + .parent = &dom1_node,
> + .id = 30,
> + };
> + struct landlock_ruleset dom2 = {
> + .hierarchy = &dom2_node,
> + .num_layers = 3,
> + };
> +
> + KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
> + KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
> + KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
> + KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id);
> +}
> +
> +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
> +
> +static bool is_valid_request(const struct landlock_request *const request)
> +{
> + if (WARN_ON_ONCE(!request->layer_plus_one))
> + return false;
> +
> + return true;
> +}
> +
> +/**
> + * landlock_log_denial - Create audit records related to a denial
> + *
> + * @domain: The domain denying an action.
> + * @request: Detail of the user space request.
> + */
> +void landlock_log_denial(const struct landlock_ruleset *const domain,
> + const struct landlock_request *const request)
> +{
> + struct audit_buffer *ab;
> + struct landlock_hierarchy *youngest_denied;
> +
> + if (WARN_ON_ONCE(!domain || !domain->hierarchy || !request))
> + return;
> +
> + if (!is_valid_request(request))
> + return;
> +
> + if (!audit_enabled)
> + return;
> +
> + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
> + AUDIT_LANDLOCK_DENY);
> + if (!ab)
> + return;
> +
> + youngest_denied = get_hierarchy(domain, request->layer_plus_one - 1);
> + audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
> + log_blockers(ab, request->type);
> + audit_log_lsm_data(ab, &request->audit);
> + audit_log_end(ab);
> +}
> +
> +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
> +
> +static struct kunit_case test_cases[] = {
> + /* clang-format off */
> + KUNIT_CASE(test_get_hierarchy),
> + {}
> + /* clang-format on */
> +};
> +
> +static struct kunit_suite test_suite = {
> + .name = "landlock_audit",
> + .test_cases = test_cases,
> +};
> +
> +kunit_test_suite(test_suite);
> +
> +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
> diff --git a/security/landlock/audit.h b/security/landlock/audit.h
> new file mode 100644
> index 000000000000..f33095afcd2f
> --- /dev/null
> +++ b/security/landlock/audit.h
> @@ -0,0 +1,52 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Landlock LSM - Audit helpers
> + *
> + * Copyright © 2023-2024 Microsoft Corporation
> + */
> +
> +#ifndef _SECURITY_LANDLOCK_AUDIT_H
> +#define _SECURITY_LANDLOCK_AUDIT_H
> +
> +#include <linux/audit.h>
> +#include <linux/lsm_audit.h>
> +
> +#include "ruleset.h"
> +
> +enum landlock_request_type {
> + LANDLOCK_REQUEST_PTRACE = 1,
> +};
> +
> +/*
> + * We should be careful to only use a variable of this type for
> + * landlock_log_denial(). This way, the compiler can remove it entirely if
> + * CONFIG_AUDIT is not set.
> + */
> +struct landlock_request {
> + /* Mandatory fields. */
> + enum landlock_request_type type;
> + struct common_audit_data audit;
> +
> + /**
> + * layer_plus_one: First layer level that denies the request + 1. The
> + * extra one is useful to detect uninitialized field.
> + */
> + size_t layer_plus_one;
> +};
> +
> +#ifdef CONFIG_AUDIT
> +
> +void landlock_log_denial(const struct landlock_ruleset *const domain,
> + const struct landlock_request *const request);
> +
> +#else /* CONFIG_AUDIT */
> +
> +static inline void
> +landlock_log_denial(const struct landlock_ruleset *const domain,
> + const struct landlock_request *const request)
> +{
> +}
> +
> +#endif /* CONFIG_AUDIT */
> +
> +#endif /* _SECURITY_LANDLOCK_AUDIT_H */
> diff --git a/security/landlock/domain.c b/security/landlock/domain.c
> new file mode 100644
> index 000000000000..965e4dc21975
> --- /dev/null
> +++ b/security/landlock/domain.c
> @@ -0,0 +1,21 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Landlock LSM - Domain management
> + *
> + * Copyright © 2024 Microsoft Corporation
> + */
> +
> +#include "domain.h"
> +#include "id.h"
> +
> +/**
> + * landlock_init_current_hierarchy - Partially initialize
> landlock_hierarchy + *
> + * @hierarchy: The hierarchy to initialize.
> + *
> + * @hierarchy->parent and @hierarchy->usage should already be set.
> + */
> +void landlock_init_current_hierarchy(struct landlock_hierarchy *const
> hierarchy) +{
> + hierarchy->id = landlock_get_id(1);
> +}
> diff --git a/security/landlock/domain.h b/security/landlock/domain.h
> index 015d61fd81ec..f82d831ca0a7 100644
> --- a/security/landlock/domain.h
> +++ b/security/landlock/domain.h
> @@ -4,6 +4,7 @@
> *
> * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
> * Copyright © 2018-2020 ANSSI
> + * Copyright © 2024 Microsoft Corporation
> */
>
> #ifndef _SECURITY_LANDLOCK_DOMAIN_H
> @@ -26,6 +27,13 @@ struct landlock_hierarchy {
> * domain.
> */
> refcount_t usage;
> +
> +#ifdef CONFIG_AUDIT
> + /**
> + * @id: Landlock domain ID, sets once at domain creation time.
> + */
> + u64 id;
> +#endif /* CONFIG_AUDIT */
> };
>
> static inline void
> @@ -45,4 +53,13 @@ static inline void landlock_put_hierarchy(struct
> landlock_hierarchy *hierarchy) }
> }
>
> +#ifdef CONFIG_AUDIT
> +void landlock_init_current_hierarchy(struct landlock_hierarchy *const
> hierarchy); +#else /* CONFIG_AUDIT */
> +static inline void
> +landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
> +{
> +}
> +#endif /* CONFIG_AUDIT */
> +
> #endif /* _SECURITY_LANDLOCK_DOMAIN_H */
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 4b3dfa3e6fcb..7a88fd9b2473 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -21,6 +21,7 @@
> #include <linux/workqueue.h>
>
> #include "access.h"
> +#include "audit.h"
> #include "domain.h"
> #include "limits.h"
> #include "object.h"
> @@ -503,6 +504,7 @@ static void free_ruleset_work(struct work_struct *const
> work) free_ruleset(ruleset);
> }
>
> +/* Only called by hook_cred_free(). */
> void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
> {
> if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
> @@ -562,6 +564,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const
> parent, if (err)
> goto out_put_dom;
>
> + landlock_init_current_hierarchy(new_dom->hierarchy);
> return new_dom;
>
> out_put_dom:
> diff --git a/security/landlock/task.c b/security/landlock/task.c
> index 98894ad1abc7..1decd6f114e8 100644
> --- a/security/landlock/task.c
> +++ b/security/landlock/task.c
> @@ -10,12 +10,14 @@
> #include <linux/cred.h>
> #include <linux/errno.h>
> #include <linux/kernel.h>
> +#include <linux/lsm_audit.h>
> #include <linux/lsm_hooks.h>
> #include <linux/rcupdate.h>
> #include <linux/sched.h>
> #include <net/af_unix.h>
> #include <net/sock.h>
>
> +#include "audit.h"
> #include "common.h"
> #include "cred.h"
> #include "domain.h"
> @@ -38,41 +40,29 @@ static bool domain_scope_le(const struct
> landlock_ruleset *const parent, {
> const struct landlock_hierarchy *walker;
>
> + /* Quick return for non-landlocked tasks. */
> if (!parent)
> return true;
> +
> if (!child)
> return false;
> +
> for (walker = child->hierarchy; walker; walker = walker->parent) {
> if (walker == parent->hierarchy)
> /* @parent is in the scoped hierarchy of @child. */
> return true;
> }
> +
> /* There is no relationship between @parent and @child. */
> return false;
> }
>
> -static bool task_is_scoped(const struct task_struct *const parent,
> - const struct task_struct *const child)
> -{
> - bool is_scoped;
> - const struct landlock_ruleset *dom_parent, *dom_child;
> -
> - rcu_read_lock();
> - dom_parent = landlock_get_task_domain(parent);
> - dom_child = landlock_get_task_domain(child);
> - is_scoped = domain_scope_le(dom_parent, dom_child);
> - rcu_read_unlock();
> - return is_scoped;
> -}
> -
> -static int task_ptrace(const struct task_struct *const parent,
> - const struct task_struct *const child)
> +static int domain_ptrace(const struct landlock_ruleset *const parent,
> + const struct landlock_ruleset *const child)
> {
> - /* Quick return for non-landlocked tasks. */
> - if (!landlocked(parent))
> - return 0;
> - if (task_is_scoped(parent, child))
> + if (domain_scope_le(parent, child))
> return 0;
> +
> return -EPERM;
> }
>
> @@ -92,7 +82,36 @@ static int task_ptrace(const struct task_struct *const
> parent, static int hook_ptrace_access_check(struct task_struct *const
> child, const unsigned int mode)
> {
> - return task_ptrace(current, child);
> + const struct landlock_ruleset *parent_dom, *child_dom;
> + struct landlock_request request = {
> + .type = LANDLOCK_REQUEST_PTRACE,
> + .audit = {
> + .type = LSM_AUDIT_DATA_TASK,
> + .u.tsk = child,
> + },
> + };
> + int err;
> +
> + /* Quick return for non-landlocked tasks. */
> + parent_dom = landlock_get_current_domain();
> + if (!parent_dom)
> + return 0;
> +
> + rcu_read_lock();
> + child_dom = landlock_get_task_domain(child);
> + err = domain_ptrace(parent_dom, child_dom);
> + rcu_read_unlock();
> +
> + /*
> + * For the ptrace_access_check case, we log the current/parent domain
> + * and the child task.
> + */
> + if (err && !(mode & PTRACE_MODE_NOAUDIT)) {
> + request.layer_plus_one = parent_dom->num_layers;
> + landlock_log_denial(parent_dom, &request);
> + }
> +
> + return err;
> }
>
> /**
> @@ -109,7 +128,35 @@ static int hook_ptrace_access_check(struct task_struct
> *const child, */
> static int hook_ptrace_traceme(struct task_struct *const parent)
> {
> - return task_ptrace(parent, current);
> + const struct landlock_ruleset *parent_dom, *child_dom;
> + struct landlock_request request = {
> + .type = LANDLOCK_REQUEST_PTRACE,
> + .audit = {
> + .type = LSM_AUDIT_DATA_TASK,
> + .u.tsk = parent,
> + },
> + };
> + int err;
> +
> + child_dom = landlock_get_current_domain();
> + rcu_read_lock();
> + parent_dom = landlock_get_task_domain(parent);
> + err = domain_ptrace(parent_dom, child_dom);
> +
> + /*
> + * For the ptrace_traceme case, we log the domain which is the cause
of
> + * the denial, which means the parent domain instead of the current
> + * domain. This may look weird because the ptrace_traceme action is a
> + * request to be traced, but the semantic is consistent with
> + * hook_ptrace_access_check().
> + */
> + if (err) {
> + request.layer_plus_one = parent_dom->num_layers;
> + landlock_log_denial(parent_dom, &request);
> + }
> +
> + rcu_read_unlock();
Nit: in the above function, you do the rcu_read_unlock() before the if.
> + return err;
> }
>
> /**
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 20/23] samples/landlock: Do not log denials from the sandboxer by default
2024-11-22 14:33 ` [PATCH v3 20/23] samples/landlock: Do not log denials from the sandboxer by default Mickaël Salaün
@ 2024-12-20 14:36 ` Francis Laniel
2024-12-24 14:48 ` Mickaël Salaün
0 siblings, 1 reply; 46+ messages in thread
From: Francis Laniel @ 2024-12-20 14:36 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn,
Mickaël Salaün
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Le vendredi 22 novembre 2024, 15:33:50 CET Mickaël Salaün a écrit :
> Do not pollute audit logs because of unknown sandboxed programs.
> Indeed, the sandboxer's security policy might not be fitted to the set
> of sandboxed processes that could be spawned (e.g. from a shell).
>
> The LANDLOCK_RESTRICT_SELF_LOGLESS flag should be used for all similar
> sandboxer tools by default. Only natively-sandboxed programs should not
> use this flag.
>
> For test purpose, parse the LL_FORCE_LOG environment variable to still
> log denials.
>
> Cc: Günther Noack <gnoack@google.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241122143353.59367-21-mic@digikod.net
> ---
>
> Changes since v2:
> - New patch.
> ---
> samples/landlock/sandboxer.c | 35 ++++++++++++++++++++++++++++++++---
> 1 file changed, 32 insertions(+), 3 deletions(-)
>
> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
> index 57565dfd74a2..49fd4fdb1dcf 100644
> --- a/samples/landlock/sandboxer.c
> +++ b/samples/landlock/sandboxer.c
> @@ -58,6 +58,7 @@ static inline int landlock_restrict_self(const int
> ruleset_fd, #define ENV_TCP_BIND_NAME "LL_TCP_BIND"
> #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
> #define ENV_SCOPED_NAME "LL_SCOPED"
> +#define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
> #define ENV_DELIMITER ":"
>
> static int str2num(const char *numstr, __u64 *num_dst)
> @@ -288,7 +289,7 @@ static bool check_ruleset_scope(const char *const
> env_var,
>
> /* clang-format on */
>
> -#define LANDLOCK_ABI_LAST 6
> +#define LANDLOCK_ABI_LAST 7
>
> #define XSTR(s) #s
> #define STR(s) XSTR(s)
> @@ -315,6 +316,9 @@ static const char help[] =
> " - \"a\" to restrict opening abstract unix sockets\n"
> " - \"s\" to restrict sending signals\n"
> "\n"
> + "A sandboxer should not log denied access requests to avoid spamming
logs,
> " + "but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
> + "\n"
> "Example:\n"
> ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
> ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
> @@ -333,7 +337,7 @@ int main(const int argc, char *const argv[], char *const
> *const envp) const char *cmd_path;
> char *const *cmd_argv;
> int ruleset_fd, abi;
> - char *env_port_name;
> + char *env_port_name, *env_force_log;
> __u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
> access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
>
> @@ -344,6 +348,8 @@ int main(const int argc, char *const argv[], char *const
> *const envp) .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> LANDLOCK_SCOPE_SIGNAL,
> };
> + /* Do not pollute audit logs because of unknown sandboxed programs. */
> + int restrict_flags = LANDLOCK_RESTRICT_SELF_LOGLESS;
>
> if (argc < 2) {
> fprintf(stderr, help, argv[0]);
> @@ -415,6 +421,12 @@ int main(const int argc, char *const argv[], char
> *const *const envp) /* Removes LANDLOCK_SCOPE_* for ABI < 6 */
> ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> LANDLOCK_SCOPE_SIGNAL);
> + __attribute__((fallthrough));
> + case 6:
> + /* Removes LANDLOCK_RESTRICT_SELF_LOGLESS for ABI < 7 */
> + restrict_flags &= ~LANDLOCK_RESTRICT_SELF_LOGLESS;
> +
> + /* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
> fprintf(stderr,
> "Hint: You should update the running kernel "
> "to leverage Landlock features "
> @@ -449,6 +461,23 @@ int main(const int argc, char *const argv[], char
> *const *const envp) if (check_ruleset_scope(ENV_SCOPED_NAME,
> &ruleset_attr))
> return 1;
>
> + /* Enables optional logs. */
> + env_force_log = getenv(ENV_FORCE_LOG_NAME);
> + if (env_force_log) {
> + if (strcmp(env_force_log, "1") != 0) {
> + fprintf(stderr,
> + "Unknown value for " ENV_FORCE_LOG_NAME "\n");
"Unknown value for " ENV_FORCE_LOG_NAME ", got %s, expected 1\n",
env_force_log)
This really helps users to figure out what they made wrong.
> + return 1;
> + }
> + if (!(restrict_flags & LANDLOCK_RESTRICT_SELF_LOGLESS)) {
> + fprintf(stderr,
> + "Audit logs not supported by current kernel\n");
> + return 1;
> + }
> + restrict_flags &= ~LANDLOCK_RESTRICT_SELF_LOGLESS;
> + unsetenv(ENV_FORCE_LOG_NAME);
> + }
> +
> ruleset_fd =
> landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr),
0);
> if (ruleset_fd < 0) {
> @@ -476,7 +505,7 @@ int main(const int argc, char *const argv[], char *const
> *const envp) perror("Failed to restrict privileges");
> goto err_close_ruleset;
> }
> - if (landlock_restrict_self(ruleset_fd, 0)) {
> + if (landlock_restrict_self(ruleset_fd, restrict_flags)) {
> perror("Failed to enforce ruleset");
> goto err_close_ruleset;
> }
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 00/23] Landlock audit support
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
` (22 preceding siblings ...)
2024-11-22 14:33 ` [PATCH v3 23/23] selftests/landlock: Add audit tests for ptrace Mickaël Salaün
@ 2024-12-20 14:36 ` Francis Laniel
23 siblings, 0 replies; 46+ messages in thread
From: Francis Laniel @ 2024-12-20 14:36 UTC (permalink / raw)
To: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn,
Mickaël Salaün
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
Hi!
Le vendredi 22 novembre 2024, 15:33:30 CET Mickaël Salaün a écrit :
> 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
> - app developers: to ease and speed up sandboxing support
>
> 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 constraints (e.g. scoped domain).
>
> # Changes from previous version
>
> This third patch series reduces the amount of domain information
> records: instead of creating a record for a domain hierarchy, only the
> domain that denied the request is logged, which is enough.
>
> The log format for domain information don't include the parent anymore
> but the creation time instead, which is useful to know how old a domain
> is relative to a first denial. We also now use hexadecimal numbers for
> domain IDs.
>
> Another major addition of this patch series are the new tests. The new
> syscall flag is tested, and all the ptrace tests are extended to check
> the source of the denials (e.g. Landlock or Yama). This greatly improve
> test consistency and I plan to extend all Landlock tests with these
> audit checks.
>
> The sandboxer sample is also updated to not generate logs by default.
>
> # Design
>
> Log records are created for any denied actions caused by a Landlock
> policy, which means that a well-sandboxed applications should not log
> anything except for unattended access requests that might be the result
> of attacks or bugs.
>
> However, sandbox tools creating restricted environments could lead to
> abundant log entries because the sandboxed processes may not be aware of
> the related restrictions. To avoid log spam, the
> landlock_restrict_self(2) syscall gets a new
> LANDLOCK_RESTRICT_SELF_LOGLESS flag to not log denials related to this
> specific domain. Except for well-understood exceptions, this flag
> should not be set. Indeed, applications sandboxing themselves should
> only try to bypass their own sandbox if they are compromised, which
> should ring a bell thanks to log events.
>
> When an action is denied, the related Landlock domain ID is specified.
> If this domain was not previously described in a log record, one is
> created. This record contains the domain ID, its creation time, and
> informations about the process that enforced the restriction (at the
> time of the call to landlock_restrict_self): PID, UID, executable path,
> and name (comm).
>
> This new approach also brings building blocks for an upcoming
> unprivileged introspection interface. The unique Landlock IDs will be
> useful to tie audit log entries to running processes, and to get
> properties of the related Landlock domains. This will replace the
> previously logged ruleset properties.
>
> # Samples
>
> Here are two examples of log events:
>
> $ LL_FS_RO=/ LL_FS_RW=/ LL_SCOPED=s LL_FORCE_LOG=1 ./sandboxer kill 1
>
> type=UNKNOWN[1423] msg=audit(1732186800.268:30): domain=1a6fdc66f
> blockers=scope_signal opid=1 ocomm="systemd" type=UNKNOWN[1424]
> msg=audit(1732186800.268:30): domain=1a6fdc66f creation=1732186800.264
> pid=286 uid=0 exe="/root/sandboxer" comm="sandboxer"UID="root" type=SYSCALL
> msg=audit(1732186800.268:30): arch=c000003e syscall=62 success=no exit=-1
> [..] ppid=272 pid=286 auid=0 uid=0 gid=0 [...] comm="kill" [...]
> type=PROCTITLE msg=audit(1732186800.268:30): proctitle=6B696C6C0031
> type=UNKNOWN[1425] msg=audit(1732186800.324:31): domain=1a6fdc66f
>
> $ LL_FS_RO=/ LL_FS_RW=/tmp LL_FORCE_LOG=1 ./sandboxer sh -c "echo >
> /etc/passwd"
>
> type=UNKNOWN[1423] msg=audit(1732186800.221:33): domain=1a6fdc679
> blockers=fs_write_file path="/dev/tty" dev="devtmpfs" ino=9
> type=UNKNOWN[1424] msg=audit(1732186800.221:33): domain=1a6fdc679
> creation=1732186800.221 pid=289 uid=0 exe="/root/sandboxer"
> comm="sandboxer"UID="root" type=SYSCALL msg=audit(1732186800.221:33):
> arch=c000003e syscall=257 success=no exit=-13 [...] ppid=272 pid=289 auid=0
> uid=0 gid=0 [...] comm="sh" [...] type=PROCTITLE
> msg=audit(1732186800.221:33):
> proctitle=7368002D63006563686F203E202F6574632F706173737764
> type=UNKNOWN[1423] msg=audit(1732186800.221:34): domain=1a6fdc679
> blockers=fs_write_file path="/etc/passwd" dev="vda2" ino=143821
> type=SYSCALL msg=audit(1732186800.221:34): arch=c000003e syscall=257
> success=no exit=-13 [...] ppid=272 pid=289 auid=0 uid=0 gid=0 [...]
> comm="sh" [...] type=PROCTITLE msg=audit(1732186800.221:34):
> proctitle=7368002D63006563686F203E202F6574632F706173737764
> type=UNKNOWN[1425] msg=audit(1732186800.261:35): domain=1a6fdc679
>
> # Future changes
>
> It would be interesting to enhance audit with the ability to filter on
> the executable path that created a sandbox, or to filter on a Landlock
> domain ID.
>
Thank you for this series, I tested with a sandboxed program trying to
fstatat("/proc/1/ns/pid") and I indeed got audit logs:
root@vm-amd64:~# uname -r
6.12.0-00023-gab1009f970a0
root@vm-amd64:~# ./share/kinvolk/landlock/landlock
Failed to fstatat: Permission denied
root@vm-amd64:~# grep 'domain=' /var/log/audit/audit.log
type=UNKNOWN[1423] msg=audit(1734704806.184:61): domain=1e925333c
blockers=ptrace opid=1 ocomm="systemd"
type=UNKNOWN[1424] msg=audit(1734704806.184:61): domain=1e925333c
creation=1734704806.184 pid=288 uid=0 exe="/root/share/kinvolk/landlock/
landlock" comm="landlock"UID="root"
type=UNKNOWN[1425] msg=audit(1734704806.191:62): domain=1e925333c
>
> Previous versions:
> v1: https://lore.kernel.org/r/20241022161009.982584-1-mic@digikod.net
> v1: https://lore.kernel.org/r/20230921061641.273654-1-mic@digikod.net
>
> Regards,
>
> Mickaël Salaün (23):
> lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are
> set
> lsm: Add audit_log_lsm_data() helper
> landlock: Factor out check_access_path()
> landlock: Add unique ID generator
> landlock: Move access types
> landlock: Simplify initially denied access rights
> landlock: Move domain hierarchy management
> landlock: Log ptrace denials
> audit: Add a new audit_get_ctime() helper
> landlock: Log domain properties and release
> landlock: Log mount-related denials
> landlock: Align partial refer access checks with final ones
> selftests/landlock: Add test to check partial access in a mount tree
> landlock: Optimize file path walks and prepare for audit support
> landlock: Log file-related denials
> landlock: Log truncate and ioctl denials
> landlock: Log TCP bind and connect denials
> landlock: Log scoped denials
> landlock: Control log events with LANDLOCK_RESTRICT_SELF_LOGLESS
> samples/landlock: Do not log denials from the sandboxer by default
> selftests/landlock: Extend tests for landlock_restrict_self()'s flags
> selftests/landlock: Add tests for audit
> selftests/landlock: Add audit tests for ptrace
>
> Documentation/userspace-api/landlock.rst | 2 +-
> include/linux/audit.h | 8 +
> include/linux/lsm_audit.h | 22 +
> include/uapi/linux/audit.h | 5 +-
> include/uapi/linux/landlock.h | 14 +
> kernel/auditsc.c | 21 +-
> samples/landlock/sandboxer.c | 35 +-
> security/Kconfig | 5 +
> security/Makefile | 2 +-
> security/landlock/.kunitconfig | 2 +
> security/landlock/Makefile | 2 +
> security/landlock/access.h | 100 ++++
> security/landlock/audit.c | 495 ++++++++++++++++++
> security/landlock/audit.h | 76 +++
> security/landlock/domain.c | 195 +++++++
> security/landlock/domain.h | 117 +++++
> security/landlock/fs.c | 279 +++++++---
> security/landlock/fs.h | 10 +
> security/landlock/id.c | 242 +++++++++
> security/landlock/id.h | 25 +
> security/landlock/net.c | 51 +-
> security/landlock/ruleset.c | 35 +-
> security/landlock/ruleset.h | 96 ++--
> security/landlock/setup.c | 2 +
> security/landlock/syscalls.c | 26 +-
> security/landlock/task.c | 150 +++++-
> security/lsm_audit.c | 27 +-
> tools/testing/kunit/configs/all_tests.config | 2 +
> tools/testing/selftests/landlock/audit.h | 308 +++++++++++
> tools/testing/selftests/landlock/audit_test.c | 168 ++++++
> tools/testing/selftests/landlock/base_test.c | 18 +-
> tools/testing/selftests/landlock/common.h | 2 +
> tools/testing/selftests/landlock/config | 1 +
> tools/testing/selftests/landlock/fs_test.c | 54 +-
> .../testing/selftests/landlock/ptrace_test.c | 62 ++-
> 35 files changed, 2454 insertions(+), 205 deletions(-)
> create mode 100644 security/landlock/access.h
> create mode 100644 security/landlock/audit.c
> create mode 100644 security/landlock/audit.h
> create mode 100644 security/landlock/domain.c
> create mode 100644 security/landlock/domain.h
> create mode 100644 security/landlock/id.c
> create mode 100644 security/landlock/id.h
> create mode 100644 tools/testing/selftests/landlock/audit.h
> create mode 100644 tools/testing/selftests/landlock/audit_test.c
>
>
> base-commit: adc218676eef25575469234709c2d87185ca223a
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 08/23] landlock: Log ptrace denials
2024-12-20 14:36 ` Francis Laniel
@ 2024-12-24 14:48 ` Mickaël Salaün
0 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-12-24 14:48 UTC (permalink / raw)
To: Francis Laniel
Cc: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn,
Ben Scarlato, Casey Schaufler, Charles Zaffery, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Fri, Dec 20, 2024 at 03:36:14PM +0100, Francis Laniel wrote:
> Le vendredi 22 novembre 2024, 15:33:38 CET Mickaël Salaün a écrit :
> > Add audit support to ptrace_access_check and ptrace_traceme hooks.
> >
> > Add a new AUDIT_LANDLOCK_DENY record type dedicated to any Landlock
> > denials.
> >
> > Log the domain ID restricting the action, the domain's blockers that are
> > missing to allow the requested access, and the target task.
> >
> > The blockers are implicit restrictions (e.g. ptrace), or explicit access
> > rights (e.g. filesystem), or explicit scopes (e.g. signal).
> >
> > For the ptrace_access_check case, we log the current/parent domain and
> > the child task. For the ptrace_traceme case, we log the parent domain
> > and the parent task. Indeed, the requester is the current task, but the
> > action would be performed by the parent task.
> >
> > The quick return for non-landlocked tasks is moved from task_ptrace() to
> > each LSM hooks.
> >
> > Audit event sample:
> >
> > type=LL_DENY msg=audit(1732186800.349:44): domain=195ba459b
> > blockers=ptrace opid=1 ocomm="systemd" type=SYSCALL
> > msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...]
> > pid=300 auid=0
> >
> > Cc: Günther Noack <gnoack@google.com>
> > Cc: Paul Moore <paul@paul-moore.com>
> > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > Link: https://lore.kernel.org/r/20241122143353.59367-9-mic@digikod.net
> > ---
> >
> > 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.
> >
> > Changes since v1:
> > - Move most audit code to this patch.
> > - Rebase on the TCP patch series.
> > - Don't log missing access right: simplify and make it generic for rule
> > types.
> > - Don't log errno and then don't wrap the error with
> > landlock_log_request(), as suggested by Jeff.
> > - Add a WARN_ON_ONCE() check to never dereference null pointers.
> > - Only log when audit is enabled.
> > - Don't log task's PID/TID with log_task() because it would be redundant
> > with the SYSCALL record.
> > - Move the "op" in front and rename "domain" to "denying_domain" to make
> > it more consistent with other entries.
> > - Don't update the request with the domain ID but add an helper to get
> > it from the layer masks (and in a following commit with a struct
> > file).
> > - Revamp get_domain_id_from_layer_masks() into
> > get_level_from_layer_masks().
> > - For ptrace_traceme, log the parent domain instead of the current one.
> > - Add documentation.
> > - Rename AUDIT_LANDLOCK_DENIAL to AUDIT_LANDLOCK_DENY.
> > - Only log the domain ID and the target task.
> > - Log "blockers", which are either implicit restrictions (e.g. ptrace)
> > or explicit access rights (e.g. filesystem), or scopes (e.g. signal).
> > - Don't log LSM hook names/operations.
> > - Pick an audit event ID folling the IPE ones.
> > - Add KUnit tests.
> > ---
> > include/uapi/linux/audit.h | 3 +-
> > security/landlock/Makefile | 2 +-
> > security/landlock/audit.c | 137 ++++++++++++++++++++++++++++++++++++
> > security/landlock/audit.h | 52 ++++++++++++++
> > security/landlock/domain.c | 21 ++++++
> > security/landlock/domain.h | 17 +++++
> > security/landlock/ruleset.c | 3 +
> > security/landlock/task.c | 91 ++++++++++++++++++------
> > 8 files changed, 302 insertions(+), 24 deletions(-)
> > create mode 100644 security/landlock/audit.c
> > create mode 100644 security/landlock/audit.h
> > create mode 100644 security/landlock/domain.c
> >
> > diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> > index 75e21a135483..60c909c396c0 100644
> > --- a/include/uapi/linux/audit.h
> > +++ b/include/uapi/linux/audit.h
> > @@ -33,7 +33,7 @@
> > * 1100 - 1199 user space trusted application messages
> > * 1200 - 1299 messages internal to the audit daemon
> > * 1300 - 1399 audit event messages
> > - * 1400 - 1499 SE Linux use
> > + * 1400 - 1499 access control messages
> > * 1500 - 1599 kernel LSPP events
> > * 1600 - 1699 kernel crypto events
> > * 1700 - 1799 kernel anomaly records
> > @@ -146,6 +146,7 @@
> > #define AUDIT_IPE_ACCESS 1420 /* IPE denial or grant */
> > #define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */
> > #define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */
> > +#define AUDIT_LANDLOCK_DENY 1423 /* Landlock denial */
> >
> > #define AUDIT_FIRST_KERN_ANOM_MSG 1700
> > #define AUDIT_LAST_KERN_ANOM_MSG 1799
> > diff --git a/security/landlock/Makefile b/security/landlock/Makefile
> > index e1777abbc413..31512ee4b041 100644
> > --- a/security/landlock/Makefile
> > +++ b/security/landlock/Makefile
> > @@ -5,4 +5,4 @@ landlock-y := setup.o syscalls.o object.o ruleset.o \
> >
> > landlock-$(CONFIG_INET) += net.o
> >
> > -landlock-$(CONFIG_AUDIT) += id.o
> > +landlock-$(CONFIG_AUDIT) += id.o domain.o audit.o
> > diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> > new file mode 100644
> > index 000000000000..eab6b3a8a231
> > --- /dev/null
> > +++ b/security/landlock/audit.c
> > @@ -0,0 +1,137 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Landlock LSM - Audit helpers
> > + *
> > + * Copyright © 2023-2024 Microsoft Corporation
> > + */
> > +
> > +#include <kunit/test.h>
> > +#include <linux/audit.h>
> > +#include <linux/lsm_audit.h>
> > +
> > +#include "audit.h"
> > +#include "domain.h"
> > +#include "ruleset.h"
> > +
> > +static const char *get_blocker(const enum landlock_request_type type)
> > +{
> > + switch (type) {
> > + case LANDLOCK_REQUEST_PTRACE:
> > + return "ptrace";
> > + }
> > +
> > + WARN_ON_ONCE(1);
> > + return "unknown";
> > +}
> > +
> > +static void log_blockers(struct audit_buffer *const ab,
> > + const enum landlock_request_type type)
> > +{
> > + audit_log_format(ab, "%s", get_blocker(type));
> > +}
> > +
> > +static struct landlock_hierarchy *
> > +get_hierarchy(const struct landlock_ruleset *const domain, const size_t
> > layer) +{
> > + struct landlock_hierarchy *node = domain->hierarchy;
> > + ssize_t i;
> > +
> > + if (WARN_ON_ONCE(layer >= domain->num_layers))
> > + return node;
> > +
> > + for (i = domain->num_layers - 1; i > layer; i--) {
> > + if (WARN_ON_ONCE(!node->parent))
> > + break;
> > +
> > + node = node->parent;
> > + }
> > +
> > + return node;
> > +}
> > +
> > +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
> > +
> > +static void test_get_hierarchy(struct kunit *const test)
> > +{
> > + struct landlock_hierarchy dom0_node = {
> > + .id = 10,
> > + };
> > + struct landlock_hierarchy dom1_node = {
> > + .parent = &dom0_node,
> > + .id = 20,
> > + };
> > + struct landlock_hierarchy dom2_node = {
> > + .parent = &dom1_node,
> > + .id = 30,
> > + };
> > + struct landlock_ruleset dom2 = {
> > + .hierarchy = &dom2_node,
> > + .num_layers = 3,
> > + };
> > +
> > + KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
> > + KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
> > + KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
> > + KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id);
> > +}
> > +
> > +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
> > +
> > +static bool is_valid_request(const struct landlock_request *const request)
> > +{
> > + if (WARN_ON_ONCE(!request->layer_plus_one))
> > + return false;
> > +
> > + return true;
> > +}
> > +
> > +/**
> > + * landlock_log_denial - Create audit records related to a denial
> > + *
> > + * @domain: The domain denying an action.
> > + * @request: Detail of the user space request.
> > + */
> > +void landlock_log_denial(const struct landlock_ruleset *const domain,
> > + const struct landlock_request *const request)
> > +{
> > + struct audit_buffer *ab;
> > + struct landlock_hierarchy *youngest_denied;
> > +
> > + if (WARN_ON_ONCE(!domain || !domain->hierarchy || !request))
> > + return;
> > +
> > + if (!is_valid_request(request))
> > + return;
> > +
> > + if (!audit_enabled)
> > + return;
> > +
> > + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
> > + AUDIT_LANDLOCK_DENY);
> > + if (!ab)
> > + return;
> > +
> > + youngest_denied = get_hierarchy(domain, request->layer_plus_one - 1);
> > + audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
> > + log_blockers(ab, request->type);
> > + audit_log_lsm_data(ab, &request->audit);
> > + audit_log_end(ab);
> > +}
> > +
> > +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
> > +
> > +static struct kunit_case test_cases[] = {
> > + /* clang-format off */
> > + KUNIT_CASE(test_get_hierarchy),
> > + {}
> > + /* clang-format on */
> > +};
> > +
> > +static struct kunit_suite test_suite = {
> > + .name = "landlock_audit",
> > + .test_cases = test_cases,
> > +};
> > +
> > +kunit_test_suite(test_suite);
> > +
> > +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
> > diff --git a/security/landlock/audit.h b/security/landlock/audit.h
> > new file mode 100644
> > index 000000000000..f33095afcd2f
> > --- /dev/null
> > +++ b/security/landlock/audit.h
> > @@ -0,0 +1,52 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Landlock LSM - Audit helpers
> > + *
> > + * Copyright © 2023-2024 Microsoft Corporation
> > + */
> > +
> > +#ifndef _SECURITY_LANDLOCK_AUDIT_H
> > +#define _SECURITY_LANDLOCK_AUDIT_H
> > +
> > +#include <linux/audit.h>
> > +#include <linux/lsm_audit.h>
> > +
> > +#include "ruleset.h"
> > +
> > +enum landlock_request_type {
> > + LANDLOCK_REQUEST_PTRACE = 1,
> > +};
> > +
> > +/*
> > + * We should be careful to only use a variable of this type for
> > + * landlock_log_denial(). This way, the compiler can remove it entirely if
> > + * CONFIG_AUDIT is not set.
> > + */
> > +struct landlock_request {
> > + /* Mandatory fields. */
> > + enum landlock_request_type type;
> > + struct common_audit_data audit;
> > +
> > + /**
> > + * layer_plus_one: First layer level that denies the request + 1. The
> > + * extra one is useful to detect uninitialized field.
> > + */
> > + size_t layer_plus_one;
> > +};
> > +
> > +#ifdef CONFIG_AUDIT
> > +
> > +void landlock_log_denial(const struct landlock_ruleset *const domain,
> > + const struct landlock_request *const request);
> > +
> > +#else /* CONFIG_AUDIT */
> > +
> > +static inline void
> > +landlock_log_denial(const struct landlock_ruleset *const domain,
> > + const struct landlock_request *const request)
> > +{
> > +}
> > +
> > +#endif /* CONFIG_AUDIT */
> > +
> > +#endif /* _SECURITY_LANDLOCK_AUDIT_H */
> > diff --git a/security/landlock/domain.c b/security/landlock/domain.c
> > new file mode 100644
> > index 000000000000..965e4dc21975
> > --- /dev/null
> > +++ b/security/landlock/domain.c
> > @@ -0,0 +1,21 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Landlock LSM - Domain management
> > + *
> > + * Copyright © 2024 Microsoft Corporation
> > + */
> > +
> > +#include "domain.h"
> > +#include "id.h"
> > +
> > +/**
> > + * landlock_init_current_hierarchy - Partially initialize
> > landlock_hierarchy + *
> > + * @hierarchy: The hierarchy to initialize.
> > + *
> > + * @hierarchy->parent and @hierarchy->usage should already be set.
> > + */
> > +void landlock_init_current_hierarchy(struct landlock_hierarchy *const
> > hierarchy) +{
> > + hierarchy->id = landlock_get_id(1);
> > +}
> > diff --git a/security/landlock/domain.h b/security/landlock/domain.h
> > index 015d61fd81ec..f82d831ca0a7 100644
> > --- a/security/landlock/domain.h
> > +++ b/security/landlock/domain.h
> > @@ -4,6 +4,7 @@
> > *
> > * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
> > * Copyright © 2018-2020 ANSSI
> > + * Copyright © 2024 Microsoft Corporation
> > */
> >
> > #ifndef _SECURITY_LANDLOCK_DOMAIN_H
> > @@ -26,6 +27,13 @@ struct landlock_hierarchy {
> > * domain.
> > */
> > refcount_t usage;
> > +
> > +#ifdef CONFIG_AUDIT
> > + /**
> > + * @id: Landlock domain ID, sets once at domain creation time.
> > + */
> > + u64 id;
> > +#endif /* CONFIG_AUDIT */
> > };
> >
> > static inline void
> > @@ -45,4 +53,13 @@ static inline void landlock_put_hierarchy(struct
> > landlock_hierarchy *hierarchy) }
> > }
> >
> > +#ifdef CONFIG_AUDIT
> > +void landlock_init_current_hierarchy(struct landlock_hierarchy *const
> > hierarchy); +#else /* CONFIG_AUDIT */
> > +static inline void
> > +landlock_init_current_hierarchy(struct landlock_hierarchy *const hierarchy)
> > +{
> > +}
> > +#endif /* CONFIG_AUDIT */
> > +
> > #endif /* _SECURITY_LANDLOCK_DOMAIN_H */
> > diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> > index 4b3dfa3e6fcb..7a88fd9b2473 100644
> > --- a/security/landlock/ruleset.c
> > +++ b/security/landlock/ruleset.c
> > @@ -21,6 +21,7 @@
> > #include <linux/workqueue.h>
> >
> > #include "access.h"
> > +#include "audit.h"
> > #include "domain.h"
> > #include "limits.h"
> > #include "object.h"
> > @@ -503,6 +504,7 @@ static void free_ruleset_work(struct work_struct *const
> > work) free_ruleset(ruleset);
> > }
> >
> > +/* Only called by hook_cred_free(). */
> > void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
> > {
> > if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
> > @@ -562,6 +564,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const
> > parent, if (err)
> > goto out_put_dom;
> >
> > + landlock_init_current_hierarchy(new_dom->hierarchy);
> > return new_dom;
> >
> > out_put_dom:
> > diff --git a/security/landlock/task.c b/security/landlock/task.c
> > index 98894ad1abc7..1decd6f114e8 100644
> > --- a/security/landlock/task.c
> > +++ b/security/landlock/task.c
> > @@ -10,12 +10,14 @@
> > #include <linux/cred.h>
> > #include <linux/errno.h>
> > #include <linux/kernel.h>
> > +#include <linux/lsm_audit.h>
> > #include <linux/lsm_hooks.h>
> > #include <linux/rcupdate.h>
> > #include <linux/sched.h>
> > #include <net/af_unix.h>
> > #include <net/sock.h>
> >
> > +#include "audit.h"
> > #include "common.h"
> > #include "cred.h"
> > #include "domain.h"
> > @@ -38,41 +40,29 @@ static bool domain_scope_le(const struct
> > landlock_ruleset *const parent, {
> > const struct landlock_hierarchy *walker;
> >
> > + /* Quick return for non-landlocked tasks. */
> > if (!parent)
> > return true;
> > +
> > if (!child)
> > return false;
> > +
> > for (walker = child->hierarchy; walker; walker = walker->parent) {
> > if (walker == parent->hierarchy)
> > /* @parent is in the scoped hierarchy of @child. */
> > return true;
> > }
> > +
> > /* There is no relationship between @parent and @child. */
> > return false;
> > }
> >
> > -static bool task_is_scoped(const struct task_struct *const parent,
> > - const struct task_struct *const child)
> > -{
> > - bool is_scoped;
> > - const struct landlock_ruleset *dom_parent, *dom_child;
> > -
> > - rcu_read_lock();
> > - dom_parent = landlock_get_task_domain(parent);
> > - dom_child = landlock_get_task_domain(child);
> > - is_scoped = domain_scope_le(dom_parent, dom_child);
> > - rcu_read_unlock();
> > - return is_scoped;
> > -}
> > -
> > -static int task_ptrace(const struct task_struct *const parent,
> > - const struct task_struct *const child)
> > +static int domain_ptrace(const struct landlock_ruleset *const parent,
> > + const struct landlock_ruleset *const child)
> > {
> > - /* Quick return for non-landlocked tasks. */
> > - if (!landlocked(parent))
> > - return 0;
> > - if (task_is_scoped(parent, child))
> > + if (domain_scope_le(parent, child))
> > return 0;
> > +
> > return -EPERM;
> > }
> >
> > @@ -92,7 +82,36 @@ static int task_ptrace(const struct task_struct *const
> > parent, static int hook_ptrace_access_check(struct task_struct *const
> > child, const unsigned int mode)
> > {
> > - return task_ptrace(current, child);
> > + const struct landlock_ruleset *parent_dom, *child_dom;
> > + struct landlock_request request = {
> > + .type = LANDLOCK_REQUEST_PTRACE,
> > + .audit = {
> > + .type = LSM_AUDIT_DATA_TASK,
> > + .u.tsk = child,
> > + },
> > + };
> > + int err;
> > +
> > + /* Quick return for non-landlocked tasks. */
> > + parent_dom = landlock_get_current_domain();
> > + if (!parent_dom)
> > + return 0;
> > +
> > + rcu_read_lock();
> > + child_dom = landlock_get_task_domain(child);
> > + err = domain_ptrace(parent_dom, child_dom);
> > + rcu_read_unlock();
> > +
> > + /*
> > + * For the ptrace_access_check case, we log the current/parent domain
> > + * and the child task.
> > + */
> > + if (err && !(mode & PTRACE_MODE_NOAUDIT)) {
> > + request.layer_plus_one = parent_dom->num_layers;
> > + landlock_log_denial(parent_dom, &request);
> > + }
> > +
> > + return err;
> > }
> >
> > /**
> > @@ -109,7 +128,35 @@ static int hook_ptrace_access_check(struct task_struct
> > *const child, */
> > static int hook_ptrace_traceme(struct task_struct *const parent)
> > {
> > - return task_ptrace(parent, current);
> > + const struct landlock_ruleset *parent_dom, *child_dom;
> > + struct landlock_request request = {
> > + .type = LANDLOCK_REQUEST_PTRACE,
> > + .audit = {
> > + .type = LSM_AUDIT_DATA_TASK,
> > + .u.tsk = parent,
> > + },
> > + };
> > + int err;
> > +
> > + child_dom = landlock_get_current_domain();
> > + rcu_read_lock();
> > + parent_dom = landlock_get_task_domain(parent);
> > + err = domain_ptrace(parent_dom, child_dom);
> > +
> > + /*
> > + * For the ptrace_traceme case, we log the domain which is the cause
> of
> > + * the denial, which means the parent domain instead of the current
> > + * domain. This may look weird because the ptrace_traceme action is a
> > + * request to be traced, but the semantic is consistent with
> > + * hook_ptrace_access_check().
> > + */
> > + if (err) {
> > + request.layer_plus_one = parent_dom->num_layers;
> > + landlock_log_denial(parent_dom, &request);
> > + }
> > +
> > + rcu_read_unlock();
>
> Nit: in the above function, you do the rcu_read_unlock() before the if.
This is because the RCU read-side critical section is needed for the
"other" task's domain reference, which is parent_dom here, and child_dom
in hook_ptrace_access_check().
>
> > + return err;
> > }
> >
> > /**
>
>
>
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 20/23] samples/landlock: Do not log denials from the sandboxer by default
2024-12-20 14:36 ` Francis Laniel
@ 2024-12-24 14:48 ` Mickaël Salaün
0 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2024-12-24 14:48 UTC (permalink / raw)
To: Francis Laniel
Cc: Eric Paris, Paul Moore, Günther Noack, Serge E . Hallyn,
Ben Scarlato, Casey Schaufler, Charles Zaffery, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Fri, Dec 20, 2024 at 03:36:30PM +0100, Francis Laniel wrote:
> Le vendredi 22 novembre 2024, 15:33:50 CET Mickaël Salaün a écrit :
> > Do not pollute audit logs because of unknown sandboxed programs.
> > Indeed, the sandboxer's security policy might not be fitted to the set
> > of sandboxed processes that could be spawned (e.g. from a shell).
> >
> > The LANDLOCK_RESTRICT_SELF_LOGLESS flag should be used for all similar
> > sandboxer tools by default. Only natively-sandboxed programs should not
> > use this flag.
> >
> > For test purpose, parse the LL_FORCE_LOG environment variable to still
> > log denials.
> >
> > Cc: Günther Noack <gnoack@google.com>
> > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > Link: https://lore.kernel.org/r/20241122143353.59367-21-mic@digikod.net
> > ---
> >
> > Changes since v2:
> > - New patch.
> > ---
> > samples/landlock/sandboxer.c | 35 ++++++++++++++++++++++++++++++++---
> > 1 file changed, 32 insertions(+), 3 deletions(-)
> >
> > diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
> > index 57565dfd74a2..49fd4fdb1dcf 100644
> > --- a/samples/landlock/sandboxer.c
> > +++ b/samples/landlock/sandboxer.c
> > @@ -58,6 +58,7 @@ static inline int landlock_restrict_self(const int
> > ruleset_fd, #define ENV_TCP_BIND_NAME "LL_TCP_BIND"
> > #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
> > #define ENV_SCOPED_NAME "LL_SCOPED"
> > +#define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
> > #define ENV_DELIMITER ":"
> >
> > static int str2num(const char *numstr, __u64 *num_dst)
> > @@ -288,7 +289,7 @@ static bool check_ruleset_scope(const char *const
> > env_var,
> >
> > /* clang-format on */
> >
> > -#define LANDLOCK_ABI_LAST 6
> > +#define LANDLOCK_ABI_LAST 7
> >
> > #define XSTR(s) #s
> > #define STR(s) XSTR(s)
> > @@ -315,6 +316,9 @@ static const char help[] =
> > " - \"a\" to restrict opening abstract unix sockets\n"
> > " - \"s\" to restrict sending signals\n"
> > "\n"
> > + "A sandboxer should not log denied access requests to avoid spamming
> logs,
> > " + "but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
> > + "\n"
> > "Example:\n"
> > ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
> > ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
> > @@ -333,7 +337,7 @@ int main(const int argc, char *const argv[], char *const
> > *const envp) const char *cmd_path;
> > char *const *cmd_argv;
> > int ruleset_fd, abi;
> > - char *env_port_name;
> > + char *env_port_name, *env_force_log;
> > __u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
> > access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
> >
> > @@ -344,6 +348,8 @@ int main(const int argc, char *const argv[], char *const
> > *const envp) .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> > LANDLOCK_SCOPE_SIGNAL,
> > };
> > + /* Do not pollute audit logs because of unknown sandboxed programs. */
> > + int restrict_flags = LANDLOCK_RESTRICT_SELF_LOGLESS;
> >
> > if (argc < 2) {
> > fprintf(stderr, help, argv[0]);
> > @@ -415,6 +421,12 @@ int main(const int argc, char *const argv[], char
> > *const *const envp) /* Removes LANDLOCK_SCOPE_* for ABI < 6 */
> > ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> > LANDLOCK_SCOPE_SIGNAL);
> > + __attribute__((fallthrough));
> > + case 6:
> > + /* Removes LANDLOCK_RESTRICT_SELF_LOGLESS for ABI < 7 */
> > + restrict_flags &= ~LANDLOCK_RESTRICT_SELF_LOGLESS;
> > +
> > + /* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
> > fprintf(stderr,
> > "Hint: You should update the running kernel "
> > "to leverage Landlock features "
> > @@ -449,6 +461,23 @@ int main(const int argc, char *const argv[], char
> > *const *const envp) if (check_ruleset_scope(ENV_SCOPED_NAME,
> > &ruleset_attr))
> > return 1;
> >
> > + /* Enables optional logs. */
> > + env_force_log = getenv(ENV_FORCE_LOG_NAME);
> > + if (env_force_log) {
> > + if (strcmp(env_force_log, "1") != 0) {
> > + fprintf(stderr,
> > + "Unknown value for " ENV_FORCE_LOG_NAME "\n");
>
> "Unknown value for " ENV_FORCE_LOG_NAME ", got %s, expected 1\n",
> env_force_log)
> This really helps users to figure out what they made wrong.
Indeed, I'll extend this message in the next version. Thanks.
> > + return 1;
> > + }
> > + if (!(restrict_flags & LANDLOCK_RESTRICT_SELF_LOGLESS)) {
> > + fprintf(stderr,
> > + "Audit logs not supported by current kernel\n");
> > + return 1;
> > + }
> > + restrict_flags &= ~LANDLOCK_RESTRICT_SELF_LOGLESS;
> > + unsetenv(ENV_FORCE_LOG_NAME);
> > + }
> > +
> > ruleset_fd =
> > landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr),
> 0);
> > if (ruleset_fd < 0) {
> > @@ -476,7 +505,7 @@ int main(const int argc, char *const argv[], char *const
> > *const envp) perror("Failed to restrict privileges");
> > goto err_close_ruleset;
> > }
> > - if (landlock_restrict_self(ruleset_fd, 0)) {
> > + if (landlock_restrict_self(ruleset_fd, restrict_flags)) {
> > perror("Failed to enforce ruleset");
> > goto err_close_ruleset;
> > }
>
>
>
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 1/23] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set
2024-11-22 14:33 ` [PATCH v3 01/23] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set Mickaël Salaün
@ 2025-01-04 16:47 ` Paul Moore
0 siblings, 0 replies; 46+ messages in thread
From: Paul Moore @ 2025-01-04 16:47 UTC (permalink / raw)
To: Mickaël Salaün, Eric Paris, Günther Noack,
Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
>
> When CONFIG_AUDIT is set, its CONFIG_NET dependency is also set, and the
> dev_get_by_index and init_net symbols (used by dump_common_audit_data)
> are found by the linker. dump_common_audit_data() should then failed to
> build when CONFIG_NET is not set. However, because the compiler is
> smart, it knows that audit_log_start() always return NULL when
> !CONFIG_AUDIT, and it doesn't build the body of common_lsm_audit(). As
> a side effect, dump_common_audit_data() is not built and the linker
> doesn't error out because of missing symbols.
>
> Let's only build lsm_audit.o when CONFIG_SECURITY and CONFIG_AUDIT are
> both set, which is checked with the new CONFIG_HAS_SECURITY_AUDIT.
>
> ipv4_skb_to_auditdata() and ipv6_skb_to_auditdata() are only used by
> Smack if CONFIG_AUDIT is set, so they don't need fake implementations.
>
> Because common_lsm_audit() is used in multiple places without
> CONFIG_AUDIT checks, add a fake implementation.
>
> Cc: Casey Schaufler <casey@schaufler-ca.com>
> Cc: James Morris <jmorris@namei.org>
> Cc: Paul Moore <paul@paul-moore.com>
> Cc: Serge E. Hallyn <serge@hallyn.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241122143353.59367-2-mic@digikod.net
> ---
> Changes since v2:
> - Add CONFIG_HAS_SECURITY_AUDIT to fix the build with AUDIT &&
> !SECURITY, reported by Guenter Roeck.
> ---
> include/linux/lsm_audit.h | 14 ++++++++++++++
> security/Kconfig | 5 +++++
> security/Makefile | 2 +-
> 3 files changed, 20 insertions(+), 1 deletion(-)
This fix should probably stand alone from the rest of the patchset as it
is a worthwhile fix independent of the Landlock feature additions. I'm
going to go ahead and merge this via the lsm/dev branch now, if anyone
has any objections please let me know.
Thanks!
--
paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 2/23] lsm: Add audit_log_lsm_data() helper
2024-11-22 14:33 ` [PATCH v3 02/23] lsm: Add audit_log_lsm_data() helper Mickaël Salaün
@ 2025-01-05 1:23 ` Paul Moore
0 siblings, 0 replies; 46+ messages in thread
From: Paul Moore @ 2025-01-05 1:23 UTC (permalink / raw)
To: Mickaël Salaün, Eric Paris, Günther Noack,
Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
>
> Extract code from dump_common_audit_data() into the audit_log_lsm_data()
> helper. This helps reuse common LSM audit data while not abusing
> AUDIT_AVC records because of the common_lsm_audit() helper.
>
> Cc: Casey Schaufler <casey@schaufler-ca.com>
> Cc: James Morris <jmorris@namei.org>
> Cc: Paul Moore <paul@paul-moore.com>
> Cc: Serge E. Hallyn <serge@hallyn.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241122143353.59367-3-mic@digikod.net
> ---
> Changes since v1:
> - Fix commit message (spotted by Paul).
> - Constify dump_common_audit_data()'s and audit_log_lsm_data()'s "a"
> argument.
> - Fix build without CONFIG_NET: see previous patch.
> ---
> include/linux/lsm_audit.h | 8 ++++++++
> security/lsm_audit.c | 27 ++++++++++++++++++---------
> 2 files changed, 26 insertions(+), 9 deletions(-)
Acked-by: Paul Moore <paul@paul-moore.com>
--
paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 8/23] landlock: Log ptrace denials
2024-11-22 14:33 ` [PATCH v3 08/23] landlock: Log ptrace denials Mickaël Salaün
2024-12-20 14:36 ` Francis Laniel
@ 2025-01-05 1:23 ` Paul Moore
2025-01-06 14:45 ` Mickaël Salaün
1 sibling, 1 reply; 46+ messages in thread
From: Paul Moore @ 2025-01-05 1:23 UTC (permalink / raw)
To: Mickaël Salaün, Eric Paris, Günther Noack,
Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
>
> Add audit support to ptrace_access_check and ptrace_traceme hooks.
>
> Add a new AUDIT_LANDLOCK_DENY record type dedicated to any Landlock
> denials.
>
> Log the domain ID restricting the action, the domain's blockers that are
> missing to allow the requested access, and the target task.
>
> The blockers are implicit restrictions (e.g. ptrace), or explicit access
> rights (e.g. filesystem), or explicit scopes (e.g. signal).
>
> For the ptrace_access_check case, we log the current/parent domain and
> the child task. For the ptrace_traceme case, we log the parent domain
> and the parent task. Indeed, the requester is the current task, but the
> action would be performed by the parent task.
>
> The quick return for non-landlocked tasks is moved from task_ptrace() to
> each LSM hooks.
>
> Audit event sample:
>
> type=LL_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
> type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
>
> Cc: Günther Noack <gnoack@google.com>
> Cc: Paul Moore <paul@paul-moore.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241122143353.59367-9-mic@digikod.net
> ---
> 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.
>
> Changes since v1:
> - Move most audit code to this patch.
> - Rebase on the TCP patch series.
> - Don't log missing access right: simplify and make it generic for rule
> types.
> - Don't log errno and then don't wrap the error with
> landlock_log_request(), as suggested by Jeff.
> - Add a WARN_ON_ONCE() check to never dereference null pointers.
> - Only log when audit is enabled.
> - Don't log task's PID/TID with log_task() because it would be redundant
> with the SYSCALL record.
> - Move the "op" in front and rename "domain" to "denying_domain" to make
> it more consistent with other entries.
> - Don't update the request with the domain ID but add an helper to get
> it from the layer masks (and in a following commit with a struct
> file).
> - Revamp get_domain_id_from_layer_masks() into
> get_level_from_layer_masks().
> - For ptrace_traceme, log the parent domain instead of the current one.
> - Add documentation.
> - Rename AUDIT_LANDLOCK_DENIAL to AUDIT_LANDLOCK_DENY.
> - Only log the domain ID and the target task.
> - Log "blockers", which are either implicit restrictions (e.g. ptrace)
> or explicit access rights (e.g. filesystem), or scopes (e.g. signal).
> - Don't log LSM hook names/operations.
> - Pick an audit event ID folling the IPE ones.
> - Add KUnit tests.
> ---
> include/uapi/linux/audit.h | 3 +-
> security/landlock/Makefile | 2 +-
> security/landlock/audit.c | 137 ++++++++++++++++++++++++++++++++++++
> security/landlock/audit.h | 52 ++++++++++++++
> security/landlock/domain.c | 21 ++++++
> security/landlock/domain.h | 17 +++++
> security/landlock/ruleset.c | 3 +
> security/landlock/task.c | 91 ++++++++++++++++++------
> 8 files changed, 302 insertions(+), 24 deletions(-)
> create mode 100644 security/landlock/audit.c
> create mode 100644 security/landlock/audit.h
> create mode 100644 security/landlock/domain.c
>
> diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> index 75e21a135483..60c909c396c0 100644
> --- a/include/uapi/linux/audit.h
> +++ b/include/uapi/linux/audit.h
> @@ -33,7 +33,7 @@
> * 1100 - 1199 user space trusted application messages
> * 1200 - 1299 messages internal to the audit daemon
> * 1300 - 1399 audit event messages
> - * 1400 - 1499 SE Linux use
> + * 1400 - 1499 access control messages
Thank you :)
I'm also reminded once again that the original audit devs stubbornly
used "SE Linux" instead of "SELinux" :/
> * 1500 - 1599 kernel LSPP events
> * 1600 - 1699 kernel crypto events
> * 1700 - 1799 kernel anomaly records
> @@ -146,6 +146,7 @@
> #define AUDIT_IPE_ACCESS 1420 /* IPE denial or grant */
> #define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */
> #define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */
> +#define AUDIT_LANDLOCK_DENY 1423 /* Landlock denial */
Generally speaking, we don't really encode denial/allowed verdicts into
the audit record type, instead we ask that developers use a field like
"access=" to indicate that an action was allowed or denied.
How about AUDIT_LANDLOCK_ACCESS ?
--
paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 9/23] audit: Add a new audit_get_ctime() helper
2024-11-22 14:33 ` [PATCH v3 09/23] audit: Add a new audit_get_ctime() helper Mickaël Salaün
@ 2025-01-05 1:23 ` Paul Moore
0 siblings, 0 replies; 46+ messages in thread
From: Paul Moore @ 2025-01-05 1:23 UTC (permalink / raw)
To: Mickaël Salaün, Eric Paris, Günther Noack,
Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
>
> It may be useful to synchronize with the audit's timestamp e.g., to
> identify asynchronous events as being created with a previous audit
> record (see next commit).
>
> auditsc_get_stamp() does more than just getting a timestamp, so add a
> new helper instead of exposing it and risking side effects.
>
> It should be noted that we cannot reliably expose event's serial numbers
> because there may not be any related event, which would then create
> holes in the sequence of serial numbers.
>
> Cc: Eric Paris <eparis@redhat.com>
> Cc: Paul Moore <paul@paul-moore.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241122143353.59367-10-mic@digikod.net
> ---
> Changes since v2:
> - New patch.
> ---
> include/linux/audit.h | 8 ++++++++
> kernel/auditsc.c | 21 ++++++++++++++++++---
> 2 files changed, 26 insertions(+), 3 deletions(-)
I need to see where you actually use this, but I'm not sure I want to
expost the audit timestamp outside of the audit subsystem.
Okay, I found at least one user in patch 10/23, and no, that's not
something I think we want to support with audit. More about this in
patch 10/23.
--
paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 10/23] landlock: Log domain properties and release
2024-11-22 14:33 ` [PATCH v3 10/23] landlock: Log domain properties and release Mickaël Salaün
@ 2025-01-05 1:23 ` Paul Moore
2025-01-06 14:51 ` Mickaël Salaün
0 siblings, 1 reply; 46+ messages in thread
From: Paul Moore @ 2025-01-05 1:23 UTC (permalink / raw)
To: Mickaël Salaün, Eric Paris, Günther Noack,
Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
>
> TODO: Update sample with audit's creation time and now-removed parent
> hierarchy
>
> Asynchronously log domain information when it first denies an access.
> This minimize the amount of generated logs, which makes it possible to
> always log denials since they should not happen (except with the new
> LANDLOCK_RESTRICT_SELF_LOGLESS flag). These records are identified by
> AUDIT_LANDLOCK_DOM_INFO.
>
> Log domain deletion with the AUDIT_LANDLOCK_DOM_DROP record type when
> a domain was previously logged. This makes it possible for user space
> to free potential resources when a domain ID will never show again.
>
> The logged domain properties include:
> - the domain ID
> - creation time
> - its creator's PID
> - its creator's UID
> - its creator's executable path (exe)
> - its creator's command line (comm)
>
> This require each domain to save some task properties at creation time:
> time, PID, credentials, exe path, and comm.
>
> It should be noted that we cannot use audit event's serial numbers
> because there may not be any related event. However, it is still useful
> to use the same potential timestamp instead of a close one. What really
> matter is how old the related Landlock domain is, not the uniquiness of
> the creation time. If audit is not enabled, Landlock creates its own
> timestamp. This timestamp will be exposed to user space with a future
> unprivileged interface.
>
> Audit event sample for a first denial:
>
> type=LL_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
> type=LL_DOM_INFO msg=audit(1732186800.349:44): domain=195ba459b creation=1732186800.345 pid=300 uid=0 exe="/root/sandboxer" comm="sandboxer"
> type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
As mentioned in patch 9/23, I don't want subsystems external to audit
to access the audit timestamp information, so the "creation=" field
in the audit event would need to be removed. Assuming that the timestamp
was used either to reference the original domain creation and/or simply
provide some additional information for analysis, all of that information
should already be in the audit log, assuming of course that you are
logging domain creation (which you should, at least as an option).
Also, is there a good reason why the LL_DOM_INFO information can't be
recorded in the LL_DENY (or LL_ACCESS) record? I think that would be
preferable.
> Audit event sample for logged domains deletion:
>
> type=LL_DOM_DROP msg=audit(1732186800.393:45): domain=195ba459b
>
> Cc: Günther Noack <gnoack@google.com>
> Cc: Paul Moore <paul@paul-moore.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241122143353.59367-11-mic@digikod.net
> ---
> Questions:
> - Should we also log the creator's loginuid?
> - Should we also log the creator's sessionid?
Creation of a Landlock domain can only happen through the Landlock
syscalls, yes? If so, that information should already be logged in
the associated syscall record (see the "auid=" and "ses=" fields )and
we generally try to avoid duplicating information across records in
the same audit event.
> Changes since v2:
> - Fix docstring.
> - Fix log_status check in log_hierarchy() to also log
> LANDLOCK_LOG_DISABLED.
> - Add audit's creation time to domain's properties.
> - Use hexadecimal notation for domain IDs.
> - Remove domain's parent records: parent domains are not really useful
> in the logs. They will be available with the upcoming introspection
> feature though.
> - Extend commit message with audit's timestamp explanation.
>
> Changes since v1:
> - Add a ruleset's version for atomic logs.
> - Rebased on the TCP patch series.
> - Rename operation using "_" instead of "-".
> - Rename AUDIT_LANDLOCK to AUDIT_LANDLOCK_RULESET.
> - Only log when audit is enabled, but always set domain IDs.
> - Don't log task's PID/TID with log_task() because it would be redundant
> with the SYSCALL record.
> - Remove race condition when logging ruleset creation and logging
> ruleset modification while the related file descriptor was already
> registered but the ruleset creation not logged yet.
> - Fix domain drop logs.
> - Move the domain drop record from the previous patch into this one.
> - Do not log domain creation but log first domain use instead.
> - Save task's properties that sandbox themselves.
> ---
> include/uapi/linux/audit.h | 2 +
> security/landlock/audit.c | 74 ++++++++++++++++++++++++++++++++++++
> security/landlock/audit.h | 7 ++++
> security/landlock/domain.c | 41 ++++++++++++++++++++
> security/landlock/domain.h | 43 +++++++++++++++++++++
> security/landlock/ruleset.c | 7 ++++
> security/landlock/ruleset.h | 1 +
> security/landlock/syscalls.c | 1 +
> 8 files changed, 176 insertions(+)
>
> diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> index 60c909c396c0..a72f7b3403be 100644
> --- a/include/uapi/linux/audit.h
> +++ b/include/uapi/linux/audit.h
> @@ -147,6 +147,8 @@
> #define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */
> #define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */
> #define AUDIT_LANDLOCK_DENY 1423 /* Landlock denial */
> +#define AUDIT_LANDLOCK_DOM_INFO 1424 /* Landlock domain properties */
> +#define AUDIT_LANDLOCK_DOM_DROP 1425 /* Landlock domain release */
See my comment about regarding AUDIT_LANDLOCK_DOM_INFO.
Similar to my previous comments regarding AUDIT_LANDLOCK_DENY, it might
be a good idea to change AUDIT_LANDLOCK_DOM_DROP to simply
AUDIT_LANDLOCK_DOM and use an "op=" field to indicate a drop, creation,
or other event.
> diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> index eab6b3a8a231..2d0a96797dd4 100644
> --- a/security/landlock/audit.c
> +++ b/security/landlock/audit.c
> @@ -8,8 +8,11 @@
> #include <kunit/test.h>
> #include <linux/audit.h>
> #include <linux/lsm_audit.h>
> +#include <linux/pid.h>
> +#include <linux/uidgid.h>
>
> #include "audit.h"
> +#include "cred.h"
> #include "domain.h"
> #include "ruleset.h"
>
> @@ -30,6 +33,41 @@ static void log_blockers(struct audit_buffer *const ab,
> audit_log_format(ab, "%s", get_blocker(type));
> }
>
> +static void log_node(struct landlock_hierarchy *const node)
> +{
> + struct audit_buffer *ab;
> +
> + if (WARN_ON_ONCE(!node))
> + return;
> +
> + /* Ignores already logged domains. */
> + if (READ_ONCE(node->log_status) == LANDLOCK_LOG_RECORDED)
> + return;
> +
> + ab = audit_log_start(audit_context(), GFP_ATOMIC,
> + AUDIT_LANDLOCK_DOM_INFO);
> + if (!ab)
> + return;
> +
> + WARN_ON_ONCE(node->id == 0);
It seems like you might want to move the WARN_ON assertion up with the
other WARN_ON check?
> + audit_log_format(ab, "domain=%llx creation=%llu.%03lu pid=%d uid=%u",
> + node->id,
> + /* See audit_log_start() */
> + (unsigned long long)node->creation.tv_sec,
> + node->creation.tv_nsec / 1000000, pid_nr(node->pid),
> + from_kuid(&init_user_ns, node->cred->uid));
> + audit_log_d_path(ab, " exe=", &node->exe);
> + audit_log_format(ab, " comm=");
> + audit_log_untrustedstring(ab, node->comm);
> + audit_log_end(ab);
> +
> + /*
> + * There may be race condition leading to logging of the same domain
> + * several times but that is OK.
> + */
> + WRITE_ONCE(node->log_status, LANDLOCK_LOG_RECORDED);
> +}
> +
> static struct landlock_hierarchy *
> get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
> {
--
paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 17/23] landlock: Log TCP bind and connect denials
2024-11-22 14:33 ` [PATCH v3 17/23] landlock: Log TCP bind and connect denials Mickaël Salaün
@ 2025-01-05 1:23 ` Paul Moore
2025-01-06 14:51 ` Mickaël Salaün
0 siblings, 1 reply; 46+ messages in thread
From: Paul Moore @ 2025-01-05 1:23 UTC (permalink / raw)
To: Mickaël Salaün, Eric Paris, Günther Noack,
Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
>
> Add audit support to socket_bind and socket_connect hooks.
>
> Audit event sample:
>
> type=LL_DENY [...]: domain=195ba459b blockers=net_connect_tcp daddr=127.0.0.1 dest=80
The destination address and port is already captured in the SOCKADDR
record for bind() and connect(), please don't duplicate it here.
> Cc: Günther Noack <gnoack@google.com>
> Cc: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241122143353.59367-18-mic@digikod.net
> ---
> Changes since v2:
> - Remove potentially superfluous IPv6 saddr log, spotted by Francis
> Laniel.
> - Cosmetic improvements.
> ---
> security/landlock/audit.c | 12 +++++++++
> security/landlock/audit.h | 1 +
> security/landlock/net.c | 51 ++++++++++++++++++++++++++++++++++++---
> 3 files changed, 60 insertions(+), 4 deletions(-)
--
paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 18/23] landlock: Log scoped denials
2024-11-22 14:33 ` [PATCH v3 18/23] landlock: Log scoped denials Mickaël Salaün
@ 2025-01-05 1:23 ` Paul Moore
2025-01-06 14:51 ` Mickaël Salaün
0 siblings, 1 reply; 46+ messages in thread
From: Paul Moore @ 2025-01-05 1:23 UTC (permalink / raw)
To: Mickaël Salaün, Eric Paris, Günther Noack,
Serge E . Hallyn
Cc: Mickaël Salaün, Ben Scarlato, Casey Schaufler,
Charles Zaffery, Francis Laniel, James Morris, Jann Horn, Jeff Xu,
Jorge Lucangeli Obes, Kees Cook, Konstantin Meskhidze,
Matt Bobrowski, Mikhail Ivanov, Phil Sutter, Praveen K Paladugu,
Robert Salvet, Shervin Oloumi, Song Liu, Tahera Fahimi, audit,
linux-kernel, linux-security-module
On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
>
> Add audit support for unix_stream_connect, unix_may_send, task_kill, and
> file_send_sigiotask hooks.
>
> Audit event sample:
>
> type=LL_DENY [...]: domain=195ba459b blockers=scope_abstract_unix_socket path=00666F6F
Similar to 17/23, I believe the SOCKADDR record should already capture
the socket address information.
It would also be nice to see an example record on a signal event.
> Cc: Günther Noack <gnoack@google.com>
> Cc: Tahera Fahimi <fahimitahera@gmail.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241122143353.59367-19-mic@digikod.net
> ---
> Changes since v1:
> - New patch.
> ---
> security/landlock/audit.c | 8 ++++++
> security/landlock/audit.h | 2 ++
> security/landlock/task.c | 58 ++++++++++++++++++++++++++++++++++++---
> 3 files changed, 64 insertions(+), 4 deletions(-)
--
paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 8/23] landlock: Log ptrace denials
2025-01-05 1:23 ` [PATCH v3 8/23] " Paul Moore
@ 2025-01-06 14:45 ` Mickaël Salaün
0 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2025-01-06 14:45 UTC (permalink / raw)
To: Paul Moore
Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
Casey Schaufler, Charles Zaffery, Francis Laniel, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Sat, Jan 04, 2025 at 08:23:49PM -0500, Paul Moore wrote:
> On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> >
> > Add audit support to ptrace_access_check and ptrace_traceme hooks.
> >
> > Add a new AUDIT_LANDLOCK_DENY record type dedicated to any Landlock
> > denials.
> >
> > Log the domain ID restricting the action, the domain's blockers that are
> > missing to allow the requested access, and the target task.
> >
> > The blockers are implicit restrictions (e.g. ptrace), or explicit access
> > rights (e.g. filesystem), or explicit scopes (e.g. signal).
> >
> > For the ptrace_access_check case, we log the current/parent domain and
> > the child task. For the ptrace_traceme case, we log the parent domain
> > and the parent task. Indeed, the requester is the current task, but the
> > action would be performed by the parent task.
> >
> > The quick return for non-landlocked tasks is moved from task_ptrace() to
> > each LSM hooks.
> >
> > Audit event sample:
> >
> > type=LL_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
> > type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
> >
> > Cc: Günther Noack <gnoack@google.com>
> > Cc: Paul Moore <paul@paul-moore.com>
> > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > Link: https://lore.kernel.org/r/20241122143353.59367-9-mic@digikod.net
> > ---
> > 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.
> >
> > Changes since v1:
> > - Move most audit code to this patch.
> > - Rebase on the TCP patch series.
> > - Don't log missing access right: simplify and make it generic for rule
> > types.
> > - Don't log errno and then don't wrap the error with
> > landlock_log_request(), as suggested by Jeff.
> > - Add a WARN_ON_ONCE() check to never dereference null pointers.
> > - Only log when audit is enabled.
> > - Don't log task's PID/TID with log_task() because it would be redundant
> > with the SYSCALL record.
> > - Move the "op" in front and rename "domain" to "denying_domain" to make
> > it more consistent with other entries.
> > - Don't update the request with the domain ID but add an helper to get
> > it from the layer masks (and in a following commit with a struct
> > file).
> > - Revamp get_domain_id_from_layer_masks() into
> > get_level_from_layer_masks().
> > - For ptrace_traceme, log the parent domain instead of the current one.
> > - Add documentation.
> > - Rename AUDIT_LANDLOCK_DENIAL to AUDIT_LANDLOCK_DENY.
> > - Only log the domain ID and the target task.
> > - Log "blockers", which are either implicit restrictions (e.g. ptrace)
> > or explicit access rights (e.g. filesystem), or scopes (e.g. signal).
> > - Don't log LSM hook names/operations.
> > - Pick an audit event ID folling the IPE ones.
> > - Add KUnit tests.
> > ---
> > include/uapi/linux/audit.h | 3 +-
> > security/landlock/Makefile | 2 +-
> > security/landlock/audit.c | 137 ++++++++++++++++++++++++++++++++++++
> > security/landlock/audit.h | 52 ++++++++++++++
> > security/landlock/domain.c | 21 ++++++
> > security/landlock/domain.h | 17 +++++
> > security/landlock/ruleset.c | 3 +
> > security/landlock/task.c | 91 ++++++++++++++++++------
> > 8 files changed, 302 insertions(+), 24 deletions(-)
> > create mode 100644 security/landlock/audit.c
> > create mode 100644 security/landlock/audit.h
> > create mode 100644 security/landlock/domain.c
> >
> > diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> > index 75e21a135483..60c909c396c0 100644
> > --- a/include/uapi/linux/audit.h
> > +++ b/include/uapi/linux/audit.h
> > @@ -33,7 +33,7 @@
> > * 1100 - 1199 user space trusted application messages
> > * 1200 - 1299 messages internal to the audit daemon
> > * 1300 - 1399 audit event messages
> > - * 1400 - 1499 SE Linux use
> > + * 1400 - 1499 access control messages
>
> Thank you :)
>
> I'm also reminded once again that the original audit devs stubbornly
> used "SE Linux" instead of "SELinux" :/
>
> > * 1500 - 1599 kernel LSPP events
> > * 1600 - 1699 kernel crypto events
> > * 1700 - 1799 kernel anomaly records
> > @@ -146,6 +146,7 @@
> > #define AUDIT_IPE_ACCESS 1420 /* IPE denial or grant */
> > #define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */
> > #define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */
> > +#define AUDIT_LANDLOCK_DENY 1423 /* Landlock denial */
>
> Generally speaking, we don't really encode denial/allowed verdicts into
> the audit record type, instead we ask that developers use a field like
> "access=" to indicate that an action was allowed or denied.
>
> How about AUDIT_LANDLOCK_ACCESS ?
A stronger type with the "denied" semantic makes more sense to me,
especially for Landlock which is unprivileged, and it makes it clear
that it should only impact performance and log size (i.e. audit log
creation) for denied actions. Having dedicated record types enables
users to easily and efficiently filter log with audit rules.
AUDIT_LANDLOCK_DENY is also a clear signal that something unexpected is
ongoing (see the LANDLOCK_RESTRICT_SELF_LOGLESS flag). The next patch
series will also contain a new kind of audit rule to specifically
identify the origin of the policy that created this denied event, which
should make more sense.
Because of its unprivileged nature, Landlock will never log granted
accesses by default. In the future, we might want a permissive-like
mode for Landlock, but this will be optional, and I would also strongly
prefer to add new audit record types for new semantics. That would also
help log parsers to stick to the current deny-only semantic and avoid
misinterpretations or even noise from debug/test log entries.
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 10/23] landlock: Log domain properties and release
2025-01-05 1:23 ` Paul Moore
@ 2025-01-06 14:51 ` Mickaël Salaün
2025-01-06 21:56 ` Paul Moore
0 siblings, 1 reply; 46+ messages in thread
From: Mickaël Salaün @ 2025-01-06 14:51 UTC (permalink / raw)
To: Paul Moore
Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
Casey Schaufler, Charles Zaffery, Francis Laniel, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Sat, Jan 04, 2025 at 08:23:51PM -0500, Paul Moore wrote:
> On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> >
> > TODO: Update sample with audit's creation time and now-removed parent
> > hierarchy
> >
> > Asynchronously log domain information when it first denies an access.
> > This minimize the amount of generated logs, which makes it possible to
> > always log denials since they should not happen (except with the new
> > LANDLOCK_RESTRICT_SELF_LOGLESS flag). These records are identified by
> > AUDIT_LANDLOCK_DOM_INFO.
> >
> > Log domain deletion with the AUDIT_LANDLOCK_DOM_DROP record type when
> > a domain was previously logged. This makes it possible for user space
> > to free potential resources when a domain ID will never show again.
> >
> > The logged domain properties include:
> > - the domain ID
> > - creation time
> > - its creator's PID
> > - its creator's UID
> > - its creator's executable path (exe)
> > - its creator's command line (comm)
> >
> > This require each domain to save some task properties at creation time:
> > time, PID, credentials, exe path, and comm.
> >
> > It should be noted that we cannot use audit event's serial numbers
> > because there may not be any related event. However, it is still useful
> > to use the same potential timestamp instead of a close one. What really
> > matter is how old the related Landlock domain is, not the uniquiness of
> > the creation time. If audit is not enabled, Landlock creates its own
> > timestamp. This timestamp will be exposed to user space with a future
> > unprivileged interface.
> >
> > Audit event sample for a first denial:
> >
> > type=LL_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
> > type=LL_DOM_INFO msg=audit(1732186800.349:44): domain=195ba459b creation=1732186800.345 pid=300 uid=0 exe="/root/sandboxer" comm="sandboxer"
> > type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
>
> As mentioned in patch 9/23, I don't want subsystems external to audit
> to access the audit timestamp information, so the "creation=" field
> in the audit event would need to be removed. Assuming that the timestamp
> was used either to reference the original domain creation and/or simply
> provide some additional information for analysis, all of that information
> should already be in the audit log, assuming of course that you are
> logging domain creation (which you should, at least as an option).
As explained in this patch, we don't want to (and cannot realistically)
log domain creations. That would make the audit support for Landlock
unusable. Moreover, these information is useless and only add noise
unless there is a denial, hence this asynchronous approach. However,
users may want to log some syscalls, including landlock_restrict_self(),
and it would make audit logs more consistent using the same timestamp as
the Landlock domain creation time. I'm wondering why exposing this
timestamp to the kernel would be an issue whereas it is already exposed
to user space.
If you're really opposed to it I can create a new unrelated timestamp
specific to Landlock.
>
> Also, is there a good reason why the LL_DOM_INFO information can't be
> recorded in the LL_DENY (or LL_ACCESS) record? I think that would be
> preferable.
The goal of the standalone LL_DOM_INFO record type is to limit useless
log verbosity. Including this information in LL_DENY would have two
downsides:
- it would increases the length of *all* LL_DENY messages
- it would make it more difficult to extend this new mixed messages with
access-related informations (e.g. file property) and domain-related
informations (and associate them with either the object or the
domain).
I prefer to have clear semantic with distinct and dedicated audit record
types. Relying on different record type also enable users to
efficiently filter them.
>
> > Audit event sample for logged domains deletion:
> >
> > type=LL_DOM_DROP msg=audit(1732186800.393:45): domain=195ba459b
> >
> > Cc: Günther Noack <gnoack@google.com>
> > Cc: Paul Moore <paul@paul-moore.com>
> > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > Link: https://lore.kernel.org/r/20241122143353.59367-11-mic@digikod.net
> > ---
> > Questions:
> > - Should we also log the creator's loginuid?
> > - Should we also log the creator's sessionid?
>
> Creation of a Landlock domain can only happen through the Landlock
> syscalls, yes? If so, that information should already be logged in
> the associated syscall record (see the "auid=" and "ses=" fields )and
> we generally try to avoid duplicating information across records in
> the same audit event.
The specificity of Landlock compared to existing supported systems is
that we cannot log domain creation for the reason I explain before.
We might extend the asynchronous LL_DOM_INFO message with such
information in the future though.
>
> > Changes since v2:
> > - Fix docstring.
> > - Fix log_status check in log_hierarchy() to also log
> > LANDLOCK_LOG_DISABLED.
> > - Add audit's creation time to domain's properties.
> > - Use hexadecimal notation for domain IDs.
> > - Remove domain's parent records: parent domains are not really useful
> > in the logs. They will be available with the upcoming introspection
> > feature though.
> > - Extend commit message with audit's timestamp explanation.
> >
> > Changes since v1:
> > - Add a ruleset's version for atomic logs.
> > - Rebased on the TCP patch series.
> > - Rename operation using "_" instead of "-".
> > - Rename AUDIT_LANDLOCK to AUDIT_LANDLOCK_RULESET.
> > - Only log when audit is enabled, but always set domain IDs.
> > - Don't log task's PID/TID with log_task() because it would be redundant
> > with the SYSCALL record.
> > - Remove race condition when logging ruleset creation and logging
> > ruleset modification while the related file descriptor was already
> > registered but the ruleset creation not logged yet.
> > - Fix domain drop logs.
> > - Move the domain drop record from the previous patch into this one.
> > - Do not log domain creation but log first domain use instead.
> > - Save task's properties that sandbox themselves.
> > ---
> > include/uapi/linux/audit.h | 2 +
> > security/landlock/audit.c | 74 ++++++++++++++++++++++++++++++++++++
> > security/landlock/audit.h | 7 ++++
> > security/landlock/domain.c | 41 ++++++++++++++++++++
> > security/landlock/domain.h | 43 +++++++++++++++++++++
> > security/landlock/ruleset.c | 7 ++++
> > security/landlock/ruleset.h | 1 +
> > security/landlock/syscalls.c | 1 +
> > 8 files changed, 176 insertions(+)
> >
> > diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> > index 60c909c396c0..a72f7b3403be 100644
> > --- a/include/uapi/linux/audit.h
> > +++ b/include/uapi/linux/audit.h
> > @@ -147,6 +147,8 @@
> > #define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */
> > #define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */
> > #define AUDIT_LANDLOCK_DENY 1423 /* Landlock denial */
> > +#define AUDIT_LANDLOCK_DOM_INFO 1424 /* Landlock domain properties */
> > +#define AUDIT_LANDLOCK_DOM_DROP 1425 /* Landlock domain release */
>
> See my comment about regarding AUDIT_LANDLOCK_DOM_INFO.
>
> Similar to my previous comments regarding AUDIT_LANDLOCK_DENY, it might
> be a good idea to change AUDIT_LANDLOCK_DOM_DROP to simply
> AUDIT_LANDLOCK_DOM and use an "op=" field to indicate a drop, creation,
> or other event.
Using a dedicated audit record type enables users to efficiently filter
according to their type, and (in this specific case, sightly) reduce log
size.
>
> > diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> > index eab6b3a8a231..2d0a96797dd4 100644
> > --- a/security/landlock/audit.c
> > +++ b/security/landlock/audit.c
> > @@ -8,8 +8,11 @@
> > #include <kunit/test.h>
> > #include <linux/audit.h>
> > #include <linux/lsm_audit.h>
> > +#include <linux/pid.h>
> > +#include <linux/uidgid.h>
> >
> > #include "audit.h"
> > +#include "cred.h"
> > #include "domain.h"
> > #include "ruleset.h"
> >
> > @@ -30,6 +33,41 @@ static void log_blockers(struct audit_buffer *const ab,
> > audit_log_format(ab, "%s", get_blocker(type));
> > }
> >
> > +static void log_node(struct landlock_hierarchy *const node)
> > +{
> > + struct audit_buffer *ab;
> > +
> > + if (WARN_ON_ONCE(!node))
> > + return;
> > +
> > + /* Ignores already logged domains. */
> > + if (READ_ONCE(node->log_status) == LANDLOCK_LOG_RECORDED)
> > + return;
> > +
> > + ab = audit_log_start(audit_context(), GFP_ATOMIC,
> > + AUDIT_LANDLOCK_DOM_INFO);
> > + if (!ab)
> > + return;
> > +
> > + WARN_ON_ONCE(node->id == 0);
>
> It seems like you might want to move the WARN_ON assertion up with the
> other WARN_ON check?
I wanted to limit this check but only do it before actually using this
ID.
>
> > + audit_log_format(ab, "domain=%llx creation=%llu.%03lu pid=%d uid=%u",
> > + node->id,
> > + /* See audit_log_start() */
> > + (unsigned long long)node->creation.tv_sec,
> > + node->creation.tv_nsec / 1000000, pid_nr(node->pid),
> > + from_kuid(&init_user_ns, node->cred->uid));
> > + audit_log_d_path(ab, " exe=", &node->exe);
> > + audit_log_format(ab, " comm=");
> > + audit_log_untrustedstring(ab, node->comm);
> > + audit_log_end(ab);
> > +
> > + /*
> > + * There may be race condition leading to logging of the same domain
> > + * several times but that is OK.
> > + */
> > + WRITE_ONCE(node->log_status, LANDLOCK_LOG_RECORDED);
> > +}
> > +
> > static struct landlock_hierarchy *
> > get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
> > {
>
> --
> paul-moore.com
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 17/23] landlock: Log TCP bind and connect denials
2025-01-05 1:23 ` Paul Moore
@ 2025-01-06 14:51 ` Mickaël Salaün
2025-01-06 22:29 ` Paul Moore
0 siblings, 1 reply; 46+ messages in thread
From: Mickaël Salaün @ 2025-01-06 14:51 UTC (permalink / raw)
To: Paul Moore
Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
Casey Schaufler, Charles Zaffery, Francis Laniel, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Sat, Jan 04, 2025 at 08:23:52PM -0500, Paul Moore wrote:
> On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> >
> > Add audit support to socket_bind and socket_connect hooks.
> >
> > Audit event sample:
> >
> > type=LL_DENY [...]: domain=195ba459b blockers=net_connect_tcp daddr=127.0.0.1 dest=80
>
> The destination address and port is already captured in the SOCKADDR
> record for bind() and connect(), please don't duplicate it here.
This does not show up when a connect or bind is denied. I guess this is
because move_addr_to_kernel() is called at syscall entry when there is
no context, whereas a Landlock denial is created after that. For this
to work, users would have to log a list of syscalls, which would not be
usable (nor reliably maintainable) for most users. I guess this might
be different with io_uring too.
SELinux and other LSMs log it this way, which makes sense to me.
>
> > Cc: Günther Noack <gnoack@google.com>
> > Cc: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> > Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > Link: https://lore.kernel.org/r/20241122143353.59367-18-mic@digikod.net
> > ---
> > Changes since v2:
> > - Remove potentially superfluous IPv6 saddr log, spotted by Francis
> > Laniel.
> > - Cosmetic improvements.
> > ---
> > security/landlock/audit.c | 12 +++++++++
> > security/landlock/audit.h | 1 +
> > security/landlock/net.c | 51 ++++++++++++++++++++++++++++++++++++---
> > 3 files changed, 60 insertions(+), 4 deletions(-)
>
> --
> paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 18/23] landlock: Log scoped denials
2025-01-05 1:23 ` Paul Moore
@ 2025-01-06 14:51 ` Mickaël Salaün
2025-01-06 22:33 ` Paul Moore
0 siblings, 1 reply; 46+ messages in thread
From: Mickaël Salaün @ 2025-01-06 14:51 UTC (permalink / raw)
To: Paul Moore
Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
Casey Schaufler, Charles Zaffery, Francis Laniel, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Sat, Jan 04, 2025 at 08:23:53PM -0500, Paul Moore wrote:
> On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> >
> > Add audit support for unix_stream_connect, unix_may_send, task_kill, and
> > file_send_sigiotask hooks.
> >
> > Audit event sample:
> >
> > type=LL_DENY [...]: domain=195ba459b blockers=scope_abstract_unix_socket path=00666F6F
>
> Similar to 17/23, I believe the SOCKADDR record should already capture
> the socket address information.
This might not be the case, which is why SELinux and others explicitly
log it I guess.
>
> It would also be nice to see an example record on a signal event.
Yes, I'll add such example.
>
> > Cc: Günther Noack <gnoack@google.com>
> > Cc: Tahera Fahimi <fahimitahera@gmail.com>
> > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > Link: https://lore.kernel.org/r/20241122143353.59367-19-mic@digikod.net
> > ---
> > Changes since v1:
> > - New patch.
> > ---
> > security/landlock/audit.c | 8 ++++++
> > security/landlock/audit.h | 2 ++
> > security/landlock/task.c | 58 ++++++++++++++++++++++++++++++++++++---
> > 3 files changed, 64 insertions(+), 4 deletions(-)
>
> --
> paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 10/23] landlock: Log domain properties and release
2025-01-06 14:51 ` Mickaël Salaün
@ 2025-01-06 21:56 ` Paul Moore
2025-01-07 14:16 ` Mickaël Salaün
0 siblings, 1 reply; 46+ messages in thread
From: Paul Moore @ 2025-01-06 21:56 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
Casey Schaufler, Charles Zaffery, Francis Laniel, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Mon, Jan 6, 2025 at 9:51 AM Mickaël Salaün <mic@digikod.net> wrote:
> On Sat, Jan 04, 2025 at 08:23:51PM -0500, Paul Moore wrote:
> > On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
...
> > > Audit event sample for a first denial:
> > >
> > > type=LL_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
> > > type=LL_DOM_INFO msg=audit(1732186800.349:44): domain=195ba459b creation=1732186800.345 pid=300 uid=0 exe="/root/sandboxer" comm="sandboxer"
> > > type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
> >
> > As mentioned in patch 9/23, I don't want subsystems external to audit
> > to access the audit timestamp information, so the "creation=" field
> > in the audit event would need to be removed. Assuming that the timestamp
> > was used either to reference the original domain creation and/or simply
> > provide some additional information for analysis, all of that information
> > should already be in the audit log, assuming of course that you are
> > logging domain creation (which you should, at least as an option).
>
> As explained in this patch, we don't want to (and cannot realistically)
> log domain creations. That would make the audit support for Landlock
> unusable. Moreover, these information is useless and only add noise
> unless there is a denial, hence this asynchronous approach.
That's fine, just know that it doesn't change my thoughts on exposing
the audit timestamp.
> However,
> users may want to log some syscalls, including landlock_restrict_self(),
> and it would make audit logs more consistent using the same timestamp as
> the Landlock domain creation time. I'm wondering why exposing this
> timestamp to the kernel would be an issue whereas it is already exposed
> to user space.
Currently there are no other users of the audit timestamp besides
audit. Making the audit timestamp available to other subsystems makes
the timestamp less flexible over the long term as it would become, in
a way, part of the API that audit provides to other in-kernel users.
I still have hopes to rework a large chunk of the audit subsystem, and
keeping the interfaces between audit and the other in-kernel
subsystems makes that easier.
> If you're really opposed to it I can create a new unrelated timestamp
> specific to Landlock.
Yes, at this point in time I don't want to support exporting the audit
timestamp outside of audit. My guess is that you probably want to use
some identifier, other than a timestamp, when trying to link Landlock
events (presumably the domain ID would do this?), but I don't pretend
to know the details of Landlock very well right now.
> > Also, is there a good reason why the LL_DOM_INFO information can't be
> > recorded in the LL_DENY (or LL_ACCESS) record? I think that would be
> > preferable.
>
> The goal of the standalone LL_DOM_INFO record type is to limit useless
> log verbosity. Including this information in LL_DENY would have two
> downsides:
> - it would increases the length of *all* LL_DENY messages
Are you ever going to emit a LL_ACCESS/LL_DENY record without a
LL_DOM_INFO record?
> - it would make it more difficult to extend this new mixed messages with
> access-related informations (e.g. file property) and domain-related
> informations (and associate them with either the object or the
> domain).
How? Please elaborate on this.
> > > Audit event sample for logged domains deletion:
> > >
> > > type=LL_DOM_DROP msg=audit(1732186800.393:45): domain=195ba459b
> > >
> > > Cc: Günther Noack <gnoack@google.com>
> > > Cc: Paul Moore <paul@paul-moore.com>
> > > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > > Link: https://lore.kernel.org/r/20241122143353.59367-11-mic@digikod.net
> > > ---
> > > Questions:
> > > - Should we also log the creator's loginuid?
> > > - Should we also log the creator's sessionid?
> >
> > Creation of a Landlock domain can only happen through the Landlock
> > syscalls, yes? If so, that information should already be logged in
> > the associated syscall record (see the "auid=" and "ses=" fields )and
> > we generally try to avoid duplicating information across records in
> > the same audit event.
>
> The specificity of Landlock compared to existing supported systems is
> that we cannot log domain creation for the reason I explain before.
Can you provide a link to that explanation? I'm sure you explained it
well, but I missed it when going over the patchset with a focus on
audit.
If the Landlock domain is created independent from any user/process
action, it likely doesn't make sense to log either the loginuid or
sessionid since the domain creation is happening independently from a
user session.
--
paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 17/23] landlock: Log TCP bind and connect denials
2025-01-06 14:51 ` Mickaël Salaün
@ 2025-01-06 22:29 ` Paul Moore
2025-01-07 14:17 ` Mickaël Salaün
0 siblings, 1 reply; 46+ messages in thread
From: Paul Moore @ 2025-01-06 22:29 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
Casey Schaufler, Charles Zaffery, Francis Laniel, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Mon, Jan 6, 2025 at 9:51 AM Mickaël Salaün <mic@digikod.net> wrote:
> On Sat, Jan 04, 2025 at 08:23:52PM -0500, Paul Moore wrote:
> > On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> > >
> > > Add audit support to socket_bind and socket_connect hooks.
> > >
> > > Audit event sample:
> > >
> > > type=LL_DENY [...]: domain=195ba459b blockers=net_connect_tcp daddr=127.0.0.1 dest=80
> >
> > The destination address and port is already captured in the SOCKADDR
> > record for bind() and connect(), please don't duplicate it here.
>
> This does not show up when a connect or bind is denied. I guess this is
> because move_addr_to_kernel() is called at syscall entry when there is
> no context, whereas a Landlock denial is created after that. For this
> to work, users would have to log a list of syscalls, which would not be
> usable (nor reliably maintainable) for most users.
Quick question, can you share the audit filter configuration you are
using on your dev/test systems (just dump /etc/audit/audit.rules,
unless you are doing it by hand)?
One can make an argument that if syscall auditing is being explicitly
denied, then the user has decided that the syscall related information
is not important to them. I'm somewhat conflicted on that argument,
but I believe the argument is at least valid.
> I guess this might be different with io_uring too.
There are other issues with SOCKADDR and io_uring related to how
io_uring wants to separate the work into different execution contexts.
In general I wouldn't spend too much time worrying about auditing and
io_uring right now, there are some general issues that need to be
resolved in io_uring/audit that are much larger than just Landlock's
audit usage.
--
paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 18/23] landlock: Log scoped denials
2025-01-06 14:51 ` Mickaël Salaün
@ 2025-01-06 22:33 ` Paul Moore
2025-01-07 14:23 ` Mickaël Salaün
0 siblings, 1 reply; 46+ messages in thread
From: Paul Moore @ 2025-01-06 22:33 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
Casey Schaufler, Charles Zaffery, Francis Laniel, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Mon, Jan 6, 2025 at 9:51 AM Mickaël Salaün <mic@digikod.net> wrote:
> On Sat, Jan 04, 2025 at 08:23:53PM -0500, Paul Moore wrote:
> > On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> > >
> > > Add audit support for unix_stream_connect, unix_may_send, task_kill, and
> > > file_send_sigiotask hooks.
> > >
> > > Audit event sample:
> > >
> > > type=LL_DENY [...]: domain=195ba459b blockers=scope_abstract_unix_socket path=00666F6F
> >
> > Similar to 17/23, I believe the SOCKADDR record should already capture
> > the socket address information.
>
> This might not be the case, which is why SELinux and others explicitly
> log it I guess.
I think I may be misunderstanding you, can you point to the section of
SELinux code that you are referring to in your comment?
--
paul-moore.com
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 10/23] landlock: Log domain properties and release
2025-01-06 21:56 ` Paul Moore
@ 2025-01-07 14:16 ` Mickaël Salaün
0 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2025-01-07 14:16 UTC (permalink / raw)
To: Paul Moore
Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
Casey Schaufler, Charles Zaffery, Francis Laniel, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Mon, Jan 06, 2025 at 04:56:50PM -0500, Paul Moore wrote:
> On Mon, Jan 6, 2025 at 9:51 AM Mickaël Salaün <mic@digikod.net> wrote:
> > On Sat, Jan 04, 2025 at 08:23:51PM -0500, Paul Moore wrote:
> > > On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
>
> ...
>
> > > > Audit event sample for a first denial:
> > > >
> > > > type=LL_DENY msg=audit(1732186800.349:44): domain=195ba459b blockers=ptrace opid=1 ocomm="systemd"
> > > > type=LL_DOM_INFO msg=audit(1732186800.349:44): domain=195ba459b creation=1732186800.345 pid=300 uid=0 exe="/root/sandboxer" comm="sandboxer"
> > > > type=SYSCALL msg=audit(1732186800.349:44): arch=c000003e syscall=101 success=no [...] pid=300 auid=0
> > >
> > > As mentioned in patch 9/23, I don't want subsystems external to audit
> > > to access the audit timestamp information, so the "creation=" field
> > > in the audit event would need to be removed. Assuming that the timestamp
> > > was used either to reference the original domain creation and/or simply
> > > provide some additional information for analysis, all of that information
> > > should already be in the audit log, assuming of course that you are
> > > logging domain creation (which you should, at least as an option).
> >
> > As explained in this patch, we don't want to (and cannot realistically)
> > log domain creations. That would make the audit support for Landlock
> > unusable. Moreover, these information is useless and only add noise
> > unless there is a denial, hence this asynchronous approach.
>
> That's fine, just know that it doesn't change my thoughts on exposing
> the audit timestamp.
>
> > However,
> > users may want to log some syscalls, including landlock_restrict_self(),
> > and it would make audit logs more consistent using the same timestamp as
> > the Landlock domain creation time. I'm wondering why exposing this
> > timestamp to the kernel would be an issue whereas it is already exposed
> > to user space.
>
> Currently there are no other users of the audit timestamp besides
> audit. Making the audit timestamp available to other subsystems makes
> the timestamp less flexible over the long term as it would become, in
> a way, part of the API that audit provides to other in-kernel users.
>
> I still have hopes to rework a large chunk of the audit subsystem, and
> keeping the interfaces between audit and the other in-kernel
> subsystems makes that easier.
OK
>
> > If you're really opposed to it I can create a new unrelated timestamp
> > specific to Landlock.
>
> Yes, at this point in time I don't want to support exporting the audit
> timestamp outside of audit. My guess is that you probably want to use
> some identifier, other than a timestamp, when trying to link Landlock
> events (presumably the domain ID would do this?), but I don't pretend
> to know the details of Landlock very well right now.
Correct, Landlock domain IDs are used to tie domain creation, denial,
and destruction/drop events (and their use will be extended to user
space in the future).
>
> > > Also, is there a good reason why the LL_DOM_INFO information can't be
> > > recorded in the LL_DENY (or LL_ACCESS) record? I think that would be
> > > preferable.
> >
> > The goal of the standalone LL_DOM_INFO record type is to limit useless
> > log verbosity. Including this information in LL_DENY would have two
> > downsides:
> > - it would increases the length of *all* LL_DENY messages
>
> Are you ever going to emit a LL_ACCESS/LL_DENY record without a
> LL_DOM_INFO record?
Yes, only the first LL_DENY (for a domain) emits an LL_DOM_INFO (for
this same domain), which is why this design is interesting: creation of
domains can happen at a high frequency (e.g. script executing a
sandboxed program in a loop, or just build a kernel with sandbox
compilers), and logging every domain creation would make 99% of these
events useless. See log_status's LANDLOCK_LOG_RECORDED in log_node() in
this patch.
>
> > - it would make it more difficult to extend this new mixed messages with
> > access-related informations (e.g. file property) and domain-related
> > informations (and associate them with either the object or the
> > domain).
>
> How? Please elaborate on this.
I mean that appending intertwined (i.e. some might be related to define
a domain whereas others might be related to define an object) and
optional (e.g. a file object and a socket object are not defined the
same way) new fields to one message type makes the message less
predictable and more difficult to parse.
>
> > > > Audit event sample for logged domains deletion:
> > > >
> > > > type=LL_DOM_DROP msg=audit(1732186800.393:45): domain=195ba459b
> > > >
> > > > Cc: Günther Noack <gnoack@google.com>
> > > > Cc: Paul Moore <paul@paul-moore.com>
> > > > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > > > Link: https://lore.kernel.org/r/20241122143353.59367-11-mic@digikod.net
> > > > ---
> > > > Questions:
> > > > - Should we also log the creator's loginuid?
> > > > - Should we also log the creator's sessionid?
> > >
> > > Creation of a Landlock domain can only happen through the Landlock
> > > syscalls, yes? If so, that information should already be logged in
> > > the associated syscall record (see the "auid=" and "ses=" fields )and
> > > we generally try to avoid duplicating information across records in
> > > the same audit event.
> >
> > The specificity of Landlock compared to existing supported systems is
> > that we cannot log domain creation for the reason I explain before.
>
> Can you provide a link to that explanation? I'm sure you explained it
> well, but I missed it when going over the patchset with a focus on
> audit.
That wasn't clear enough, I'll include the previous description in the
next series, but the basic design idea is defined in the cover letter:
https://lore.kernel.org/all/20241122143353.59367-1-mic@digikod.net/
>
> If the Landlock domain is created independent from any user/process
> action, it likely doesn't make sense to log either the loginuid or
> sessionid since the domain creation is happening independently from a
> user session.
Landlock domain creations are a process action. What we want to log are
the denials and a minimal context (e.g. which task created the related
domain). I was wondering if we should (right now) include loginuid or
sessionid in this (asynchronous) context.
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 17/23] landlock: Log TCP bind and connect denials
2025-01-06 22:29 ` Paul Moore
@ 2025-01-07 14:17 ` Mickaël Salaün
0 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2025-01-07 14:17 UTC (permalink / raw)
To: Paul Moore
Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
Casey Schaufler, Charles Zaffery, Francis Laniel, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Mon, Jan 06, 2025 at 05:29:51PM -0500, Paul Moore wrote:
> On Mon, Jan 6, 2025 at 9:51 AM Mickaël Salaün <mic@digikod.net> wrote:
> > On Sat, Jan 04, 2025 at 08:23:52PM -0500, Paul Moore wrote:
> > > On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> > > >
> > > > Add audit support to socket_bind and socket_connect hooks.
> > > >
> > > > Audit event sample:
> > > >
> > > > type=LL_DENY [...]: domain=195ba459b blockers=net_connect_tcp daddr=127.0.0.1 dest=80
> > >
> > > The destination address and port is already captured in the SOCKADDR
> > > record for bind() and connect(), please don't duplicate it here.
> >
> > This does not show up when a connect or bind is denied. I guess this is
> > because move_addr_to_kernel() is called at syscall entry when there is
> > no context, whereas a Landlock denial is created after that. For this
> > to work, users would have to log a list of syscalls, which would not be
> > usable (nor reliably maintainable) for most users.
>
> Quick question, can you share the audit filter configuration you are
> using on your dev/test systems (just dump /etc/audit/audit.rules,
> unless you are doing it by hand)?
This file only contains a comment. auditctl -l says that there is no
rules.
>
> One can make an argument that if syscall auditing is being explicitly
> denied, then the user has decided that the syscall related information
> is not important to them. I'm somewhat conflicted on that argument,
> but I believe the argument is at least valid.
I did not disable syscall auditing, I get the type=SYSCALL record for
every Landlock deny event, but there is no SOCKADDR one. For instance:
type=UNKNOWN[1423] msg=audit(1736258533.147:45): domain=190464446 blockers=net.connect_tcp daddr=127.0.0.1 dest=80
type=UNKNOWN[1424] msg=audit(1736258533.147:45): domain=190464446 creation=1736258533.135 pid=359 uid=0 exe="/root/sandboxer" comm="sandboxer"UID="root"
type=SYSCALL msg=audit(1736258533.147:45): arch=c000003e syscall=42 success=no exit=-13 a0=5 a1=5647c6a26b98 a2=10 a3=7ffd2f5f6acc items=0 ppid=356 pid=359 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4 comm="curl" exe="/usr/bin/curl" key=(null)ARCH=x86_64 SYSCALL=connect AUID="root" UID="root" GID="root" EUID="root" SUID="root" FSUID="root" EGID="root" SGID="root" FSGID="root"
type=PROCTITLE msg=audit(1736258533.147:45): proctitle=6375726C00687474703A2F2F3132372E31
type=UNKNOWN[1425] msg=audit(1736258533.199:46): domain=190464446
>
> > I guess this might be different with io_uring too.
>
> There are other issues with SOCKADDR and io_uring related to how
> io_uring wants to separate the work into different execution contexts.
> In general I wouldn't spend too much time worrying about auditing and
> io_uring right now, there are some general issues that need to be
> resolved in io_uring/audit that are much larger than just Landlock's
> audit usage.
OK. Anyway, my understanding is that SOCKADDR is just a way to enrich
the syscall record to ease debugging or tracing. In the case of an
access control, we want to identify an object/subject, and each LSM may
have a different way to identify such an object, and this description
should be enough to identify the relevant part of the object.
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v3 18/23] landlock: Log scoped denials
2025-01-06 22:33 ` Paul Moore
@ 2025-01-07 14:23 ` Mickaël Salaün
0 siblings, 0 replies; 46+ messages in thread
From: Mickaël Salaün @ 2025-01-07 14:23 UTC (permalink / raw)
To: Paul Moore
Cc: Eric Paris, Günther Noack, Serge E . Hallyn, Ben Scarlato,
Casey Schaufler, Charles Zaffery, Francis Laniel, James Morris,
Jann Horn, Jeff Xu, Jorge Lucangeli Obes, Kees Cook,
Konstantin Meskhidze, Matt Bobrowski, Mikhail Ivanov, Phil Sutter,
Praveen K Paladugu, Robert Salvet, Shervin Oloumi, Song Liu,
Tahera Fahimi, audit, linux-kernel, linux-security-module
On Mon, Jan 06, 2025 at 05:33:07PM -0500, Paul Moore wrote:
> On Mon, Jan 6, 2025 at 9:51 AM Mickaël Salaün <mic@digikod.net> wrote:
> > On Sat, Jan 04, 2025 at 08:23:53PM -0500, Paul Moore wrote:
> > > On Nov 22, 2024 =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= <mic@digikod.net> wrote:
> > > >
> > > > Add audit support for unix_stream_connect, unix_may_send, task_kill, and
> > > > file_send_sigiotask hooks.
> > > >
> > > > Audit event sample:
> > > >
> > > > type=LL_DENY [...]: domain=195ba459b blockers=scope_abstract_unix_socket path=00666F6F
> > >
> > > Similar to 17/23, I believe the SOCKADDR record should already capture
> > > the socket address information.
> >
> > This might not be the case, which is why SELinux and others explicitly
> > log it I guess.
>
> I think I may be misunderstanding you, can you point to the section of
> SELinux code that you are referring to in your comment?
I'm refering to struct lsm_network_audit, and the related information
ending in the logs with LSM_AUDIT_DATA_NET. Landlock follows the
current implementations, hence the generalization brought by the second
patch with audit_log_lsm_data().
^ permalink raw reply [flat|nested] 46+ messages in thread
end of thread, other threads:[~2025-01-07 14:23 UTC | newest]
Thread overview: 46+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-11-22 14:33 [PATCH v3 00/23] Landlock audit support Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 01/23] lsm: Only build lsm_audit.c if CONFIG_SECURITY and CONFIG_AUDIT are set Mickaël Salaün
2025-01-04 16:47 ` [PATCH v3 1/23] " Paul Moore
2024-11-22 14:33 ` [PATCH v3 02/23] lsm: Add audit_log_lsm_data() helper Mickaël Salaün
2025-01-05 1:23 ` [PATCH v3 2/23] " Paul Moore
2024-11-22 14:33 ` [PATCH v3 03/23] landlock: Factor out check_access_path() Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 04/23] landlock: Add unique ID generator Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 05/23] landlock: Move access types Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 06/23] landlock: Simplify initially denied access rights Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 07/23] landlock: Move domain hierarchy management Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 08/23] landlock: Log ptrace denials Mickaël Salaün
2024-12-20 14:36 ` Francis Laniel
2024-12-24 14:48 ` Mickaël Salaün
2025-01-05 1:23 ` [PATCH v3 8/23] " Paul Moore
2025-01-06 14:45 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 09/23] audit: Add a new audit_get_ctime() helper Mickaël Salaün
2025-01-05 1:23 ` [PATCH v3 9/23] " Paul Moore
2024-11-22 14:33 ` [PATCH v3 10/23] landlock: Log domain properties and release Mickaël Salaün
2025-01-05 1:23 ` Paul Moore
2025-01-06 14:51 ` Mickaël Salaün
2025-01-06 21:56 ` Paul Moore
2025-01-07 14:16 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 11/23] landlock: Log mount-related denials Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 12/23] landlock: Align partial refer access checks with final ones Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 13/23] selftests/landlock: Add test to check partial access in a mount tree Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 14/23] landlock: Optimize file path walks and prepare for audit support Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 15/23] landlock: Log file-related denials Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 16/23] landlock: Log truncate and ioctl denials Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 17/23] landlock: Log TCP bind and connect denials Mickaël Salaün
2025-01-05 1:23 ` Paul Moore
2025-01-06 14:51 ` Mickaël Salaün
2025-01-06 22:29 ` Paul Moore
2025-01-07 14:17 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 18/23] landlock: Log scoped denials Mickaël Salaün
2025-01-05 1:23 ` Paul Moore
2025-01-06 14:51 ` Mickaël Salaün
2025-01-06 22:33 ` Paul Moore
2025-01-07 14:23 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 19/23] landlock: Control log events with LANDLOCK_RESTRICT_SELF_LOGLESS Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 20/23] samples/landlock: Do not log denials from the sandboxer by default Mickaël Salaün
2024-12-20 14:36 ` Francis Laniel
2024-12-24 14:48 ` Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 21/23] selftests/landlock: Extend tests for landlock_restrict_self()'s flags Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 22/23] selftests/landlock: Add tests for audit Mickaël Salaün
2024-11-22 14:33 ` [PATCH v3 23/23] selftests/landlock: Add audit tests for ptrace Mickaël Salaün
2024-12-20 14:36 ` [PATCH v3 00/23] Landlock audit support Francis Laniel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).