[RFC PATCH v1 1/3] landlock: Add landlock_read_domain_id()
Mickaël Salaün
mic at digikod.net
Fri Jan 31 16:34:45 UTC 2025
This public landlock_read_domain_id() helper will be used in the next
commit by the PIDFD_GET_INFO IOCTL to read a task's domain ID.
A task is only allowed to get information about its nested domains.
Being able to read tasks' domain IDs is useful for telemetry, debugging,
and tests. It enables users:
- to know if a task is well sandboxed;
- to know which tasks are part of the same sandbox;
- to map Landlock audit logs to running processes.
Future changes will leverage this feature to improve Landlock
observability.
Landlock domain IDs are now always generated at domain creation time.
Cc: Christian Brauner <brauner at kernel.org>
Cc: Günther Noack <gnoack at google.com>
Cc: Luca Boccassi <luca.boccassi at gmail.com>
Cc: Praveen K Paladugu <prapal at linux.microsoft.com>
Signed-off-by: Mickaël Salaün <mic at digikod.net>
Link: https://lore.kernel.org/r/20250131163447.1140564-2-mic@digikod.net
---
include/linux/landlock.h | 26 +++++++++++++
security/landlock/Makefile | 12 ++++--
security/landlock/domain.c | 2 -
security/landlock/domain.h | 8 ++--
security/landlock/ruleset.c | 2 +
security/landlock/syscalls.c | 75 ++++++++++++++++++++++++++++++++++++
6 files changed, 116 insertions(+), 9 deletions(-)
create mode 100644 include/linux/landlock.h
diff --git a/include/linux/landlock.h b/include/linux/landlock.h
new file mode 100644
index 000000000000..a091deee9ad7
--- /dev/null
+++ b/include/linux/landlock.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock public helpers
+ *
+ * Copyright © 2025 Microsoft Corporation
+ */
+
+#include <linux/cred.h>
+#include <linux/sched.h>
+
+#ifdef CONFIG_SECURITY_LANDLOCK
+
+int landlock_read_domain_id(const struct cred *const cred,
+ struct task_struct *const task, const bool last,
+ u64 *const id);
+
+#else /* CONFIG_SECURITY_LANDLOCK */
+
+static inline int landlock_read_domain_id(const struct cred *const cred,
+ struct task_struct *const task,
+ const bool last, u64 *const id)
+{
+ return -EOPNOTSUPP;
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK */
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index 3160c2bdac1d..5db653a1717e 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,11 +1,17 @@
obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
-landlock-y := setup.o syscalls.o object.o ruleset.o \
- cred.o task.o fs.o
+landlock-y := \
+ setup.o \
+ syscalls.o \
+ object.o \
+ ruleset.o \
+ cred.o \
+ task.o \
+ fs.o \
+ id.o
landlock-$(CONFIG_INET) += net.o
landlock-$(CONFIG_AUDIT) += \
- id.o \
audit.o \
domain.o
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 49ccb0f72e53..782cc4ef73e0 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -21,7 +21,6 @@
#include "common.h"
#include "domain.h"
#include "fs.h"
-#include "id.h"
#ifdef CONFIG_AUDIT
@@ -125,7 +124,6 @@ int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy)
return PTR_ERR(details);
hierarchy->details = details;
- hierarchy->id = landlock_get_id_range(1);
hierarchy->log_status = LANDLOCK_LOG_PENDING;
hierarchy->quiet_subdomains = false;
hierarchy->log_cross_exec = false;
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 06b213aa7579..3f678caddaff 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -78,6 +78,10 @@ struct landlock_hierarchy {
* Landlock domain.
*/
struct landlock_hierarchy *parent;
+ /**
+ * @id: Landlock domain ID, sets once at domain creation time.
+ */
+ u64 id;
/**
* @usage: Number of potential children domains plus their parent
* domain.
@@ -96,10 +100,6 @@ struct landlock_hierarchy {
* Masked (i.e. never logged) denials are still counted.
*/
atomic64_t num_denials;
- /**
- * @id: Landlock domain ID, sets once at domain creation time.
- */
- u64 id;
/**
* @details: Information about the related domain.
*/
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index de41e8bde2e4..e42933361c32 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -25,6 +25,7 @@
#include "access.h"
#include "audit.h"
#include "domain.h"
+#include "id.h"
#include "limits.h"
#include "object.h"
#include "ruleset.h"
@@ -576,6 +577,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
if (err)
return ERR_PTR(err);
+ new_dom->hierarchy->id = landlock_get_id_range(1);
return no_free_ptr(new_dom);
}
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 5709a53c4a09..060ac9d52527 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -16,6 +16,7 @@
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fs.h>
+#include <linux/landlock.h>
#include <linux/limits.h>
#include <linux/mount.h>
#include <linux/path.h>
@@ -538,3 +539,77 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
return commit_creds(new_cred);
}
+
+/**
+ * landlock_read_domain_id - Read the domain ID of a task
+ *
+ * @cred: The credential used to check permission.
+ * @task: Task to read the domain ID from.
+ * @last: Either get the ID of the last or the closest nested domain relative
+ * to @cred.
+ * @id: Returned domain ID on success.
+ *
+ * @cred is only allowed to get information about its nested domains.
+ *
+ * Any error returned by this function just translates to non-information in a
+ * PIDFD_GET_INFO's PIDFD_INFO_LANDLOCK_LAST_DOMAIN or
+ * PIDFD_INFO_LANDLOCK_FIRST_DOMAIN request.
+ *
+ * Returns: 0 on success, or -errno on error.
+ */
+int landlock_read_domain_id(const struct cred *const cred,
+ struct task_struct *const task, const bool last,
+ u64 *const id)
+{
+ const struct landlock_ruleset *cred_dom, *task_dom;
+ const struct landlock_hierarchy *walker, *prev;
+ bool is_allowed;
+
+ if (!is_initialized())
+ return -EOPNOTSUPP;
+
+ cred_dom = landlock_cred(cred)->domain;
+
+ guard(rcu)();
+ task_dom = landlock_get_task_domain(task);
+
+ /*
+ * Domain introspection is denied to not leak information about the
+ * current restrictions. However, a task can open a pidfd to itself,
+ * sandbox itself, and then read its domain ID from the previously
+ * opened pidfd. This is OK because the task already knows its
+ * sandbox, which might not be the case for its children.
+ *
+ * For consistency wrt sandboxed and non-sandboxed tasks, any task
+ * should get the same result when directly reading its own domain. A
+ * non-sandboxed task requesting to read another non-sandboxed task
+ * should then also be denied (i.e. cred_dom == task_dom == NULL).
+ */
+ if (cred_dom == task_dom)
+ return -EPERM;
+
+ if (!task_dom)
+ return -EPERM;
+
+ /* Reading properties of @cred's nested domains is allowed. */
+ is_allowed = !cred_dom;
+ prev = task_dom->hierarchy;
+ for (walker = prev; walker; walker = walker->parent) {
+ if (cred_dom && walker == cred_dom->hierarchy) {
+ is_allowed = true;
+ break;
+ }
+
+ prev = walker;
+ }
+
+ if (!is_allowed)
+ return -EPERM;
+
+ if (last)
+ *id = task_dom->hierarchy->id;
+ else
+ *id = prev->id;
+
+ return 0;
+}
--
2.48.1
More information about the Linux-security-module-archive
mailing list