| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/01 11:07 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"BPF_LSM",
"SECURITY_LANDLOCK",
"BPF_SYSCALL",
"SECURITY",
"BPF_EVENTS",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_landlock_get_ruleset_from_fd",
"bpf_landlock_put_ruleset",
"bpf_landlock_restrict_binprm",
"bpf_landlock_put_ruleset_dtor",
"security_policy_kptr_from_fd",
"security_policy_kptr_put",
"security_bprm_enforce_policy_kptr",
"landlock_prepare_restriction",
"landlock_apply_restriction"
],
"Reasoning": "The patch introduces BPF kfuncs for Landlock LSM, allowing BPF programs to enforce Landlock rulesets on executions. This is a functional change to the kernel that introduces new reachable code paths in the BPF and Landlock subsystems, which should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/01 11:07 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 4eb6da94ebc515a6ed42f892f192a0bc1efde579\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Aug 1 11:07:27 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst\nindex c5186526e76f6..01197c7580ec0 100644\n--- a/Documentation/security/landlock.rst\n+++ b/Documentation/security/landlock.rst\n@@ -129,6 +129,31 @@ The reasoning is:\n restrictions, because access within the same scope is already\n allowed based on ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``.\n \n+BPF kfuncs\n+==========\n+\n+BPF programs can apply a userspace-created Landlock ruleset to an\n+execution. A syscall program (``BPF_PROG_TYPE_SYSCALL``), running in\n+the context of the process that set the ruleset up, acquires the\n+ruleset from its file descriptor and typically hands it over through\n+a map kptr field; a sleepable LSM BPF program attached to the\n+``bprm_creds_for_exec`` or ``bprm_creds_from_file`` hooks then\n+enforces it on an execution.\n+\n+This can be used to inspect the runtime context of a pending execution,\n+and enforce a Landlock policy through BPF.\n+\n+The restriction is staged in the Landlock blob of the\n+credentials prepared for the execution and committed past the exec\n+point of no return, so a failed execution leaves the calling task\n+untouched. The ``landlock_restrict_self(2)`` flags apply, with the\n+exception of ``LANDLOCK_RESTRICT_SELF_TSYNC``.\n+\n+.. kernel-doc:: kernel/bpf/bpf_lsm.c\n+ :identifiers: bpf_landlock_get_ruleset_from_fd\n+ bpf_landlock_put_ruleset\n+ bpf_landlock_restrict_binprm\n+\n Tests\n =====\n \ndiff --git a/Documentation/security/lsm-development.rst b/Documentation/security/lsm-development.rst\nindex 5895e529da7ff..fc3af206e7950 100644\n--- a/Documentation/security/lsm-development.rst\n+++ b/Documentation/security/lsm-development.rst\n@@ -15,3 +15,30 @@ see ``security/security.c`` and associated structures:\n \n .. kernel-doc:: security/security.c\n :export:\n+\n+LSM policy kptr hooks and BPF kfuncs\n+====================================\n+\n+The LSM framework implements an interface for individual LSMs to\n+expose their configuration through BPF kfuncs and kptrs. An LSM\n+may not export any kfunc or other BPF interface directly.\n+\n+An LSM wishing to expose a BPF kfunc must reuse an existing security\n+hook or implement a new sufficiently generic LSM hook for the desired\n+interface. The hooks are then called from kfunc definitions in\n+``kernel/bpf``. This allows LSM hooks to remain sufficiently generic\n+while allowing BPF programs to take advantage of the strong typing\n+and runtime checking offered by the BPF verifier.\n+\n+The LSM providing an operation implements the operation's hook with\n+``LSM_HOOK_INIT()`` like any other hook. A BPF program calling an\n+LSM kfunc therefore reaches the LSM the same way every other\n+kernel caller does: through an LSM hook. The hooks backing kfuncs\n+follow the usual rules for new LSM hooks: their contract must be\n+LSM agnostic so that other LSMs could provide a meaningful\n+implementation of the same operation.\n+\n+Whether the LSM providing an operation is built in and active is a\n+runtime property: the kfuncs are always registered when\n+``CONFIG_BPF_LSM`` is enabled. BPF program loading is thus\n+independent of the boot-time LSM configuration.\ndiff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h\nindex 65c9609ec2077..a70edbd7b761e 100644\n--- a/include/linux/lsm_hook_defs.h\n+++ b/include/linux/lsm_hook_defs.h\n@@ -452,6 +452,11 @@ LSM_HOOK(int, 0, bpf_token_create, struct bpf_token *token, union bpf_attr *attr\n LSM_HOOK(void, LSM_RET_VOID, bpf_token_free, struct bpf_token *token)\n LSM_HOOK(int, 0, bpf_token_cmd, const struct bpf_token *token, enum bpf_cmd cmd)\n LSM_HOOK(int, 0, bpf_token_capable, const struct bpf_token *token, int cap)\n+LSM_HOOK(int, -EOPNOTSUPP, policy_kptr_from_fd, int fd,\n+\t union lsm_policy_kptr *policy)\n+LSM_HOOK(void, LSM_RET_VOID, policy_kptr_put, union lsm_policy_kptr *policy)\n+LSM_HOOK(int, -EOPNOTSUPP, bprm_enforce_policy_kptr, struct linux_binprm *bprm,\n+\t union lsm_policy_kptr *policy, u32 flags)\n #endif /* CONFIG_BPF_SYSCALL */\n \n LSM_HOOK(int, 0, locked_down, enum lockdown_reason what)\ndiff --git a/include/linux/security.h b/include/linux/security.h\nindex 153e9043058f8..40dfa96b6a719 100644\n--- a/include/linux/security.h\n+++ b/include/linux/security.h\n@@ -168,6 +168,23 @@ struct lsm_prop {\n \tstruct lsm_prop_bpf bpf;\n };\n \n+struct bpf_landlock_ruleset;\n+\n+struct lsm_policy_landlock {\n+\tstruct bpf_landlock_ruleset *ruleset;\n+};\n+\n+/*\n+ * A reference to an LSM policy object, tagged by the LSM_ID_* value\n+ * passed alongside: only the matching LSM's member is valid. The\n+ * members are not guarded by the LSMs' CONFIG options: the callers\n+ * are built independently of any individual LSM and a call for a\n+ * missing LSM must fail at runtime, not at build time.\n+ */\n+union lsm_policy_kptr {\n+\tstruct lsm_policy_landlock landlock;\n+};\n+\n extern const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1];\n \n /* These functions are in security/commoncap.c */\n@@ -2312,6 +2329,13 @@ extern int security_bpf_token_create(struct bpf_token *token, union bpf_attr *at\n extern void security_bpf_token_free(struct bpf_token *token);\n extern int security_bpf_token_cmd(const struct bpf_token *token, enum bpf_cmd cmd);\n extern int security_bpf_token_capable(const struct bpf_token *token, int cap);\n+extern int security_policy_kptr_from_fd(u64 lsmid, int fd,\n+\t\t\t\t\tunion lsm_policy_kptr *policy);\n+extern void security_policy_kptr_put(u64 lsmid, union lsm_policy_kptr *policy);\n+extern int security_bprm_enforce_policy_kptr(u64 lsmid,\n+\t\t\t\t\t struct linux_binprm *bprm,\n+\t\t\t\t\t union lsm_policy_kptr *policy,\n+\t\t\t\t\t u32 flags);\n #else\n static inline int security_bpf(int cmd, union bpf_attr *attr,\n \t\t\t unsigned int size, bool kernel)\n@@ -2365,6 +2389,25 @@ static inline int security_bpf_token_capable(const struct bpf_token *token, int\n {\n \treturn 0;\n }\n+\n+static inline int security_policy_kptr_from_fd(u64 lsmid, int fd,\n+\t\t\t\t\t union lsm_policy_kptr *policy)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static inline void security_policy_kptr_put(u64 lsmid,\n+\t\t\t\t\t union lsm_policy_kptr *policy)\n+{\n+}\n+\n+static inline int security_bprm_enforce_policy_kptr(u64 lsmid,\n+\t\t\t\t\t\t struct linux_binprm *bprm,\n+\t\t\t\t\t\t union lsm_policy_kptr *policy,\n+\t\t\t\t\t\t u32 flags)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n #endif /* CONFIG_SECURITY */\n #endif /* CONFIG_BPF_SYSCALL */\n \ndiff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c\nindex 3983b4ce73c81..67aa902f12570 100644\n--- a/kernel/bpf/bpf_lsm.c\n+++ b/kernel/bpf/bpf_lsm.c\n@@ -14,8 +14,10 @@\n #include \u003cnet/bpf_sk_storage.h\u003e\n #include \u003clinux/bpf_local_storage.h\u003e\n #include \u003clinux/btf_ids.h\u003e\n+#include \u003clinux/cfi.h\u003e\n #include \u003clinux/ima.h\u003e\n #include \u003clinux/bpf-cgroup.h\u003e\n+#include \u003cuapi/linux/lsm.h\u003e\n \n /* For every LSM hook that allows attachment of BPF programs, declare a nop\n * function where a BPF program can be attached. Notably, we qualify each with\n@@ -56,6 +58,9 @@ BTF_ID(func, bpf_lsm_xfrm_decode_session)\n #endif\n BTF_ID(func, bpf_lsm_ismaclabel)\n BTF_ID(func, bpf_lsm_file_alloc_security)\n+BTF_ID(func, bpf_lsm_policy_kptr_from_fd)\n+BTF_ID(func, bpf_lsm_policy_kptr_put)\n+BTF_ID(func, bpf_lsm_bprm_enforce_policy_kptr)\n BTF_SET_END(bpf_lsm_disabled_hooks)\n \n /* List of LSM hooks that should operate on 'current' cgroup regardless\n@@ -470,3 +475,200 @@ int bpf_lsm_get_retval_range(const struct bpf_prog *prog,\n \t}\n \treturn 0;\n }\n+\n+/* LSM policy kfuncs */\n+\n+/*\n+ * Opaque handle for a Landlock ruleset. Only\n+ * bpf_landlock_get_ruleset_from_fd() produces one, and only Landlock\n+ * resolves it.\n+ */\n+struct bpf_landlock_ruleset {};\n+\n+/*\n+ * The sleepable LSM hooks bpf_landlock_restrict_binprm() and\n+ * bpf_landlock_put_ruleset() may be called from.\n+ */\n+BTF_SET_START(bpf_landlock_kfunc_hooks)\n+BTF_ID(func, bpf_lsm_bprm_creds_for_exec)\n+BTF_ID(func, bpf_lsm_bprm_creds_from_file)\n+BTF_SET_END(bpf_landlock_kfunc_hooks)\n+\n+__bpf_kfunc_start_defs();\n+\n+/**\n+ * bpf_landlock_get_ruleset_from_fd - Get a Landlock ruleset from a fd\n+ * @fd: file descriptor of a Landlock ruleset, resolved in the file\n+ * descriptor table of the task running the program\n+ *\n+ * Acquire a reference on the Landlock ruleset referred to by @fd, as\n+ * created by landlock_create_ruleset(2) and populated with\n+ * landlock_add_rule(2). Only syscall programs may call this kfunc:\n+ * they run in the context of the task invoking them, where the\n+ * ruleset fd is meaningful. The acquired ruleset can be handed to an\n+ * enforcement program through a map kptr field. The reference must\n+ * be released with bpf_landlock_put_ruleset().\n+ *\n+ * Return: A referenced ruleset handle, or NULL if @fd is not a\n+ * readable Landlock ruleset fd or the Landlock LSM is not enabled.\n+ */\n+__bpf_kfunc struct bpf_landlock_ruleset *\n+bpf_landlock_get_ruleset_from_fd(int fd)\n+{\n+\tunion lsm_policy_kptr policy;\n+\n+\tif (security_policy_kptr_from_fd(LSM_ID_LANDLOCK, fd, \u0026policy))\n+\t\treturn NULL;\n+\treturn policy.landlock.ruleset;\n+}\n+\n+/**\n+ * bpf_landlock_put_ruleset - Put a Landlock ruleset\n+ * @ruleset: Landlock ruleset to put\n+ *\n+ * Release a reference acquired with bpf_landlock_get_ruleset_from_fd().\n+ */\n+__bpf_kfunc void bpf_landlock_put_ruleset(struct bpf_landlock_ruleset *ruleset)\n+{\n+\tunion lsm_policy_kptr policy = { .landlock.ruleset = ruleset };\n+\n+\tsecurity_policy_kptr_put(LSM_ID_LANDLOCK, \u0026policy);\n+}\n+\n+/**\n+ * bpf_landlock_restrict_binprm - Enforce a Landlock ruleset on exec\n+ * credentials\n+ * @bprm: execution context providing the prepared credentials to\n+ * restrict\n+ * @ruleset: Landlock ruleset to enforce\n+ * @flags: landlock_restrict_self(2) flags, except\n+ * %LANDLOCK_RESTRICT_SELF_TSYNC\n+ *\n+ * Restrict the credentials prepared in @bprm with @ruleset, so that\n+ * the executed task starts confined by it, following the same domain\n+ * composition rules as landlock_restrict_self(2). The restriction is\n+ * staged and only committed at the execution's point of no return: an\n+ * execution that fails before that point is unaffected, and a later\n+ * call on the same execution replaces a previously staged\n+ * restriction. @ruleset is only borrowed: the caller keeps its\n+ * reference.\n+ *\n+ * The landlock_restrict_self(2) flags apply with their usual\n+ * semantics. Only %LANDLOCK_RESTRICT_SELF_TSYNC is rejected, as it\n+ * targets the calling threads rather than the execution.\n+ *\n+ * Return: 0 on success, -EOPNOTSUPP if the Landlock LSM is not\n+ * enabled, -EINVAL if @flags contains an unsupported flag, other\n+ * negative values on failure as for landlock_restrict_self(2).\n+ */\n+__bpf_kfunc int bpf_landlock_restrict_binprm(struct linux_binprm *bprm,\n+\t\t\t\t\t struct bpf_landlock_ruleset *ruleset,\n+\t\t\t\t\t u32 flags)\n+{\n+\tunion lsm_policy_kptr policy = { .landlock.ruleset = ruleset };\n+\n+\treturn security_bprm_enforce_policy_kptr(LSM_ID_LANDLOCK, bprm,\n+\t\t\t\t\t\t \u0026policy, flags);\n+}\n+\n+/* Destructor for referenced bpf_landlock_ruleset kptrs. */\n+__bpf_kfunc void bpf_landlock_put_ruleset_dtor(void *ruleset)\n+{\n+\tunion lsm_policy_kptr policy = { .landlock.ruleset = ruleset };\n+\n+\tsecurity_policy_kptr_put(LSM_ID_LANDLOCK, \u0026policy);\n+}\n+CFI_NOSEAL(bpf_landlock_put_ruleset_dtor);\n+\n+__bpf_kfunc_end_defs();\n+\n+BTF_KFUNCS_START(bpf_landlock_kfunc_ids)\n+BTF_ID_FLAGS(func, bpf_landlock_get_ruleset_from_fd,\n+\t KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)\n+BTF_ID_FLAGS(func, bpf_landlock_put_ruleset, KF_RELEASE | KF_SLEEPABLE)\n+BTF_ID_FLAGS(func, bpf_landlock_restrict_binprm, KF_SLEEPABLE)\n+BTF_KFUNCS_END(bpf_landlock_kfunc_ids)\n+\n+BTF_ID_LIST(bpf_landlock_dtor_ids)\n+BTF_ID(struct, bpf_landlock_ruleset)\n+BTF_ID(func, bpf_landlock_put_ruleset_dtor)\n+\n+BTF_ID_LIST_SINGLE(bpf_landlock_get_ruleset_ids, func,\n+\t\t bpf_landlock_get_ruleset_from_fd)\n+BTF_ID_LIST_SINGLE(bpf_landlock_restrict_binprm_ids, func,\n+\t\t bpf_landlock_restrict_binprm)\n+\n+/*\n+ * BPF_PROG_TYPE_LSM and BPF_PROG_TYPE_SYSCALL share their kfunc\n+ * lookup buckets with other program types, so restricting the LSM\n+ * policy kfuncs requires a filter. A ruleset fd is only meaningful\n+ * in the fd table of the task that set the ruleset up, so\n+ * bpf_landlock_get_ruleset_from_fd() is exclusive to syscall\n+ * programs, which run in that task's context; an LSM program runs in\n+ * the context of the task it mediates. Enforcement is exclusive to\n+ * the sleepable bprm LSM hooks the policy operation is specified\n+ * for, and the release kfunc is allowed wherever a reference can be\n+ * held.\n+ */\n+static int bpf_landlock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)\n+{\n+\tif (!btf_id_set8_contains(\u0026bpf_landlock_kfunc_ids, kfunc_id))\n+\t\treturn 0;\n+\n+\tswitch (prog-\u003etype) {\n+\tcase BPF_PROG_TYPE_SYSCALL:\n+\t\tif (kfunc_id == bpf_landlock_restrict_binprm_ids[0])\n+\t\t\treturn -EACCES;\n+\t\treturn 0;\n+\tcase BPF_PROG_TYPE_LSM:\n+\t\tif (kfunc_id == bpf_landlock_get_ruleset_ids[0])\n+\t\t\treturn -EACCES;\n+\n+\t\t/*\n+\t\t * BPF_LSM_CGROUP programs run under classic RCU and\n+\t\t * cannot sleep.\n+\t\t */\n+\t\tif (prog-\u003eexpected_attach_type == BPF_LSM_CGROUP)\n+\t\t\treturn -EACCES;\n+\n+\t\tif (!btf_id_set_contains(\u0026bpf_landlock_kfunc_hooks,\n+\t\t\t\t\t prog-\u003eaux-\u003eattach_btf_id))\n+\t\t\treturn -EACCES;\n+\n+\t\treturn 0;\n+\tdefault:\n+\t\treturn -EACCES;\n+\t}\n+}\n+\n+static const struct btf_kfunc_id_set bpf_landlock_kfunc_set = {\n+\t.owner = THIS_MODULE,\n+\t.set = \u0026bpf_landlock_kfunc_ids,\n+\t.filter = bpf_landlock_kfunc_filter,\n+};\n+\n+static int __init bpf_lsm_policy_kfunc_init(void)\n+{\n+\tconst struct btf_id_dtor_kfunc bpf_landlock_dtors[] = {\n+\t\t{\n+\t\t\t.btf_id = bpf_landlock_dtor_ids[0],\n+\t\t\t.kfunc_btf_id = bpf_landlock_dtor_ids[1],\n+\t\t},\n+\t};\n+\tint ret;\n+\n+\tret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM,\n+\t\t\t\t\t\u0026bpf_landlock_kfunc_set);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,\n+\t\t\t\t\t\u0026bpf_landlock_kfunc_set);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn register_btf_id_dtor_kfuncs(bpf_landlock_dtors,\n+\t\t\t\t\t ARRAY_SIZE(bpf_landlock_dtors),\n+\t\t\t\t\t THIS_MODULE);\n+}\n+late_initcall(bpf_lsm_policy_kfunc_init);\ndiff --git a/security/landlock/Makefile b/security/landlock/Makefile\nindex ffa7646d99f3e..9ba28c06b5ac2 100644\n--- a/security/landlock/Makefile\n+++ b/security/landlock/Makefile\n@@ -16,3 +16,5 @@ landlock-$(CONFIG_AUDIT) += \\\n \tid.o \\\n \taudit.o \\\n \tdomain.o\n+\n+landlock-$(CONFIG_BPF_LSM) += bpf.o\ndiff --git a/security/landlock/bpf.c b/security/landlock/bpf.c\nnew file mode 100644\nindex 0000000000000..cf89062d35a70\n--- /dev/null\n+++ b/security/landlock/bpf.c\n@@ -0,0 +1,130 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Landlock - LSM policy kptr hooks\n+ *\n+ * Implementation of the LSM hooks backing the Landlock kfuncs\n+ *\n+ * Copyright © 2026 Justin Suess \u003cutilityemal77@gmail.com\u003e\n+ */\n+\n+#include \u003clinux/binfmts.h\u003e\n+#include \u003clinux/cred.h\u003e\n+#include \u003clinux/err.h\u003e\n+#include \u003clinux/errno.h\u003e\n+#include \u003clinux/fs.h\u003e\n+#include \u003clinux/lsm_hooks.h\u003e\n+#include \u003clinux/memcontrol.h\u003e\n+#include \u003clinux/sched/mm.h\u003e\n+#include \u003cuapi/linux/landlock.h\u003e\n+\n+#include \"bpf.h\"\n+#include \"cred.h\"\n+#include \"limits.h\"\n+#include \"ruleset.h\"\n+#include \"setup.h\"\n+\n+static int hook_policy_kptr_from_fd(int fd, union lsm_policy_kptr *policy)\n+{\n+\tstruct landlock_ruleset *ruleset;\n+\n+\truleset = landlock_get_ruleset_from_fd(fd, FMODE_CAN_READ);\n+\tif (IS_ERR(ruleset))\n+\t\treturn PTR_ERR(ruleset);\n+\n+\tpolicy-\u003elandlock.ruleset = (struct bpf_landlock_ruleset *)ruleset;\n+\treturn 0;\n+}\n+\n+static void hook_policy_kptr_put(union lsm_policy_kptr *policy)\n+{\n+\tstruct landlock_ruleset *ruleset =\n+\t\t(struct landlock_ruleset *)policy-\u003elandlock.ruleset;\n+\n+\t/*\n+\t * May be called from a BPF object destructor that cannot sleep,\n+\t * whereas dropping the last ruleset reference frees it and may\n+\t * sleep: always defer the free.\n+\t */\n+\tlandlock_put_ruleset_deferred(ruleset);\n+}\n+\n+/* Charging scope for the BPF-driven domain allocations: root, i.e. nobody. */\n+static struct mem_cgroup *get_bpf_memcg(void)\n+{\n+#ifdef CONFIG_MEMCG\n+\treturn root_mem_cgroup;\n+#else\n+\treturn NULL;\n+#endif /* CONFIG_MEMCG */\n+}\n+\n+static int hook_bprm_enforce_policy_kptr(struct linux_binprm *bprm,\n+\t\t\t\t\t union lsm_policy_kptr *policy,\n+\t\t\t\t\t u32 flags)\n+{\n+\tstruct landlock_cred_security *bprm_llcred = landlock_cred(bprm-\u003ecred);\n+\tstruct landlock_ruleset *ruleset =\n+\t\t(struct landlock_ruleset *)policy-\u003elandlock.ruleset;\n+\tstruct landlock_restriction restriction;\n+\tstruct mem_cgroup *old_memcg;\n+\tint err;\n+\n+\t/*\n+\t * Same flags as landlock_restrict_self(2), except\n+\t * LANDLOCK_RESTRICT_SELF_TSYNC: the restriction targets the\n+\t * execution, not the calling threads.\n+\t */\n+\tif ((flags | LANDLOCK_MASK_RESTRICT_BINPRM) !=\n+\t LANDLOCK_MASK_RESTRICT_BINPRM)\n+\t\treturn -EINVAL;\n+\n+\t/*\n+\t * The domain confines the execution on behalf of the BPF\n+\t * program, not of the mediated task: do not charge the task's\n+\t * memcg for it.\n+\t */\n+\told_memcg = set_active_memcg(get_bpf_memcg());\n+\terr = landlock_prepare_restriction(bprm_llcred, ruleset, flags,\n+\t\t\t\t\t \u0026restriction);\n+\tset_active_memcg(old_memcg);\n+\tif (err)\n+\t\treturn err;\n+\n+\t/*\n+\t * Stages the restriction until the point of no return of the\n+\t * execution, replacing (and releasing) any previously staged\n+\t * one. Nothing is enforced yet: an execution that fails before\n+\t * committing its credentials drops the staged restriction in\n+\t * hook_cred_free() with no effect on the calling task.\n+\t */\n+\tlandlock_put_ruleset(bprm_llcred-\u003estaged.domain);\n+\tbprm_llcred-\u003estaged = restriction;\n+\treturn 0;\n+}\n+\n+static void hook_bprm_committing_creds(const struct linux_binprm *bprm)\n+{\n+\tstruct landlock_cred_security *bprm_llcred = landlock_cred(bprm-\u003ecred);\n+\tstruct landlock_restriction restriction;\n+\n+\tif (!bprm_llcred-\u003estaged.domain)\n+\t\treturn;\n+\n+\trestriction = bprm_llcred-\u003estaged;\n+\tbprm_llcred-\u003estaged = (struct landlock_restriction){};\n+\n+\tlandlock_apply_restriction(bprm_llcred, \u0026restriction);\n+}\n+\n+static struct security_hook_list landlock_hooks[] __ro_after_init = {\n+\tLSM_HOOK_INIT(policy_kptr_from_fd, hook_policy_kptr_from_fd),\n+\tLSM_HOOK_INIT(policy_kptr_put, hook_policy_kptr_put),\n+\tLSM_HOOK_INIT(bprm_enforce_policy_kptr, hook_bprm_enforce_policy_kptr),\n+\tLSM_HOOK_INIT(bprm_committing_creds, hook_bprm_committing_creds),\n+};\n+\n+__init void landlock_add_bpf_hooks(void)\n+{\n+\tsecurity_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),\n+\t\t\t \u0026landlock_lsmid);\n+}\ndiff --git a/security/landlock/bpf.h b/security/landlock/bpf.h\nnew file mode 100644\nindex 0000000000000..e57729e6a5644\n--- /dev/null\n+++ b/security/landlock/bpf.h\n@@ -0,0 +1,21 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Landlock - LSM policy kptr hooks\n+ *\n+ * Copyright © 2026 Justin Suess \u003cutilityemal77@gmail.com\u003e\n+ */\n+\n+#ifndef _SECURITY_LANDLOCK_BPF_H\n+#define _SECURITY_LANDLOCK_BPF_H\n+\n+#include \u003clinux/init.h\u003e\n+\n+#ifdef CONFIG_BPF_LSM\n+__init void landlock_add_bpf_hooks(void);\n+#else /* CONFIG_BPF_LSM */\n+static inline void landlock_add_bpf_hooks(void)\n+{\n+}\n+#endif /* CONFIG_BPF_LSM */\n+\n+#endif /* _SECURITY_LANDLOCK_BPF_H */\ndiff --git a/security/landlock/cred.c b/security/landlock/cred.c\nindex cc419de75cd6b..efbfd0c204753 100644\n--- a/security/landlock/cred.c\n+++ b/security/landlock/cred.c\n@@ -8,14 +8,114 @@\n */\n \n #include \u003clinux/binfmts.h\u003e\n+#include \u003clinux/bits.h\u003e\n #include \u003clinux/cred.h\u003e\n+#include \u003clinux/err.h\u003e\n+#include \u003clinux/errno.h\u003e\n #include \u003clinux/lsm_hooks.h\u003e\n+#include \u003cuapi/linux/landlock.h\u003e\n \n #include \"common.h\"\n #include \"cred.h\"\n+#include \"domain.h\"\n #include \"ruleset.h\"\n #include \"setup.h\"\n \n+/**\n+ * landlock_prepare_restriction - Compute a credential restriction\n+ *\n+ * @llcred: Landlock credentials to restrict: provides the parent domain and\n+ * the previous log configuration. Not modified.\n+ * @ruleset: Ruleset to enforce, or NULL for a log-configuration-only change.\n+ * @flags: landlock_restrict_self(2) flags. The caller is responsible for\n+ * validating them against the set of flags it supports.\n+ * @restriction: Computed restriction. On success, holds a reference on\n+ * @restriction-\u003edomain (if any), which\n+ * landlock_apply_restriction() transfers to the restricted\n+ * credentials.\n+ *\n+ * The restriction builds on @llcred's current state: the caller must apply\n+ * it to (or stage it for) these same credentials.\n+ *\n+ * Return: 0 on success, -errno on failure.\n+ */\n+int landlock_prepare_restriction(\n+\tconst struct landlock_cred_security *const llcred,\n+\tstruct landlock_ruleset *const ruleset, const u32 flags,\n+\tstruct landlock_restriction *const restriction)\n+{\n+#ifdef CONFIG_AUDIT\n+\t/* Translates \"off\" and \"on\" flags to booleans. */\n+\tconst bool log_same_exec =\n+\t\t!(flags \u0026 LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF);\n+\tconst bool log_new_exec =\n+\t\t!!(flags \u0026 LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);\n+\tconst bool log_subdomains =\n+\t\t!(flags \u0026 LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);\n+\tconst bool prev_log_subdomains = !llcred-\u003elog_subdomains_off;\n+#endif /* CONFIG_AUDIT */\n+\n+\t*restriction = (struct landlock_restriction){};\n+\n+#ifdef CONFIG_AUDIT\n+\trestriction-\u003elog_subdomains_off = !prev_log_subdomains ||\n+\t\t\t\t\t !log_subdomains;\n+#endif /* CONFIG_AUDIT */\n+\n+\tif (!ruleset)\n+\t\treturn 0;\n+\n+\trestriction-\u003edomain = landlock_merge_ruleset(llcred-\u003edomain, ruleset);\n+\tif (IS_ERR(restriction-\u003edomain)) {\n+\t\tconst int err = PTR_ERR(restriction-\u003edomain);\n+\n+\t\trestriction-\u003edomain = NULL;\n+\t\treturn err;\n+\t}\n+\n+#ifdef CONFIG_AUDIT\n+\trestriction-\u003edomain-\u003ehierarchy-\u003elog_same_exec = log_same_exec;\n+\trestriction-\u003edomain-\u003ehierarchy-\u003elog_new_exec = log_new_exec;\n+\tif ((!log_same_exec \u0026\u0026 !log_new_exec) || !prev_log_subdomains)\n+\t\trestriction-\u003edomain-\u003ehierarchy-\u003elog_status =\n+\t\t\tLANDLOCK_LOG_DISABLED;\n+#endif /* CONFIG_AUDIT */\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * landlock_apply_restriction - Enforce a computed restriction on credentials\n+ *\n+ * @llcred: Landlock credentials to restrict, exclusively owned by the caller\n+ * (prepared and not yet committed).\n+ * @restriction: Restriction computed by landlock_prepare_restriction()\n+ * against the same credential state; its domain reference is\n+ * transferred to @llcred.\n+ *\n+ * Cannot fail, so that a caller may apply a restriction past its last point\n+ * of failure, e.g. an exec point of no return.\n+ */\n+void landlock_apply_restriction(struct landlock_cred_security *const llcred,\n+\t\t\t\tstruct landlock_restriction *const restriction)\n+{\n+#ifdef CONFIG_AUDIT\n+\tllcred-\u003elog_subdomains_off = restriction-\u003elog_subdomains_off;\n+#endif /* CONFIG_AUDIT */\n+\n+\tif (!restriction-\u003edomain)\n+\t\treturn;\n+\n+\t/* Replaces the old domain. */\n+\tlandlock_put_ruleset(llcred-\u003edomain);\n+\tllcred-\u003edomain = restriction-\u003edomain;\n+\trestriction-\u003edomain = NULL;\n+\n+#ifdef CONFIG_AUDIT\n+\tllcred-\u003edomain_exec |= BIT(llcred-\u003edomain-\u003enum_layers - 1);\n+#endif /* CONFIG_AUDIT */\n+}\n+\n static void hook_cred_transfer(struct cred *const new,\n \t\t\t const struct cred *const old)\n {\n@@ -24,6 +124,12 @@ static void hook_cred_transfer(struct cred *const new,\n \n \tlandlock_get_ruleset(old_llcred-\u003edomain);\n \t*landlock_cred(new) = *old_llcred;\n+\n+#ifdef CONFIG_BPF_LSM\n+\t/* Only bprm credentials own a staged restriction: never copied. */\n+\tWARN_ON_ONCE(landlock_cred(new)-\u003estaged.domain);\n+\tlandlock_cred(new)-\u003estaged = (struct landlock_restriction){};\n+#endif /* CONFIG_BPF_LSM */\n }\n \n static int hook_cred_prepare(struct cred *const new,\n@@ -35,10 +141,14 @@ static int hook_cred_prepare(struct cred *const new,\n \n static void hook_cred_free(struct cred *const cred)\n {\n-\tstruct landlock_ruleset *const dom = landlock_cred(cred)-\u003edomain;\n+\tstruct landlock_cred_security *const llcred = landlock_cred(cred);\n+\n+\tlandlock_put_ruleset_deferred(llcred-\u003edomain);\n \n-\tif (dom)\n-\t\tlandlock_put_ruleset_deferred(dom);\n+#ifdef CONFIG_BPF_LSM\n+\t/* Releases a restriction staged for an aborted execution. */\n+\tlandlock_put_ruleset_deferred(llcred-\u003estaged.domain);\n+#endif /* CONFIG_BPF_LSM */\n }\n \n #ifdef CONFIG_AUDIT\ndiff --git a/security/landlock/cred.h b/security/landlock/cred.h\nindex f287c56b5fd4b..74f8c9808dc4c 100644\n--- a/security/landlock/cred.h\n+++ b/security/landlock/cred.h\n@@ -20,6 +20,31 @@\n #include \"ruleset.h\"\n #include \"setup.h\"\n \n+/**\n+ * struct landlock_restriction - Computed credential restriction\n+ *\n+ * The result of landlock_prepare_restriction(): the new state that\n+ * enforcing a ruleset with a set of landlock_restrict_self(2) flags\n+ * gives to a credential, decoupled from its application. It is\n+ * enforced with landlock_apply_restriction(), either right away\n+ * (landlock_restrict_self(2)) or after a staging period (restriction\n+ * of an execution).\n+ */\n+struct landlock_restriction {\n+\t/**\n+\t * @domain: New domain to enforce, owning a reference. NULL if the\n+\t * restriction only carries a log configuration change.\n+\t */\n+\tstruct landlock_ruleset *domain;\n+#ifdef CONFIG_AUDIT\n+\t/**\n+\t * @log_subdomains_off: New value of the credentials'\n+\t * @landlock_cred_security.log_subdomains_off.\n+\t */\n+\tu8 log_subdomains_off : 1;\n+#endif /* CONFIG_AUDIT */\n+};\n+\n /**\n * struct landlock_cred_security - Credential security blob\n *\n@@ -35,6 +60,18 @@ struct landlock_cred_security {\n \t */\n \tstruct landlock_ruleset *domain;\n \n+#ifdef CONFIG_BPF_LSM\n+\t/**\n+\t * @staged: Restriction staged by the bprm_enforce_policy_kptr() hook,\n+\t * owning its domain reference and applied at the point of no return of\n+\t * the execution (bprm_committing_creds). Only ever set on the\n+\t * credentials prepared for an execution, between the staging and\n+\t * either the application or the release of the aborted credentials;\n+\t * committed task credentials never carry a staged restriction.\n+\t */\n+\tstruct landlock_restriction staged;\n+#endif /* CONFIG_BPF_LSM */\n+\n #ifdef CONFIG_AUDIT\n \t/**\n \t * @domain_exec: Bitmask identifying the domain layers that were enforced by\n@@ -75,6 +112,12 @@ static inline void landlock_cred_copy(struct landlock_cred_security *dst,\n \t*dst = *src;\n \n \tlandlock_get_ruleset(src-\u003edomain);\n+\n+#ifdef CONFIG_BPF_LSM\n+\t/* Only bprm credentials own a staged restriction: never copied. */\n+\tWARN_ON_ONCE(src-\u003estaged.domain);\n+\tdst-\u003estaged = (struct landlock_restriction){};\n+#endif /* CONFIG_BPF_LSM */\n }\n \n static inline struct landlock_ruleset *landlock_get_current_domain(void)\n@@ -153,6 +196,14 @@ landlock_get_applicable_subject(const struct cred *const cred,\n \treturn NULL;\n }\n \n+int landlock_prepare_restriction(\n+\tconst struct landlock_cred_security *const llcred,\n+\tstruct landlock_ruleset *const ruleset, const u32 flags,\n+\tstruct landlock_restriction *const restriction);\n+\n+void landlock_apply_restriction(struct landlock_cred_security *const llcred,\n+\t\t\t\tstruct landlock_restriction *const restriction);\n+\n __init void landlock_add_cred_hooks(void);\n \n #endif /* _SECURITY_LANDLOCK_CRED_H */\ndiff --git a/security/landlock/limits.h b/security/landlock/limits.h\nindex 08d5f2f6d321a..0bedfe650ea05 100644\n--- a/security/landlock/limits.h\n+++ b/security/landlock/limits.h\n@@ -37,6 +37,10 @@\n #define LANDLOCK_LAST_RESTRICT_SELF\tLANDLOCK_RESTRICT_SELF_TSYNC\n #define LANDLOCK_MASK_RESTRICT_SELF\t((LANDLOCK_LAST_RESTRICT_SELF \u003c\u003c 1) - 1)\n \n+/* Subset of the restrict-self flags applicable to an execution. */\n+#define LANDLOCK_MASK_RESTRICT_BINPRM \\\n+\t(LANDLOCK_MASK_RESTRICT_SELF \u0026 ~LANDLOCK_RESTRICT_SELF_TSYNC)\n+\n /* clang-format on */\n \n #endif /* _SECURITY_LANDLOCK_LIMITS_H */\ndiff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c\nindex 4dd09ea22c844..176c626f9f105 100644\n--- a/security/landlock/ruleset.c\n+++ b/security/landlock/ruleset.c\n@@ -520,7 +520,7 @@ static void free_ruleset_work(struct work_struct *const work)\n \tfree_ruleset(ruleset);\n }\n \n-/* Only called by hook_cred_free(). */\n+/* For contexts that cannot sleep, e.g. hook_cred_free(). */\n void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)\n {\n \tif (ruleset \u0026\u0026 refcount_dec_and_test(\u0026ruleset-\u003eusage)) {\ndiff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h\nindex 0437adf174287..b65d1c07e192d 100644\n--- a/security/landlock/ruleset.h\n+++ b/security/landlock/ruleset.h\n@@ -225,6 +225,9 @@ struct landlock_ruleset *\n landlock_merge_ruleset(struct landlock_ruleset *const parent,\n \t\t struct landlock_ruleset *const ruleset);\n \n+struct landlock_ruleset *landlock_get_ruleset_from_fd(const int fd,\n+\t\t\t\t\t\t const fmode_t mode);\n+\n const struct landlock_rule *\n landlock_find_rule(const struct landlock_ruleset *const ruleset,\n \t\t const struct landlock_id id);\ndiff --git a/security/landlock/setup.c b/security/landlock/setup.c\nindex 47dac1736f100..3b7e18edadfbc 100644\n--- a/security/landlock/setup.c\n+++ b/security/landlock/setup.c\n@@ -11,6 +11,7 @@\n #include \u003clinux/lsm_hooks.h\u003e\n #include \u003cuapi/linux/lsm.h\u003e\n \n+#include \"bpf.h\"\n #include \"common.h\"\n #include \"cred.h\"\n #include \"errata.h\"\n@@ -68,6 +69,7 @@ static int __init landlock_init(void)\n \tlandlock_add_task_hooks();\n \tlandlock_add_fs_hooks();\n \tlandlock_add_net_hooks();\n+\tlandlock_add_bpf_hooks();\n \tlandlock_init_id();\n \tlandlock_initialized = true;\n \tpr_info(\"Up and running.\\n\");\ndiff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c\nindex 36b02892c62f5..899601af7c4e1 100644\n--- a/security/landlock/syscalls.c\n+++ b/security/landlock/syscalls.c\n@@ -293,8 +293,8 @@ SYSCALL_DEFINE3(landlock_create_ruleset,\n * Returns an owned ruleset from a FD. It is thus needed to call\n * landlock_put_ruleset() on the return value.\n */\n-static struct landlock_ruleset *get_ruleset_from_fd(const int fd,\n-\t\t\t\t\t\t const fmode_t mode)\n+struct landlock_ruleset *landlock_get_ruleset_from_fd(const int fd,\n+\t\t\t\t\t\t const fmode_t mode)\n {\n \tCLASS(fd, ruleset_f)(fd);\n \tstruct landlock_ruleset *ruleset;\n@@ -476,7 +476,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,\n \t\treturn -EINVAL;\n \n \t/* Gets and checks the ruleset. */\n-\truleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);\n+\truleset = landlock_get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);\n \tif (IS_ERR(ruleset))\n \t\treturn PTR_ERR(ruleset);\n \n@@ -528,9 +528,8 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,\n {\n \tstruct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL;\n \tstruct cred *new_cred;\n-\tstruct landlock_cred_security *new_llcred;\n-\tbool __maybe_unused log_same_exec, log_new_exec, log_subdomains,\n-\t\tprev_log_subdomains;\n+\tstruct landlock_restriction restriction;\n+\tint err;\n \n \tif (!is_initialized())\n \t\treturn -EOPNOTSUPP;\n@@ -547,13 +546,6 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,\n \t LANDLOCK_MASK_RESTRICT_SELF)\n \t\treturn -EINVAL;\n \n-\t/* Translates \"off\" flag to boolean. */\n-\tlog_same_exec = !(flags \u0026 LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF);\n-\t/* Translates \"on\" flag to boolean. */\n-\tlog_new_exec = !!(flags \u0026 LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);\n-\t/* Translates \"off\" flag to boolean. */\n-\tlog_subdomains = !(flags \u0026 LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);\n-\n \t/*\n \t * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with\n \t * -1 as ruleset_fd, optionally combined with\n@@ -564,7 +556,8 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,\n \t (flags \u0026 ~LANDLOCK_RESTRICT_SELF_TSYNC) ==\n \t\t LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) {\n \t\t/* Gets and checks the ruleset. */\n-\t\truleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ);\n+\t\truleset = landlock_get_ruleset_from_fd(ruleset_fd,\n+\t\t\t\t\t\t FMODE_CAN_READ);\n \t\tif (IS_ERR(ruleset))\n \t\t\treturn PTR_ERR(ruleset);\n \t}\n@@ -574,53 +567,28 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,\n \tif (!new_cred)\n \t\treturn -ENOMEM;\n \n-\tnew_llcred = landlock_cred(new_cred);\n-\n-#ifdef CONFIG_AUDIT\n-\tprev_log_subdomains = !new_llcred-\u003elog_subdomains_off;\n-\tnew_llcred-\u003elog_subdomains_off = !prev_log_subdomains ||\n-\t\t\t\t\t !log_subdomains;\n-#endif /* CONFIG_AUDIT */\n-\n \t/*\n \t * The only case when a ruleset may not be set is if\n \t * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set (optionally with\n \t * LANDLOCK_RESTRICT_SELF_TSYNC) and ruleset_fd is -1. We could\n \t * optimize this case by not calling commit_creds() if this flag was\n \t * already set, but it is not worth the complexity.\n+\t *\n+\t * There is no possible race condition while copying and manipulating\n+\t * the current credentials because they are dedicated per thread.\n \t */\n-\tif (ruleset) {\n-\t\t/*\n-\t\t * There is no possible race condition while copying and\n-\t\t * manipulating the current credentials because they are\n-\t\t * dedicated per thread.\n-\t\t */\n-\t\tstruct landlock_ruleset *const new_dom =\n-\t\t\tlandlock_merge_ruleset(new_llcred-\u003edomain, ruleset);\n-\t\tif (IS_ERR(new_dom)) {\n-\t\t\tabort_creds(new_cred);\n-\t\t\treturn PTR_ERR(new_dom);\n-\t\t}\n-\n-#ifdef CONFIG_AUDIT\n-\t\tnew_dom-\u003ehierarchy-\u003elog_same_exec = log_same_exec;\n-\t\tnew_dom-\u003ehierarchy-\u003elog_new_exec = log_new_exec;\n-\t\tif ((!log_same_exec \u0026\u0026 !log_new_exec) || !prev_log_subdomains)\n-\t\t\tnew_dom-\u003ehierarchy-\u003elog_status = LANDLOCK_LOG_DISABLED;\n-#endif /* CONFIG_AUDIT */\n-\n-\t\t/* Replaces the old (prepared) domain. */\n-\t\tlandlock_put_ruleset(new_llcred-\u003edomain);\n-\t\tnew_llcred-\u003edomain = new_dom;\n-\n-#ifdef CONFIG_AUDIT\n-\t\tnew_llcred-\u003edomain_exec |= BIT(new_dom-\u003enum_layers - 1);\n-#endif /* CONFIG_AUDIT */\n+\terr = landlock_prepare_restriction(landlock_cred(new_cred), ruleset,\n+\t\t\t\t\t flags, \u0026restriction);\n+\tif (err) {\n+\t\tabort_creds(new_cred);\n+\t\treturn err;\n \t}\n \n+\tlandlock_apply_restriction(landlock_cred(new_cred), \u0026restriction);\n+\n \tif (flags \u0026 LANDLOCK_RESTRICT_SELF_TSYNC) {\n-\t\tconst int err = landlock_restrict_sibling_threads(\n-\t\t\tcurrent_cred(), new_cred);\n+\t\terr = landlock_restrict_sibling_threads(current_cred(),\n+\t\t\t\t\t\t\tnew_cred);\n \t\tif (err) {\n \t\t\tabort_creds(new_cred);\n \t\t\treturn err;\ndiff --git a/security/security.c b/security/security.c\nindex 71aea8fdf0142..e9d8c9492bdb2 100644\n--- a/security/security.c\n+++ b/security/security.c\n@@ -5441,6 +5441,112 @@ int security_bpf_token_capable(const struct bpf_token *token, int cap)\n \treturn call_int_hook(bpf_token_capable, token, cap);\n }\n \n+/**\n+ * security_policy_kptr_from_fd() - Get an LSM policy object from a fd\n+ * @lsmid: LSM_ID_* value of the LSM asked to interpret @fd\n+ * @fd: file descriptor referring to a policy object, resolved in the\n+ * calling task's file descriptor table\n+ * @policy: receives the referenced policy object in the member of the\n+ * LSM identified by @lsmid\n+ *\n+ * Ask the LSM identified by @lsmid to translate @fd into a reference\n+ * counted policy object. The caller must not dereference the\n+ * returned handle, must only hand it back to the same LSM, e.g.\n+ * through security_bprm_enforce_policy_kptr(), and must release it\n+ * with security_policy_kptr_put(). Only the hook implementation of\n+ * the LSM identified by @lsmid is called. The hook is only called\n+ * from a context that may sleep.\n+ *\n+ * An implementation must fill its own member of @policy with a\n+ * reference that remains valid until it is released through the\n+ * policy_kptr_put hook, and must not assume anything about @fd beyond\n+ * what its own userspace API created it with.\n+ *\n+ * Return: Returns 0 if @policy holds a reference counted policy\n+ * object, -EOPNOTSUPP if the LSM does not implement the hook, negative\n+ * values on other failures.\n+ */\n+int security_policy_kptr_from_fd(u64 lsmid, int fd,\n+\t\t\t\t union lsm_policy_kptr *policy)\n+{\n+\tstruct lsm_static_call *scall;\n+\n+\tlsm_for_each_hook(scall, policy_kptr_from_fd) {\n+\t\tif (scall-\u003ehl-\u003elsmid-\u003eid != lsmid)\n+\t\t\tcontinue;\n+\t\treturn scall-\u003ehl-\u003ehook.policy_kptr_from_fd(fd, policy);\n+\t}\n+\treturn LSM_RET_DEFAULT(policy_kptr_from_fd);\n+}\n+\n+/**\n+ * security_policy_kptr_put() - Put a reference on an LSM policy object\n+ * @lsmid: LSM_ID_* value of the LSM owning @policy\n+ * @policy: the policy object, in the member of the LSM identified by\n+ * @lsmid\n+ *\n+ * Release a reference previously obtained with\n+ * security_policy_kptr_from_fd(). Only the hook implementation\n+ * of the LSM identified by @lsmid is called, and @policy must have\n+ * been obtained from that same LSM. An implementation must support\n+ * being called from a context that cannot sleep: the release of BPF\n+ * managed references may be driven from object destructors.\n+ */\n+void security_policy_kptr_put(u64 lsmid, union lsm_policy_kptr *policy)\n+{\n+\tstruct lsm_static_call *scall;\n+\n+\tlsm_for_each_hook(scall, policy_kptr_put) {\n+\t\tif (scall-\u003ehl-\u003elsmid-\u003eid != lsmid)\n+\t\t\tcontinue;\n+\t\tscall-\u003ehl-\u003ehook.policy_kptr_put(policy);\n+\t\treturn;\n+\t}\n+\t/* A held reference implies the matching LSM implements the hook. */\n+\tWARN_ON_ONCE(1);\n+}\n+\n+/**\n+ * security_bprm_enforce_policy_kptr() - Enforce a policy on exec credentials\n+ * @lsmid: LSM_ID_* value of the LSM owning @policy\n+ * @bprm: execution context providing the prepared credentials to restrict\n+ * @policy: the policy object to enforce, in the member of the LSM\n+ * identified by @lsmid\n+ * @flags: LSM-specific enforcement flags\n+ *\n+ * Ask the LSM identified by @lsmid to restrict the credentials\n+ * prepared in @bprm with @policy, so that the executed task starts\n+ * confined by it. @policy must have been obtained from the same LSM\n+ * with security_policy_kptr_from_fd(); the hook borrows the\n+ * reference and the caller remains responsible for releasing it.\n+ * Only the hook implementation of the LSM identified by @lsmid is\n+ * called: an LSM never receives a policy object meant for another LSM.\n+ *\n+ * This hook may only be called from an exec security context where\n+ * @bprm's credentials are prepared but not yet committed, i.e. from a\n+ * bprm_creds_for_exec() or bprm_creds_from_file() hook.\n+ *\n+ * How @policy composes with restrictions the credentials already\n+ * carry is defined by the implementing LSM, as is the meaning of\n+ * @flags, unsupported values of which it must reject with -EINVAL.\n+ *\n+ * Return: Returns 0 on success, -EOPNOTSUPP if the LSM does not\n+ * implement the hook, negative values on other failures.\n+ */\n+int security_bprm_enforce_policy_kptr(u64 lsmid, struct linux_binprm *bprm,\n+\t\t\t\t union lsm_policy_kptr *policy, u32 flags)\n+{\n+\tstruct lsm_static_call *scall;\n+\n+\tlsm_for_each_hook(scall, bprm_enforce_policy_kptr) {\n+\t\tif (scall-\u003ehl-\u003elsmid-\u003eid != lsmid)\n+\t\t\tcontinue;\n+\t\treturn scall-\u003ehl-\u003ehook.bprm_enforce_policy_kptr(bprm, policy,\n+\t\t\t\t\t\t\t\t flags);\n+\t}\n+\treturn LSM_RET_DEFAULT(bprm_enforce_policy_kptr);\n+}\n+\n /**\n * security_bpf_map_free() - Free a bpf map's LSM blob\n * @map: bpf map\ndiff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config\nindex ea7044f30adc3..2fa7344974612 100644\n--- a/tools/testing/selftests/bpf/config\n+++ b/tools/testing/selftests/bpf/config\n@@ -120,6 +120,7 @@ CONFIG_SAMPLES=y\n CONFIG_SAMPLE_LIVEPATCH=m\n CONFIG_SECURITY=y\n CONFIG_SECURITYFS=y\n+CONFIG_SECURITY_LANDLOCK=y\n CONFIG_SYN_COOKIES=y\n CONFIG_TEST_BPF=m\n CONFIG_UDMABUF=y\ndiff --git a/tools/testing/selftests/bpf/config.x86_64 b/tools/testing/selftests/bpf/config.x86_64\nindex 523e0d29bbd43..2c4d857f69f54 100644\n--- a/tools/testing/selftests/bpf/config.x86_64\n+++ b/tools/testing/selftests/bpf/config.x86_64\n@@ -125,7 +125,7 @@ CONFIG_LEGACY_VSYSCALL_NONE=y\n CONFIG_LOG_BUF_SHIFT=21\n CONFIG_LOG_CPU_MAX_BUF_SHIFT=0\n CONFIG_LOGO=y\n-CONFIG_LSM=\"selinux,bpf,integrity\"\n+CONFIG_LSM=\"landlock,selinux,bpf,integrity\"\n CONFIG_MAC_PARTITION=y\n CONFIG_MAGIC_SYSRQ=y\n CONFIG_MCORE2=y\ndiff --git a/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c\nnew file mode 100644\nindex 0000000000000..c91929f98e88e\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c\n@@ -0,0 +1,329 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright © 2026 Justin Suess \u003cutilityemal77@gmail.com\u003e */\n+\n+#include \u003ctest_progs.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003clinux/landlock.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003csys/syscall.h\u003e\n+#include \u003csys/wait.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"lsm_policy_kfuncs_success.skel.h\"\n+#include \"lsm_policy_kfuncs_failure.skel.h\"\n+\n+/* Fallbacks for old system headers. */\n+#ifndef LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON\n+#define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (1U \u003c\u003c 1)\n+#endif\n+#ifndef LANDLOCK_RESTRICT_SELF_TSYNC\n+#define LANDLOCK_RESTRICT_SELF_TSYNC (1U \u003c\u003c 3)\n+#endif\n+\n+static int create_ruleset(void)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_access_fs = LANDLOCK_ACCESS_FS_WRITE_FILE,\n+\t};\n+\n+\treturn syscall(__NR_landlock_create_ruleset, \u0026attr, sizeof(attr), 0);\n+}\n+\n+static void reset_prog_state(struct lsm_policy_kfuncs_success *skel)\n+{\n+\tskel-\u003ebss-\u003ecalled = false;\n+\tskel-\u003ebss-\u003eno_ruleset = false;\n+\tskel-\u003ebss-\u003erestrict_err = -1;\n+\tskel-\u003ebss-\u003erestrict2_err = -1;\n+\tskel-\u003ebss-\u003ekfunc_flags = 0;\n+\tskel-\u003ebss-\u003edouble_call = false;\n+}\n+\n+/*\n+ * Runs the syscall program that acquires the ruleset from\n+ * @ruleset_fd, in the runner's fd table, and parks it in the map kptr\n+ * slot for the LSM program.\n+ */\n+static int load_ruleset_into_map(struct lsm_policy_kfuncs_success *skel)\n+{\n+\tLIBBPF_OPTS(bpf_test_run_opts, opts);\n+\tint err;\n+\n+\terr = bpf_prog_test_run_opts(bpf_program__fd(skel-\u003eprogs.load_ruleset),\n+\t\t\t\t \u0026opts);\n+\tif (!ASSERT_OK(err, \"load_ruleset_run\"))\n+\t\treturn -1;\n+\tif (!ASSERT_OK(opts.retval, \"load_ruleset_retval\"))\n+\t\treturn -1;\n+\tASSERT_TRUE(skel-\u003ebss-\u003egot_null_for_bad_fd, \"bad_fd_null\");\n+\treturn 0;\n+}\n+\n+/*\n+ * Forks a child that execs \"sh -c '\u003cshell_cmd\u003e'\". The monitored pid\n+ * is only known, and can only be published to the BPF program, once\n+ * the child exists: the child waits on a pipe until the parent has\n+ * updated it. Returns the child's exit status, or -1 on error.\n+ */\n+static int run_exec_child(struct lsm_policy_kfuncs_success *skel,\n+\t\t\t bool monitored, const char *shell_cmd)\n+{\n+\tint pipe_fds[2], status;\n+\tchar buf = 0;\n+\tpid_t pid;\n+\n+\tif (!ASSERT_OK(pipe(pipe_fds), \"pipe\"))\n+\t\treturn -1;\n+\n+\tpid = fork();\n+\tif (!ASSERT_GE(pid, 0, \"fork\")) {\n+\t\tclose(pipe_fds[0]);\n+\t\tclose(pipe_fds[1]);\n+\t\treturn -1;\n+\t}\n+\tif (pid == 0) {\n+\t\tchar *argv[] = { \"sh\", \"-c\", (char *)shell_cmd, NULL };\n+\n+\t\tclose(pipe_fds[1]);\n+\t\tread(pipe_fds[0], \u0026buf, 1);\n+\t\tclose(pipe_fds[0]);\n+\t\texecv(\"/bin/sh\", argv);\n+\t\texit(127);\n+\t}\n+\tclose(pipe_fds[0]);\n+\tskel-\u003ebss-\u003emonitored_pid = monitored ? pid : 0;\n+\twrite(pipe_fds[1], \u0026buf, 1);\n+\tclose(pipe_fds[1]);\n+\n+\tif (!ASSERT_EQ(waitpid(pid, \u0026status, 0), pid, \"waitpid\"))\n+\t\treturn -1;\n+\tif (!ASSERT_TRUE(WIFEXITED(status), \"child_exited\"))\n+\t\treturn -1;\n+\treturn WEXITSTATUS(status);\n+}\n+\n+/*\n+ * Exit codes: 4 = unexpected write outcome, 0 = everything as\n+ * expected.\n+ */\n+static void format_child_cmd(char *cmd, size_t len, bool expect_write_ok,\n+\t\t\t const char *tmp_path)\n+{\n+\tif (expect_write_ok)\n+\t\tsnprintf(cmd, len, \"echo x \u003e %s || exit 4; exit 0\", tmp_path);\n+\telse\n+\t\tsnprintf(cmd, len,\n+\t\t\t \"if echo x \u003e %s 2\u003e/dev/null; then exit 4; fi; exit 0\",\n+\t\t\t tmp_path);\n+}\n+\n+static void test_restrict_binprm(void)\n+{\n+\tstruct lsm_policy_kfuncs_success *skel = NULL;\n+\tchar tmp_path[] = \"/tmp/lsm_policy_kfuncs_XXXXXX\";\n+\tchar cmd[256];\n+\tint ruleset_fd, tmp_fd, ret;\n+\n+\ttmp_fd = mkstemp(tmp_path);\n+\tif (!ASSERT_GE(tmp_fd, 0, \"mkstemp\"))\n+\t\treturn;\n+\tclose(tmp_fd);\n+\n+\truleset_fd = create_ruleset();\n+\tif (ruleset_fd \u003c 0) {\n+\t\tif (errno == EOPNOTSUPP || errno == ENOSYS)\n+\t\t\ttest__skip();\n+\t\telse\n+\t\t\tASSERT_GE(ruleset_fd, 0, \"landlock_create_ruleset\");\n+\t\tgoto out_unlink;\n+\t}\n+\n+\tskel = lsm_policy_kfuncs_success__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open_and_load\"))\n+\t\tgoto out;\n+\tskel-\u003ebss-\u003eruleset_fd = ruleset_fd;\n+\n+\tif (!ASSERT_OK(lsm_policy_kfuncs_success__attach(skel), \"skel_attach\"))\n+\t\tgoto out;\n+\n+\tif (load_ruleset_into_map(skel))\n+\t\tgoto out;\n+\n+\t/* Control: an unmonitored execution may write to the tmp file. */\n+\treset_prog_state(skel);\n+\tformat_child_cmd(cmd, sizeof(cmd), true, tmp_path);\n+\tret = run_exec_child(skel, false, cmd);\n+\tif (!ASSERT_EQ(ret, 0, \"control_child_exit\"))\n+\t\tgoto out;\n+\tASSERT_FALSE(skel-\u003ebss-\u003ecalled, \"control_not_monitored\");\n+\n+\t/*\n+\t * A monitored execution starts landlocked: the ruleset handles\n+\t * LANDLOCK_ACCESS_FS_WRITE_FILE without any rule, so the write\n+\t * must fail.\n+\t */\n+\treset_prog_state(skel);\n+\tformat_child_cmd(cmd, sizeof(cmd), false, tmp_path);\n+\tret = run_exec_child(skel, true, cmd);\n+\tif (!ASSERT_EQ(ret, 0, \"restricted_child_exit\"))\n+\t\tgoto out;\n+\tASSERT_TRUE(skel-\u003ebss-\u003ecalled, \"lsm_prog_called\");\n+\tASSERT_FALSE(skel-\u003ebss-\u003eno_ruleset, \"ruleset_in_map\");\n+\tASSERT_EQ(skel-\u003ebss-\u003erestrict_err, 0, \"restrict_binprm\");\n+\n+\t/* The audit log flags of landlock_restrict_self(2) apply too. */\n+\treset_prog_state(skel);\n+\tskel-\u003ebss-\u003ekfunc_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;\n+\tformat_child_cmd(cmd, sizeof(cmd), false, tmp_path);\n+\tret = run_exec_child(skel, true, cmd);\n+\tif (!ASSERT_EQ(ret, 0, \"log_flags_child_exit\"))\n+\t\tgoto out;\n+\tASSERT_EQ(skel-\u003ebss-\u003erestrict_err, 0, \"log_flags_restrict_binprm\");\n+\n+\t/*\n+\t * LANDLOCK_RESTRICT_SELF_TSYNC targets the calling threads, not\n+\t * an execution: the kfunc must reject it and the execution must\n+\t * stay unrestricted.\n+\t */\n+\treset_prog_state(skel);\n+\tskel-\u003ebss-\u003ekfunc_flags = LANDLOCK_RESTRICT_SELF_TSYNC;\n+\tformat_child_cmd(cmd, sizeof(cmd), true, tmp_path);\n+\tret = run_exec_child(skel, true, cmd);\n+\tif (!ASSERT_EQ(ret, 0, \"tsync_child_exit\"))\n+\t\tgoto out;\n+\tASSERT_TRUE(skel-\u003ebss-\u003ecalled, \"tsync_prog_called\");\n+\tASSERT_EQ(skel-\u003ebss-\u003erestrict_err, -EINVAL, \"tsync_rejected\");\n+\n+\t/*\n+\t * A second call on the same execution replaces the staged\n+\t * domain (and releases the first one): the result is a single\n+\t * restriction, not an error.\n+\t */\n+\treset_prog_state(skel);\n+\tskel-\u003ebss-\u003edouble_call = true;\n+\tformat_child_cmd(cmd, sizeof(cmd), false, tmp_path);\n+\tret = run_exec_child(skel, true, cmd);\n+\tif (!ASSERT_EQ(ret, 0, \"double_child_exit\"))\n+\t\tgoto out;\n+\tASSERT_EQ(skel-\u003ebss-\u003erestrict_err, 0, \"double_restrict_first\");\n+\tASSERT_EQ(skel-\u003ebss-\u003erestrict2_err, 0, \"double_restrict_second\");\n+out:\n+\tlsm_policy_kfuncs_success__destroy(skel);\n+\tclose(ruleset_fd);\n+out_unlink:\n+\tunlink(tmp_path);\n+}\n+\n+/*\n+ * Checks that a staged restriction is discarded, and the staged\n+ * domain released, when the execution fails after the bprm hook: the\n+ * calling task must not end up landlocked.\n+ */\n+static void test_restrict_binprm_discard(void)\n+{\n+\tstruct lsm_policy_kfuncs_success *skel = NULL;\n+\tchar tmp_path[] = \"/tmp/lsm_policy_kfuncs_XXXXXX\";\n+\tchar garbage_path[] = \"/tmp/lsm_policy_garbage_XXXXXX\";\n+\tint ruleset_fd = -1, tmp_fd, garbage_fd, pipe_fds[2], status;\n+\tchar buf = 0;\n+\tpid_t pid;\n+\n+\ttmp_fd = mkstemp(tmp_path);\n+\tif (!ASSERT_GE(tmp_fd, 0, \"mkstemp\"))\n+\t\treturn;\n+\tclose(tmp_fd);\n+\n+\t/*\n+\t * An executable file that no binfmt handler accepts: the exec\n+\t * fails with ENOEXEC after bprm_creds_for_exec() has run.\n+\t */\n+\tgarbage_fd = mkstemp(garbage_path);\n+\tif (!ASSERT_GE(garbage_fd, 0, \"mkstemp_garbage\"))\n+\t\tgoto out_unlink;\n+\tif (!ASSERT_EQ(write(garbage_fd, \"junk\\n\", 5), 5, \"write_garbage\")) {\n+\t\tclose(garbage_fd);\n+\t\tgoto out_unlink;\n+\t}\n+\tif (!ASSERT_OK(fchmod(garbage_fd, 0700), \"chmod_garbage\")) {\n+\t\tclose(garbage_fd);\n+\t\tgoto out_unlink;\n+\t}\n+\tclose(garbage_fd);\n+\n+\truleset_fd = create_ruleset();\n+\tif (ruleset_fd \u003c 0) {\n+\t\tif (errno == EOPNOTSUPP || errno == ENOSYS)\n+\t\t\ttest__skip();\n+\t\telse\n+\t\t\tASSERT_GE(ruleset_fd, 0, \"landlock_create_ruleset\");\n+\t\tgoto out_unlink;\n+\t}\n+\n+\tskel = lsm_policy_kfuncs_success__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open_and_load\"))\n+\t\tgoto out;\n+\tskel-\u003ebss-\u003eruleset_fd = ruleset_fd;\n+\treset_prog_state(skel);\n+\n+\tif (!ASSERT_OK(lsm_policy_kfuncs_success__attach(skel), \"skel_attach\"))\n+\t\tgoto out;\n+\n+\tif (load_ruleset_into_map(skel))\n+\t\tgoto out;\n+\n+\tif (!ASSERT_OK(pipe(pipe_fds), \"pipe\"))\n+\t\tgoto out;\n+\n+\tpid = fork();\n+\tif (!ASSERT_GE(pid, 0, \"fork\"))\n+\t\tgoto out;\n+\tif (pid == 0) {\n+\t\tchar *argv[] = { \"garbage\", NULL };\n+\t\tint fd;\n+\n+\t\tclose(pipe_fds[1]);\n+\t\tread(pipe_fds[0], \u0026buf, 1);\n+\t\tclose(pipe_fds[0]);\n+\t\texecv(garbage_path, argv);\n+\t\t/*\n+\t\t * The failed execution must leave no trace: no\n+\t\t * Landlock domain, i.e. writing must still work\n+\t\t * (exit 6).\n+\t\t */\n+\t\tfd = open(tmp_path, O_WRONLY | O_TRUNC);\n+\t\tif (fd \u003c 0)\n+\t\t\texit(6);\n+\t\tclose(fd);\n+\t\texit(0);\n+\t}\n+\tclose(pipe_fds[0]);\n+\tskel-\u003ebss-\u003emonitored_pid = pid;\n+\twrite(pipe_fds[1], \u0026buf, 1);\n+\tclose(pipe_fds[1]);\n+\n+\tif (!ASSERT_EQ(waitpid(pid, \u0026status, 0), pid, \"waitpid\"))\n+\t\tgoto out;\n+\tif (!ASSERT_TRUE(WIFEXITED(status), \"child_exited\"))\n+\t\tgoto out;\n+\tASSERT_EQ(WEXITSTATUS(status), 0, \"discard_child_exit\");\n+\tASSERT_TRUE(skel-\u003ebss-\u003ecalled, \"lsm_prog_called\");\n+\tASSERT_EQ(skel-\u003ebss-\u003erestrict_err, 0, \"restrict_binprm\");\n+out:\n+\tlsm_policy_kfuncs_success__destroy(skel);\n+\tif (ruleset_fd \u003e= 0)\n+\t\tclose(ruleset_fd);\n+out_unlink:\n+\tunlink(garbage_path);\n+\tunlink(tmp_path);\n+}\n+\n+void test_lsm_policy_kfuncs(void)\n+{\n+\tif (test__start_subtest(\"restrict_binprm\"))\n+\t\ttest_restrict_binprm();\n+\tif (test__start_subtest(\"restrict_binprm_discard\"))\n+\t\ttest_restrict_binprm_discard();\n+\tRUN_TESTS(lsm_policy_kfuncs_failure);\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c\nnew file mode 100644\nindex 0000000000000..0335db5470400\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c\n@@ -0,0 +1,100 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright © 2026 Justin Suess \u003cutilityemal77@gmail.com\u003e */\n+\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+#include \"bpf_misc.h\"\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+struct bpf_landlock_ruleset;\n+\n+extern struct bpf_landlock_ruleset *\n+bpf_landlock_get_ruleset_from_fd(int fd) __ksym;\n+extern void\n+bpf_landlock_put_ruleset(struct bpf_landlock_ruleset *ruleset) __ksym;\n+extern int bpf_landlock_restrict_binprm(struct linux_binprm *bprm,\n+\t\t\t\t\tstruct bpf_landlock_ruleset *ruleset,\n+\t\t\t\t\tu32 flags) __ksym;\n+\n+/*\n+ * The LSM policy kfuncs are limited to LSM and syscall programs by\n+ * the BPF-side kfunc filter: a tracing program calling one must fail\n+ * verification.\n+ */\n+SEC(\"tp_btf/task_newtask\")\n+__failure __msg(\"calling kernel function bpf_landlock_get_ruleset_from_fd is not allowed\")\n+int BPF_PROG(tracing_prog, struct task_struct *task, u64 clone_flags)\n+{\n+\tstruct bpf_landlock_ruleset *ruleset;\n+\n+\truleset = bpf_landlock_get_ruleset_from_fd(-1);\n+\tif (ruleset)\n+\t\tbpf_landlock_put_ruleset(ruleset);\n+\treturn 0;\n+}\n+\n+/*\n+ * A ruleset fd is only meaningful in the fd table of the task that\n+ * set the ruleset up: the acquire kfunc is exclusive to syscall\n+ * programs and must be rejected in an LSM program, even on an\n+ * allowed hook.\n+ */\n+SEC(\"lsm.s/bprm_creds_for_exec\")\n+__failure __msg(\"calling kernel function bpf_landlock_get_ruleset_from_fd is not allowed\")\n+int BPF_PROG(lsm_get, struct linux_binprm *bprm)\n+{\n+\tstruct bpf_landlock_ruleset *ruleset;\n+\n+\truleset = bpf_landlock_get_ruleset_from_fd(-1);\n+\tif (ruleset)\n+\t\tbpf_landlock_put_ruleset(ruleset);\n+\treturn 0;\n+}\n+\n+/*\n+ * Enforcement needs an execution to restrict: the enforcement kfunc\n+ * is exclusive to the sleepable bprm LSM hooks and must be rejected\n+ * in a syscall program.\n+ */\n+SEC(\"syscall\")\n+__failure __msg(\"calling kernel function bpf_landlock_restrict_binprm is not allowed\")\n+int syscall_restrict(void *ctx)\n+{\n+\treturn bpf_landlock_restrict_binprm(NULL, NULL, 0);\n+}\n+\n+/*\n+ * Any LSM attach point other than the sleepable bprm hooks must be\n+ * rejected.\n+ */\n+SEC(\"lsm.s/file_open\")\n+__failure __msg(\"calling kernel function bpf_landlock_put_ruleset is not allowed\")\n+int BPF_PROG(wrong_hook, struct file *file)\n+{\n+\tbpf_landlock_put_ruleset(NULL);\n+\treturn 0;\n+}\n+\n+/*\n+ * The kfuncs may sleep: a non-sleepable program on an allowed hook\n+ * must be rejected.\n+ */\n+SEC(\"lsm/bprm_creds_for_exec\")\n+__failure\n+__msg(\"program must be sleepable to call sleepable kfunc bpf_landlock_put_ruleset\")\n+int BPF_PROG(nonsleepable_prog, struct linux_binprm *bprm)\n+{\n+\tbpf_landlock_put_ruleset(NULL);\n+\treturn 0;\n+}\n+\n+/* An acquired ruleset reference must be released before returning. */\n+SEC(\"syscall\")\n+__failure __msg(\"Unreleased reference\")\n+int leak_ruleset(void *ctx)\n+{\n+\tbpf_landlock_get_ruleset_from_fd(-1);\n+\treturn 0;\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c\nnew file mode 100644\nindex 0000000000000..2107206ae144d\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c\n@@ -0,0 +1,107 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright © 2026 Justin Suess \u003cutilityemal77@gmail.com\u003e */\n+\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+struct bpf_landlock_ruleset;\n+\n+extern struct bpf_landlock_ruleset *\n+bpf_landlock_get_ruleset_from_fd(int fd) __ksym;\n+extern void\n+bpf_landlock_put_ruleset(struct bpf_landlock_ruleset *ruleset) __ksym;\n+extern int bpf_landlock_restrict_binprm(struct linux_binprm *bprm,\n+\t\t\t\t\tstruct bpf_landlock_ruleset *ruleset,\n+\t\t\t\t\tu32 flags) __ksym;\n+\n+struct ruleset_slot {\n+\tstruct bpf_landlock_ruleset __kptr *ruleset;\n+};\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARRAY);\n+\t__uint(max_entries, 1);\n+\t__type(key, int);\n+\t__type(value, struct ruleset_slot);\n+} ruleset_map SEC(\".maps\");\n+\n+int monitored_pid;\n+int ruleset_fd;\n+u32 kfunc_flags;\n+bool double_call;\n+bool got_null_for_bad_fd;\n+bool no_ruleset;\n+int restrict_err;\n+int restrict2_err;\n+bool called;\n+\n+/*\n+ * Runs in the test runner's context through BPF_PROG_RUN:\n+ * @ruleset_fd is resolved in the runner's fd table and the acquired\n+ * ruleset is handed to the LSM program through the map kptr slot.\n+ */\n+SEC(\"syscall\")\n+int load_ruleset(void *ctx)\n+{\n+\tstruct bpf_landlock_ruleset *ruleset, *old;\n+\tstruct ruleset_slot *slot;\n+\tint key = 0;\n+\n+\tslot = bpf_map_lookup_elem(\u0026ruleset_map, \u0026key);\n+\tif (!slot)\n+\t\treturn 1;\n+\n+\t/* A fd that is not a Landlock ruleset must resolve to NULL. */\n+\truleset = bpf_landlock_get_ruleset_from_fd(-1);\n+\tif (!ruleset)\n+\t\tgot_null_for_bad_fd = true;\n+\telse\n+\t\tbpf_landlock_put_ruleset(ruleset);\n+\n+\truleset = bpf_landlock_get_ruleset_from_fd(ruleset_fd);\n+\tif (!ruleset)\n+\t\treturn 2;\n+\n+\told = bpf_kptr_xchg(\u0026slot-\u003eruleset, ruleset);\n+\tif (old)\n+\t\tbpf_landlock_put_ruleset(old);\n+\treturn 0;\n+}\n+\n+SEC(\"lsm.s/bprm_creds_for_exec\")\n+int BPF_PROG(restrict_exec, struct linux_binprm *bprm)\n+{\n+\tstruct bpf_landlock_ruleset *ruleset, *old;\n+\tstruct ruleset_slot *slot;\n+\tint key = 0;\n+\n+\tif (monitored_pid != (bpf_get_current_pid_tgid() \u003e\u003e 32))\n+\t\treturn 0;\n+\n+\tcalled = true;\n+\n+\tslot = bpf_map_lookup_elem(\u0026ruleset_map, \u0026key);\n+\tif (!slot)\n+\t\treturn 0;\n+\n+\truleset = bpf_kptr_xchg(\u0026slot-\u003eruleset, NULL);\n+\tif (!ruleset) {\n+\t\tno_ruleset = true;\n+\t\treturn 0;\n+\t}\n+\n+\trestrict_err = bpf_landlock_restrict_binprm(bprm, ruleset, kfunc_flags);\n+\tif (double_call)\n+\t\t/* Replaces the domain staged by the first call. */\n+\t\trestrict2_err = bpf_landlock_restrict_binprm(bprm, ruleset,\n+\t\t\t\t\t\t\t kfunc_flags);\n+\n+\t/* Keep the ruleset for the next monitored execution. */\n+\told = bpf_kptr_xchg(\u0026slot-\u003eruleset, ruleset);\n+\tif (old)\n+\t\tbpf_landlock_put_ruleset(old);\n+\treturn 0;\n+}\n"
} |
| 2/1 | 2026/08/01 11:07 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"BPF_LSM",
"SECURITY_LANDLOCK",
"BPF_SYSCALL",
"SECURITY",
"BPF_EVENTS",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_landlock_get_ruleset_from_fd",
"bpf_landlock_put_ruleset",
"bpf_landlock_restrict_binprm",
"bpf_landlock_put_ruleset_dtor",
"security_policy_kptr_from_fd",
"security_policy_kptr_put",
"security_bprm_enforce_policy_kptr",
"landlock_prepare_restriction",
"landlock_apply_restriction"
],
"Reasoning": "The patch introduces BPF kfuncs for Landlock LSM, allowing BPF programs to enforce Landlock rulesets on executions. This is a functional change to the kernel that introduces new reachable code paths in the BPF and Landlock subsystems, which should be fuzzed.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 4eb6da94ebc515a6ed42f892f192a0bc1efde579
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Aug 1 11:07:27 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst
index c5186526e76f6..01197c7580ec0 100644
--- a/Documentation/security/landlock.rst
+++ b/Documentation/security/landlock.rst
@@ -129,6 +129,31 @@ The reasoning is:
restrictions, because access within the same scope is already
allowed based on ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``.
+BPF kfuncs
+==========
+
+BPF programs can apply a userspace-created Landlock ruleset to an
+execution. A syscall program (``BPF_PROG_TYPE_SYSCALL``), running in
+the context of the process that set the ruleset up, acquires the
+ruleset from its file descriptor and typically hands it over through
+a map kptr field; a sleepable LSM BPF program attached to the
+``bprm_creds_for_exec`` or ``bprm_creds_from_file`` hooks then
+enforces it on an execution.
+
+This can be used to inspect the runtime context of a pending execution,
+and enforce a Landlock policy through BPF.
+
+The restriction is staged in the Landlock blob of the
+credentials prepared for the execution and committed past the exec
+point of no return, so a failed execution leaves the calling task
+untouched. The ``landlock_restrict_self(2)`` flags apply, with the
+exception of ``LANDLOCK_RESTRICT_SELF_TSYNC``.
+
+.. kernel-doc:: kernel/bpf/bpf_lsm.c
+ :identifiers: bpf_landlock_get_ruleset_from_fd
+ bpf_landlock_put_ruleset
+ bpf_landlock_restrict_binprm
+
Tests
=====
diff --git a/Documentation/security/lsm-development.rst b/Documentation/security/lsm-development.rst
index 5895e529da7ff..fc3af206e7950 100644
--- a/Documentation/security/lsm-development.rst
+++ b/Documentation/security/lsm-development.rst
@@ -15,3 +15,30 @@ see ``security/security.c`` and associated structures:
.. kernel-doc:: security/security.c
:export:
+
+LSM policy kptr hooks and BPF kfuncs
+====================================
+
+The LSM framework implements an interface for individual LSMs to
+expose their configuration through BPF kfuncs and kptrs. An LSM
+may not export any kfunc or other BPF interface directly.
+
+An LSM wishing to expose a BPF kfunc must reuse an existing security
+hook or implement a new sufficiently generic LSM hook for the desired
+interface. The hooks are then called from kfunc definitions in
+``kernel/bpf``. This allows LSM hooks to remain sufficiently generic
+while allowing BPF programs to take advantage of the strong typing
+and runtime checking offered by the BPF verifier.
+
+The LSM providing an operation implements the operation's hook with
+``LSM_HOOK_INIT()`` like any other hook. A BPF program calling an
+LSM kfunc therefore reaches the LSM the same way every other
+kernel caller does: through an LSM hook. The hooks backing kfuncs
+follow the usual rules for new LSM hooks: their contract must be
+LSM agnostic so that other LSMs could provide a meaningful
+implementation of the same operation.
+
+Whether the LSM providing an operation is built in and active is a
+runtime property: the kfuncs are always registered when
+``CONFIG_BPF_LSM`` is enabled. BPF program loading is thus
+independent of the boot-time LSM configuration.
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 65c9609ec2077..a70edbd7b761e 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -452,6 +452,11 @@ LSM_HOOK(int, 0, bpf_token_create, struct bpf_token *token, union bpf_attr *attr
LSM_HOOK(void, LSM_RET_VOID, bpf_token_free, struct bpf_token *token)
LSM_HOOK(int, 0, bpf_token_cmd, const struct bpf_token *token, enum bpf_cmd cmd)
LSM_HOOK(int, 0, bpf_token_capable, const struct bpf_token *token, int cap)
+LSM_HOOK(int, -EOPNOTSUPP, policy_kptr_from_fd, int fd,
+ union lsm_policy_kptr *policy)
+LSM_HOOK(void, LSM_RET_VOID, policy_kptr_put, union lsm_policy_kptr *policy)
+LSM_HOOK(int, -EOPNOTSUPP, bprm_enforce_policy_kptr, struct linux_binprm *bprm,
+ union lsm_policy_kptr *policy, u32 flags)
#endif /* CONFIG_BPF_SYSCALL */
LSM_HOOK(int, 0, locked_down, enum lockdown_reason what)
diff --git a/include/linux/security.h b/include/linux/security.h
index 153e9043058f8..40dfa96b6a719 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -168,6 +168,23 @@ struct lsm_prop {
struct lsm_prop_bpf bpf;
};
+struct bpf_landlock_ruleset;
+
+struct lsm_policy_landlock {
+ struct bpf_landlock_ruleset *ruleset;
+};
+
+/*
+ * A reference to an LSM policy object, tagged by the LSM_ID_* value
+ * passed alongside: only the matching LSM's member is valid. The
+ * members are not guarded by the LSMs' CONFIG options: the callers
+ * are built independently of any individual LSM and a call for a
+ * missing LSM must fail at runtime, not at build time.
+ */
+union lsm_policy_kptr {
+ struct lsm_policy_landlock landlock;
+};
+
extern const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1];
/* These functions are in security/commoncap.c */
@@ -2312,6 +2329,13 @@ extern int security_bpf_token_create(struct bpf_token *token, union bpf_attr *at
extern void security_bpf_token_free(struct bpf_token *token);
extern int security_bpf_token_cmd(const struct bpf_token *token, enum bpf_cmd cmd);
extern int security_bpf_token_capable(const struct bpf_token *token, int cap);
+extern int security_policy_kptr_from_fd(u64 lsmid, int fd,
+ union lsm_policy_kptr *policy);
+extern void security_policy_kptr_put(u64 lsmid, union lsm_policy_kptr *policy);
+extern int security_bprm_enforce_policy_kptr(u64 lsmid,
+ struct linux_binprm *bprm,
+ union lsm_policy_kptr *policy,
+ u32 flags);
#else
static inline int security_bpf(int cmd, union bpf_attr *attr,
unsigned int size, bool kernel)
@@ -2365,6 +2389,25 @@ static inline int security_bpf_token_capable(const struct bpf_token *token, int
{
return 0;
}
+
+static inline int security_policy_kptr_from_fd(u64 lsmid, int fd,
+ union lsm_policy_kptr *policy)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void security_policy_kptr_put(u64 lsmid,
+ union lsm_policy_kptr *policy)
+{
+}
+
+static inline int security_bprm_enforce_policy_kptr(u64 lsmid,
+ struct linux_binprm *bprm,
+ union lsm_policy_kptr *policy,
+ u32 flags)
+{
+ return -EOPNOTSUPP;
+}
#endif /* CONFIG_SECURITY */
#endif /* CONFIG_BPF_SYSCALL */
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 3983b4ce73c81..67aa902f12570 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -14,8 +14,10 @@
#include <net/bpf_sk_storage.h>
#include <linux/bpf_local_storage.h>
#include <linux/btf_ids.h>
+#include <linux/cfi.h>
#include <linux/ima.h>
#include <linux/bpf-cgroup.h>
+#include <uapi/linux/lsm.h>
/* For every LSM hook that allows attachment of BPF programs, declare a nop
* function where a BPF program can be attached. Notably, we qualify each with
@@ -56,6 +58,9 @@ BTF_ID(func, bpf_lsm_xfrm_decode_session)
#endif
BTF_ID(func, bpf_lsm_ismaclabel)
BTF_ID(func, bpf_lsm_file_alloc_security)
+BTF_ID(func, bpf_lsm_policy_kptr_from_fd)
+BTF_ID(func, bpf_lsm_policy_kptr_put)
+BTF_ID(func, bpf_lsm_bprm_enforce_policy_kptr)
BTF_SET_END(bpf_lsm_disabled_hooks)
/* List of LSM hooks that should operate on 'current' cgroup regardless
@@ -470,3 +475,200 @@ int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
}
return 0;
}
+
+/* LSM policy kfuncs */
+
+/*
+ * Opaque handle for a Landlock ruleset. Only
+ * bpf_landlock_get_ruleset_from_fd() produces one, and only Landlock
+ * resolves it.
+ */
+struct bpf_landlock_ruleset {};
+
+/*
+ * The sleepable LSM hooks bpf_landlock_restrict_binprm() and
+ * bpf_landlock_put_ruleset() may be called from.
+ */
+BTF_SET_START(bpf_landlock_kfunc_hooks)
+BTF_ID(func, bpf_lsm_bprm_creds_for_exec)
+BTF_ID(func, bpf_lsm_bprm_creds_from_file)
+BTF_SET_END(bpf_landlock_kfunc_hooks)
+
+__bpf_kfunc_start_defs();
+
+/**
+ * bpf_landlock_get_ruleset_from_fd - Get a Landlock ruleset from a fd
+ * @fd: file descriptor of a Landlock ruleset, resolved in the file
+ * descriptor table of the task running the program
+ *
+ * Acquire a reference on the Landlock ruleset referred to by @fd, as
+ * created by landlock_create_ruleset(2) and populated with
+ * landlock_add_rule(2). Only syscall programs may call this kfunc:
+ * they run in the context of the task invoking them, where the
+ * ruleset fd is meaningful. The acquired ruleset can be handed to an
+ * enforcement program through a map kptr field. The reference must
+ * be released with bpf_landlock_put_ruleset().
+ *
+ * Return: A referenced ruleset handle, or NULL if @fd is not a
+ * readable Landlock ruleset fd or the Landlock LSM is not enabled.
+ */
+__bpf_kfunc struct bpf_landlock_ruleset *
+bpf_landlock_get_ruleset_from_fd(int fd)
+{
+ union lsm_policy_kptr policy;
+
+ if (security_policy_kptr_from_fd(LSM_ID_LANDLOCK, fd, &policy))
+ return NULL;
+ return policy.landlock.ruleset;
+}
+
+/**
+ * bpf_landlock_put_ruleset - Put a Landlock ruleset
+ * @ruleset: Landlock ruleset to put
+ *
+ * Release a reference acquired with bpf_landlock_get_ruleset_from_fd().
+ */
+__bpf_kfunc void bpf_landlock_put_ruleset(struct bpf_landlock_ruleset *ruleset)
+{
+ union lsm_policy_kptr policy = { .landlock.ruleset = ruleset };
+
+ security_policy_kptr_put(LSM_ID_LANDLOCK, &policy);
+}
+
+/**
+ * bpf_landlock_restrict_binprm - Enforce a Landlock ruleset on exec
+ * credentials
+ * @bprm: execution context providing the prepared credentials to
+ * restrict
+ * @ruleset: Landlock ruleset to enforce
+ * @flags: landlock_restrict_self(2) flags, except
+ * %LANDLOCK_RESTRICT_SELF_TSYNC
+ *
+ * Restrict the credentials prepared in @bprm with @ruleset, so that
+ * the executed task starts confined by it, following the same domain
+ * composition rules as landlock_restrict_self(2). The restriction is
+ * staged and only committed at the execution's point of no return: an
+ * execution that fails before that point is unaffected, and a later
+ * call on the same execution replaces a previously staged
+ * restriction. @ruleset is only borrowed: the caller keeps its
+ * reference.
+ *
+ * The landlock_restrict_self(2) flags apply with their usual
+ * semantics. Only %LANDLOCK_RESTRICT_SELF_TSYNC is rejected, as it
+ * targets the calling threads rather than the execution.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the Landlock LSM is not
+ * enabled, -EINVAL if @flags contains an unsupported flag, other
+ * negative values on failure as for landlock_restrict_self(2).
+ */
+__bpf_kfunc int bpf_landlock_restrict_binprm(struct linux_binprm *bprm,
+ struct bpf_landlock_ruleset *ruleset,
+ u32 flags)
+{
+ union lsm_policy_kptr policy = { .landlock.ruleset = ruleset };
+
+ return security_bprm_enforce_policy_kptr(LSM_ID_LANDLOCK, bprm,
+ &policy, flags);
+}
+
+/* Destructor for referenced bpf_landlock_ruleset kptrs. */
+__bpf_kfunc void bpf_landlock_put_ruleset_dtor(void *ruleset)
+{
+ union lsm_policy_kptr policy = { .landlock.ruleset = ruleset };
+
+ security_policy_kptr_put(LSM_ID_LANDLOCK, &policy);
+}
+CFI_NOSEAL(bpf_landlock_put_ruleset_dtor);
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(bpf_landlock_kfunc_ids)
+BTF_ID_FLAGS(func, bpf_landlock_get_ruleset_from_fd,
+ KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_landlock_put_ruleset, KF_RELEASE | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_landlock_restrict_binprm, KF_SLEEPABLE)
+BTF_KFUNCS_END(bpf_landlock_kfunc_ids)
+
+BTF_ID_LIST(bpf_landlock_dtor_ids)
+BTF_ID(struct, bpf_landlock_ruleset)
+BTF_ID(func, bpf_landlock_put_ruleset_dtor)
+
+BTF_ID_LIST_SINGLE(bpf_landlock_get_ruleset_ids, func,
+ bpf_landlock_get_ruleset_from_fd)
+BTF_ID_LIST_SINGLE(bpf_landlock_restrict_binprm_ids, func,
+ bpf_landlock_restrict_binprm)
+
+/*
+ * BPF_PROG_TYPE_LSM and BPF_PROG_TYPE_SYSCALL share their kfunc
+ * lookup buckets with other program types, so restricting the LSM
+ * policy kfuncs requires a filter. A ruleset fd is only meaningful
+ * in the fd table of the task that set the ruleset up, so
+ * bpf_landlock_get_ruleset_from_fd() is exclusive to syscall
+ * programs, which run in that task's context; an LSM program runs in
+ * the context of the task it mediates. Enforcement is exclusive to
+ * the sleepable bprm LSM hooks the policy operation is specified
+ * for, and the release kfunc is allowed wherever a reference can be
+ * held.
+ */
+static int bpf_landlock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+ if (!btf_id_set8_contains(&bpf_landlock_kfunc_ids, kfunc_id))
+ return 0;
+
+ switch (prog->type) {
+ case BPF_PROG_TYPE_SYSCALL:
+ if (kfunc_id == bpf_landlock_restrict_binprm_ids[0])
+ return -EACCES;
+ return 0;
+ case BPF_PROG_TYPE_LSM:
+ if (kfunc_id == bpf_landlock_get_ruleset_ids[0])
+ return -EACCES;
+
+ /*
+ * BPF_LSM_CGROUP programs run under classic RCU and
+ * cannot sleep.
+ */
+ if (prog->expected_attach_type == BPF_LSM_CGROUP)
+ return -EACCES;
+
+ if (!btf_id_set_contains(&bpf_landlock_kfunc_hooks,
+ prog->aux->attach_btf_id))
+ return -EACCES;
+
+ return 0;
+ default:
+ return -EACCES;
+ }
+}
+
+static const struct btf_kfunc_id_set bpf_landlock_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &bpf_landlock_kfunc_ids,
+ .filter = bpf_landlock_kfunc_filter,
+};
+
+static int __init bpf_lsm_policy_kfunc_init(void)
+{
+ const struct btf_id_dtor_kfunc bpf_landlock_dtors[] = {
+ {
+ .btf_id = bpf_landlock_dtor_ids[0],
+ .kfunc_btf_id = bpf_landlock_dtor_ids[1],
+ },
+ };
+ int ret;
+
+ ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM,
+ &bpf_landlock_kfunc_set);
+ if (ret)
+ return ret;
+
+ ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
+ &bpf_landlock_kfunc_set);
+ if (ret)
+ return ret;
+
+ return register_btf_id_dtor_kfuncs(bpf_landlock_dtors,
+ ARRAY_SIZE(bpf_landlock_dtors),
+ THIS_MODULE);
+}
+late_initcall(bpf_lsm_policy_kfunc_init);
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index ffa7646d99f3e..9ba28c06b5ac2 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -16,3 +16,5 @@ landlock-$(CONFIG_AUDIT) += \
id.o \
audit.o \
domain.o
+
+landlock-$(CONFIG_BPF_LSM) += bpf.o
diff --git a/security/landlock/bpf.c b/security/landlock/bpf.c
new file mode 100644
index 0000000000000..cf89062d35a70
--- /dev/null
+++ b/security/landlock/bpf.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - LSM policy kptr hooks
+ *
+ * Implementation of the LSM hooks backing the Landlock kfuncs
+ *
+ * Copyright © 2026 Justin Suess <utilityemal77@gmail.com>
+ */
+
+#include <linux/binfmts.h>
+#include <linux/cred.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/lsm_hooks.h>
+#include <linux/memcontrol.h>
+#include <linux/sched/mm.h>
+#include <uapi/linux/landlock.h>
+
+#include "bpf.h"
+#include "cred.h"
+#include "limits.h"
+#include "ruleset.h"
+#include "setup.h"
+
+static int hook_policy_kptr_from_fd(int fd, union lsm_policy_kptr *policy)
+{
+ struct landlock_ruleset *ruleset;
+
+ ruleset = landlock_get_ruleset_from_fd(fd, FMODE_CAN_READ);
+ if (IS_ERR(ruleset))
+ return PTR_ERR(ruleset);
+
+ policy->landlock.ruleset = (struct bpf_landlock_ruleset *)ruleset;
+ return 0;
+}
+
+static void hook_policy_kptr_put(union lsm_policy_kptr *policy)
+{
+ struct landlock_ruleset *ruleset =
+ (struct landlock_ruleset *)policy->landlock.ruleset;
+
+ /*
+ * May be called from a BPF object destructor that cannot sleep,
+ * whereas dropping the last ruleset reference frees it and may
+ * sleep: always defer the free.
+ */
+ landlock_put_ruleset_deferred(ruleset);
+}
+
+/* Charging scope for the BPF-driven domain allocations: root, i.e. nobody. */
+static struct mem_cgroup *get_bpf_memcg(void)
+{
+#ifdef CONFIG_MEMCG
+ return root_mem_cgroup;
+#else
+ return NULL;
+#endif /* CONFIG_MEMCG */
+}
+
+static int hook_bprm_enforce_policy_kptr(struct linux_binprm *bprm,
+ union lsm_policy_kptr *policy,
+ u32 flags)
+{
+ struct landlock_cred_security *bprm_llcred = landlock_cred(bprm->cred);
+ struct landlock_ruleset *ruleset =
+ (struct landlock_ruleset *)policy->landlock.ruleset;
+ struct landlock_restriction restriction;
+ struct mem_cgroup *old_memcg;
+ int err;
+
+ /*
+ * Same flags as landlock_restrict_self(2), except
+ * LANDLOCK_RESTRICT_SELF_TSYNC: the restriction targets the
+ * execution, not the calling threads.
+ */
+ if ((flags | LANDLOCK_MASK_RESTRICT_BINPRM) !=
+ LANDLOCK_MASK_RESTRICT_BINPRM)
+ return -EINVAL;
+
+ /*
+ * The domain confines the execution on behalf of the BPF
+ * program, not of the mediated task: do not charge the task's
+ * memcg for it.
+ */
+ old_memcg = set_active_memcg(get_bpf_memcg());
+ err = landlock_prepare_restriction(bprm_llcred, ruleset, flags,
+ &restriction);
+ set_active_memcg(old_memcg);
+ if (err)
+ return err;
+
+ /*
+ * Stages the restriction until the point of no return of the
+ * execution, replacing (and releasing) any previously staged
+ * one. Nothing is enforced yet: an execution that fails before
+ * committing its credentials drops the staged restriction in
+ * hook_cred_free() with no effect on the calling task.
+ */
+ landlock_put_ruleset(bprm_llcred->staged.domain);
+ bprm_llcred->staged = restriction;
+ return 0;
+}
+
+static void hook_bprm_committing_creds(const struct linux_binprm *bprm)
+{
+ struct landlock_cred_security *bprm_llcred = landlock_cred(bprm->cred);
+ struct landlock_restriction restriction;
+
+ if (!bprm_llcred->staged.domain)
+ return;
+
+ restriction = bprm_llcred->staged;
+ bprm_llcred->staged = (struct landlock_restriction){};
+
+ landlock_apply_restriction(bprm_llcred, &restriction);
+}
+
+static struct security_hook_list landlock_hooks[] __ro_after_init = {
+ LSM_HOOK_INIT(policy_kptr_from_fd, hook_policy_kptr_from_fd),
+ LSM_HOOK_INIT(policy_kptr_put, hook_policy_kptr_put),
+ LSM_HOOK_INIT(bprm_enforce_policy_kptr, hook_bprm_enforce_policy_kptr),
+ LSM_HOOK_INIT(bprm_committing_creds, hook_bprm_committing_creds),
+};
+
+__init void landlock_add_bpf_hooks(void)
+{
+ security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
+ &landlock_lsmid);
+}
diff --git a/security/landlock/bpf.h b/security/landlock/bpf.h
new file mode 100644
index 0000000000000..e57729e6a5644
--- /dev/null
+++ b/security/landlock/bpf.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - LSM policy kptr hooks
+ *
+ * Copyright © 2026 Justin Suess <utilityemal77@gmail.com>
+ */
+
+#ifndef _SECURITY_LANDLOCK_BPF_H
+#define _SECURITY_LANDLOCK_BPF_H
+
+#include <linux/init.h>
+
+#ifdef CONFIG_BPF_LSM
+__init void landlock_add_bpf_hooks(void);
+#else /* CONFIG_BPF_LSM */
+static inline void landlock_add_bpf_hooks(void)
+{
+}
+#endif /* CONFIG_BPF_LSM */
+
+#endif /* _SECURITY_LANDLOCK_BPF_H */
diff --git a/security/landlock/cred.c b/security/landlock/cred.c
index cc419de75cd6b..efbfd0c204753 100644
--- a/security/landlock/cred.c
+++ b/security/landlock/cred.c
@@ -8,14 +8,114 @@
*/
#include <linux/binfmts.h>
+#include <linux/bits.h>
#include <linux/cred.h>
+#include <linux/err.h>
+#include <linux/errno.h>
#include <linux/lsm_hooks.h>
+#include <uapi/linux/landlock.h>
#include "common.h"
#include "cred.h"
+#include "domain.h"
#include "ruleset.h"
#include "setup.h"
+/**
+ * landlock_prepare_restriction - Compute a credential restriction
+ *
+ * @llcred: Landlock credentials to restrict: provides the parent domain and
+ * the previous log configuration. Not modified.
+ * @ruleset: Ruleset to enforce, or NULL for a log-configuration-only change.
+ * @flags: landlock_restrict_self(2) flags. The caller is responsible for
+ * validating them against the set of flags it supports.
+ * @restriction: Computed restriction. On success, holds a reference on
+ * @restriction->domain (if any), which
+ * landlock_apply_restriction() transfers to the restricted
+ * credentials.
+ *
+ * The restriction builds on @llcred's current state: the caller must apply
+ * it to (or stage it for) these same credentials.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int landlock_prepare_restriction(
+ const struct landlock_cred_security *const llcred,
+ struct landlock_ruleset *const ruleset, const u32 flags,
+ struct landlock_restriction *const restriction)
+{
+#ifdef CONFIG_AUDIT
+ /* Translates "off" and "on" flags to booleans. */
+ const bool log_same_exec =
+ !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF);
+ const bool log_new_exec =
+ !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
+ const bool log_subdomains =
+ !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);
+ const bool prev_log_subdomains = !llcred->log_subdomains_off;
+#endif /* CONFIG_AUDIT */
+
+ *restriction = (struct landlock_restriction){};
+
+#ifdef CONFIG_AUDIT
+ restriction->log_subdomains_off = !prev_log_subdomains ||
+ !log_subdomains;
+#endif /* CONFIG_AUDIT */
+
+ if (!ruleset)
+ return 0;
+
+ restriction->domain = landlock_merge_ruleset(llcred->domain, ruleset);
+ if (IS_ERR(restriction->domain)) {
+ const int err = PTR_ERR(restriction->domain);
+
+ restriction->domain = NULL;
+ return err;
+ }
+
+#ifdef CONFIG_AUDIT
+ restriction->domain->hierarchy->log_same_exec = log_same_exec;
+ restriction->domain->hierarchy->log_new_exec = log_new_exec;
+ if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
+ restriction->domain->hierarchy->log_status =
+ LANDLOCK_LOG_DISABLED;
+#endif /* CONFIG_AUDIT */
+
+ return 0;
+}
+
+/**
+ * landlock_apply_restriction - Enforce a computed restriction on credentials
+ *
+ * @llcred: Landlock credentials to restrict, exclusively owned by the caller
+ * (prepared and not yet committed).
+ * @restriction: Restriction computed by landlock_prepare_restriction()
+ * against the same credential state; its domain reference is
+ * transferred to @llcred.
+ *
+ * Cannot fail, so that a caller may apply a restriction past its last point
+ * of failure, e.g. an exec point of no return.
+ */
+void landlock_apply_restriction(struct landlock_cred_security *const llcred,
+ struct landlock_restriction *const restriction)
+{
+#ifdef CONFIG_AUDIT
+ llcred->log_subdomains_off = restriction->log_subdomains_off;
+#endif /* CONFIG_AUDIT */
+
+ if (!restriction->domain)
+ return;
+
+ /* Replaces the old domain. */
+ landlock_put_ruleset(llcred->domain);
+ llcred->domain = restriction->domain;
+ restriction->domain = NULL;
+
+#ifdef CONFIG_AUDIT
+ llcred->domain_exec |= BIT(llcred->domain->num_layers - 1);
+#endif /* CONFIG_AUDIT */
+}
+
static void hook_cred_transfer(struct cred *const new,
const struct cred *const old)
{
@@ -24,6 +124,12 @@ static void hook_cred_transfer(struct cred *const new,
landlock_get_ruleset(old_llcred->domain);
*landlock_cred(new) = *old_llcred;
+
+#ifdef CONFIG_BPF_LSM
+ /* Only bprm credentials own a staged restriction: never copied. */
+ WARN_ON_ONCE(landlock_cred(new)->staged.domain);
+ landlock_cred(new)->staged = (struct landlock_restriction){};
+#endif /* CONFIG_BPF_LSM */
}
static int hook_cred_prepare(struct cred *const new,
@@ -35,10 +141,14 @@ static int hook_cred_prepare(struct cred *const new,
static void hook_cred_free(struct cred *const cred)
{
- struct landlock_ruleset *const dom = landlock_cred(cred)->domain;
+ struct landlock_cred_security *const llcred = landlock_cred(cred);
+
+ landlock_put_ruleset_deferred(llcred->domain);
- if (dom)
- landlock_put_ruleset_deferred(dom);
+#ifdef CONFIG_BPF_LSM
+ /* Releases a restriction staged for an aborted execution. */
+ landlock_put_ruleset_deferred(llcred->staged.domain);
+#endif /* CONFIG_BPF_LSM */
}
#ifdef CONFIG_AUDIT
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
index f287c56b5fd4b..74f8c9808dc4c 100644
--- a/security/landlock/cred.h
+++ b/security/landlock/cred.h
@@ -20,6 +20,31 @@
#include "ruleset.h"
#include "setup.h"
+/**
+ * struct landlock_restriction - Computed credential restriction
+ *
+ * The result of landlock_prepare_restriction(): the new state that
+ * enforcing a ruleset with a set of landlock_restrict_self(2) flags
+ * gives to a credential, decoupled from its application. It is
+ * enforced with landlock_apply_restriction(), either right away
+ * (landlock_restrict_self(2)) or after a staging period (restriction
+ * of an execution).
+ */
+struct landlock_restriction {
+ /**
+ * @domain: New domain to enforce, owning a reference. NULL if the
+ * restriction only carries a log configuration change.
+ */
+ struct landlock_ruleset *domain;
+#ifdef CONFIG_AUDIT
+ /**
+ * @log_subdomains_off: New value of the credentials'
+ * @landlock_cred_security.log_subdomains_off.
+ */
+ u8 log_subdomains_off : 1;
+#endif /* CONFIG_AUDIT */
+};
+
/**
* struct landlock_cred_security - Credential security blob
*
@@ -35,6 +60,18 @@ struct landlock_cred_security {
*/
struct landlock_ruleset *domain;
+#ifdef CONFIG_BPF_LSM
+ /**
+ * @staged: Restriction staged by the bprm_enforce_policy_kptr() hook,
+ * owning its domain reference and applied at the point of no return of
+ * the execution (bprm_committing_creds). Only ever set on the
+ * credentials prepared for an execution, between the staging and
+ * either the application or the release of the aborted credentials;
+ * committed task credentials never carry a staged restriction.
+ */
+ struct landlock_restriction staged;
+#endif /* CONFIG_BPF_LSM */
+
#ifdef CONFIG_AUDIT
/**
* @domain_exec: Bitmask identifying the domain layers that were enforced by
@@ -75,6 +112,12 @@ static inline void landlock_cred_copy(struct landlock_cred_security *dst,
*dst = *src;
landlock_get_ruleset(src->domain);
+
+#ifdef CONFIG_BPF_LSM
+ /* Only bprm credentials own a staged restriction: never copied. */
+ WARN_ON_ONCE(src->staged.domain);
+ dst->staged = (struct landlock_restriction){};
+#endif /* CONFIG_BPF_LSM */
}
static inline struct landlock_ruleset *landlock_get_current_domain(void)
@@ -153,6 +196,14 @@ landlock_get_applicable_subject(const struct cred *const cred,
return NULL;
}
+int landlock_prepare_restriction(
+ const struct landlock_cred_security *const llcred,
+ struct landlock_ruleset *const ruleset, const u32 flags,
+ struct landlock_restriction *const restriction);
+
+void landlock_apply_restriction(struct landlock_cred_security *const llcred,
+ struct landlock_restriction *const restriction);
+
__init void landlock_add_cred_hooks(void);
#endif /* _SECURITY_LANDLOCK_CRED_H */
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 08d5f2f6d321a..0bedfe650ea05 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -37,6 +37,10 @@
#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC
#define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
+/* Subset of the restrict-self flags applicable to an execution. */
+#define LANDLOCK_MASK_RESTRICT_BINPRM \
+ (LANDLOCK_MASK_RESTRICT_SELF & ~LANDLOCK_RESTRICT_SELF_TSYNC)
+
/* clang-format on */
#endif /* _SECURITY_LANDLOCK_LIMITS_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 4dd09ea22c844..176c626f9f105 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -520,7 +520,7 @@ static void free_ruleset_work(struct work_struct *const work)
free_ruleset(ruleset);
}
-/* Only called by hook_cred_free(). */
+/* For contexts that cannot sleep, e.g. hook_cred_free(). */
void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
{
if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 0437adf174287..b65d1c07e192d 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -225,6 +225,9 @@ struct landlock_ruleset *
landlock_merge_ruleset(struct landlock_ruleset *const parent,
struct landlock_ruleset *const ruleset);
+struct landlock_ruleset *landlock_get_ruleset_from_fd(const int fd,
+ const fmode_t mode);
+
const struct landlock_rule *
landlock_find_rule(const struct landlock_ruleset *const ruleset,
const struct landlock_id id);
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index 47dac1736f100..3b7e18edadfbc 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -11,6 +11,7 @@
#include <linux/lsm_hooks.h>
#include <uapi/linux/lsm.h>
+#include "bpf.h"
#include "common.h"
#include "cred.h"
#include "errata.h"
@@ -68,6 +69,7 @@ static int __init landlock_init(void)
landlock_add_task_hooks();
landlock_add_fs_hooks();
landlock_add_net_hooks();
+ landlock_add_bpf_hooks();
landlock_init_id();
landlock_initialized = true;
pr_info("Up and running.\n");
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 36b02892c62f5..899601af7c4e1 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -293,8 +293,8 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
* Returns an owned ruleset from a FD. It is thus needed to call
* landlock_put_ruleset() on the return value.
*/
-static struct landlock_ruleset *get_ruleset_from_fd(const int fd,
- const fmode_t mode)
+struct landlock_ruleset *landlock_get_ruleset_from_fd(const int fd,
+ const fmode_t mode)
{
CLASS(fd, ruleset_f)(fd);
struct landlock_ruleset *ruleset;
@@ -476,7 +476,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
return -EINVAL;
/* Gets and checks the ruleset. */
- ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
+ ruleset = landlock_get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
@@ -528,9 +528,8 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
{
struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL;
struct cred *new_cred;
- struct landlock_cred_security *new_llcred;
- bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
- prev_log_subdomains;
+ struct landlock_restriction restriction;
+ int err;
if (!is_initialized())
return -EOPNOTSUPP;
@@ -547,13 +546,6 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
LANDLOCK_MASK_RESTRICT_SELF)
return -EINVAL;
- /* Translates "off" flag to boolean. */
- log_same_exec = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF);
- /* Translates "on" flag to boolean. */
- log_new_exec = !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
- /* Translates "off" flag to boolean. */
- log_subdomains = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);
-
/*
* It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with
* -1 as ruleset_fd, optionally combined with
@@ -564,7 +556,8 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
(flags & ~LANDLOCK_RESTRICT_SELF_TSYNC) ==
LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) {
/* Gets and checks the ruleset. */
- ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ);
+ ruleset = landlock_get_ruleset_from_fd(ruleset_fd,
+ FMODE_CAN_READ);
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
}
@@ -574,53 +567,28 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
if (!new_cred)
return -ENOMEM;
- new_llcred = landlock_cred(new_cred);
-
-#ifdef CONFIG_AUDIT
- prev_log_subdomains = !new_llcred->log_subdomains_off;
- new_llcred->log_subdomains_off = !prev_log_subdomains ||
- !log_subdomains;
-#endif /* CONFIG_AUDIT */
-
/*
* The only case when a ruleset may not be set is if
* LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set (optionally with
* LANDLOCK_RESTRICT_SELF_TSYNC) and ruleset_fd is -1. We could
* optimize this case by not calling commit_creds() if this flag was
* already set, but it is not worth the complexity.
+ *
+ * There is no possible race condition while copying and manipulating
+ * the current credentials because they are dedicated per thread.
*/
- if (ruleset) {
- /*
- * There is no possible race condition while copying and
- * manipulating the current credentials because they are
- * dedicated per thread.
- */
- struct landlock_ruleset *const new_dom =
- landlock_merge_ruleset(new_llcred->domain, ruleset);
- if (IS_ERR(new_dom)) {
- abort_creds(new_cred);
- return PTR_ERR(new_dom);
- }
-
-#ifdef CONFIG_AUDIT
- new_dom->hierarchy->log_same_exec = log_same_exec;
- new_dom->hierarchy->log_new_exec = log_new_exec;
- if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
- 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;
-
-#ifdef CONFIG_AUDIT
- new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
-#endif /* CONFIG_AUDIT */
+ err = landlock_prepare_restriction(landlock_cred(new_cred), ruleset,
+ flags, &restriction);
+ if (err) {
+ abort_creds(new_cred);
+ return err;
}
+ landlock_apply_restriction(landlock_cred(new_cred), &restriction);
+
if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
- const int err = landlock_restrict_sibling_threads(
- current_cred(), new_cred);
+ err = landlock_restrict_sibling_threads(current_cred(),
+ new_cred);
if (err) {
abort_creds(new_cred);
return err;
diff --git a/security/security.c b/security/security.c
index 71aea8fdf0142..e9d8c9492bdb2 100644
--- a/security/security.c
+++ b/security/security.c
@@ -5441,6 +5441,112 @@ int security_bpf_token_capable(const struct bpf_token *token, int cap)
return call_int_hook(bpf_token_capable, token, cap);
}
+/**
+ * security_policy_kptr_from_fd() - Get an LSM policy object from a fd
+ * @lsmid: LSM_ID_* value of the LSM asked to interpret @fd
+ * @fd: file descriptor referring to a policy object, resolved in the
+ * calling task's file descriptor table
+ * @policy: receives the referenced policy object in the member of the
+ * LSM identified by @lsmid
+ *
+ * Ask the LSM identified by @lsmid to translate @fd into a reference
+ * counted policy object. The caller must not dereference the
+ * returned handle, must only hand it back to the same LSM, e.g.
+ * through security_bprm_enforce_policy_kptr(), and must release it
+ * with security_policy_kptr_put(). Only the hook implementation of
+ * the LSM identified by @lsmid is called. The hook is only called
+ * from a context that may sleep.
+ *
+ * An implementation must fill its own member of @policy with a
+ * reference that remains valid until it is released through the
+ * policy_kptr_put hook, and must not assume anything about @fd beyond
+ * what its own userspace API created it with.
+ *
+ * Return: Returns 0 if @policy holds a reference counted policy
+ * object, -EOPNOTSUPP if the LSM does not implement the hook, negative
+ * values on other failures.
+ */
+int security_policy_kptr_from_fd(u64 lsmid, int fd,
+ union lsm_policy_kptr *policy)
+{
+ struct lsm_static_call *scall;
+
+ lsm_for_each_hook(scall, policy_kptr_from_fd) {
+ if (scall->hl->lsmid->id != lsmid)
+ continue;
+ return scall->hl->hook.policy_kptr_from_fd(fd, policy);
+ }
+ return LSM_RET_DEFAULT(policy_kptr_from_fd);
+}
+
+/**
+ * security_policy_kptr_put() - Put a reference on an LSM policy object
+ * @lsmid: LSM_ID_* value of the LSM owning @policy
+ * @policy: the policy object, in the member of the LSM identified by
+ * @lsmid
+ *
+ * Release a reference previously obtained with
+ * security_policy_kptr_from_fd(). Only the hook implementation
+ * of the LSM identified by @lsmid is called, and @policy must have
+ * been obtained from that same LSM. An implementation must support
+ * being called from a context that cannot sleep: the release of BPF
+ * managed references may be driven from object destructors.
+ */
+void security_policy_kptr_put(u64 lsmid, union lsm_policy_kptr *policy)
+{
+ struct lsm_static_call *scall;
+
+ lsm_for_each_hook(scall, policy_kptr_put) {
+ if (scall->hl->lsmid->id != lsmid)
+ continue;
+ scall->hl->hook.policy_kptr_put(policy);
+ return;
+ }
+ /* A held reference implies the matching LSM implements the hook. */
+ WARN_ON_ONCE(1);
+}
+
+/**
+ * security_bprm_enforce_policy_kptr() - Enforce a policy on exec credentials
+ * @lsmid: LSM_ID_* value of the LSM owning @policy
+ * @bprm: execution context providing the prepared credentials to restrict
+ * @policy: the policy object to enforce, in the member of the LSM
+ * identified by @lsmid
+ * @flags: LSM-specific enforcement flags
+ *
+ * Ask the LSM identified by @lsmid to restrict the credentials
+ * prepared in @bprm with @policy, so that the executed task starts
+ * confined by it. @policy must have been obtained from the same LSM
+ * with security_policy_kptr_from_fd(); the hook borrows the
+ * reference and the caller remains responsible for releasing it.
+ * Only the hook implementation of the LSM identified by @lsmid is
+ * called: an LSM never receives a policy object meant for another LSM.
+ *
+ * This hook may only be called from an exec security context where
+ * @bprm's credentials are prepared but not yet committed, i.e. from a
+ * bprm_creds_for_exec() or bprm_creds_from_file() hook.
+ *
+ * How @policy composes with restrictions the credentials already
+ * carry is defined by the implementing LSM, as is the meaning of
+ * @flags, unsupported values of which it must reject with -EINVAL.
+ *
+ * Return: Returns 0 on success, -EOPNOTSUPP if the LSM does not
+ * implement the hook, negative values on other failures.
+ */
+int security_bprm_enforce_policy_kptr(u64 lsmid, struct linux_binprm *bprm,
+ union lsm_policy_kptr *policy, u32 flags)
+{
+ struct lsm_static_call *scall;
+
+ lsm_for_each_hook(scall, bprm_enforce_policy_kptr) {
+ if (scall->hl->lsmid->id != lsmid)
+ continue;
+ return scall->hl->hook.bprm_enforce_policy_kptr(bprm, policy,
+ flags);
+ }
+ return LSM_RET_DEFAULT(bprm_enforce_policy_kptr);
+}
+
/**
* security_bpf_map_free() - Free a bpf map's LSM blob
* @map: bpf map
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index ea7044f30adc3..2fa7344974612 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -120,6 +120,7 @@ CONFIG_SAMPLES=y
CONFIG_SAMPLE_LIVEPATCH=m
CONFIG_SECURITY=y
CONFIG_SECURITYFS=y
+CONFIG_SECURITY_LANDLOCK=y
CONFIG_SYN_COOKIES=y
CONFIG_TEST_BPF=m
CONFIG_UDMABUF=y
diff --git a/tools/testing/selftests/bpf/config.x86_64 b/tools/testing/selftests/bpf/config.x86_64
index 523e0d29bbd43..2c4d857f69f54 100644
--- a/tools/testing/selftests/bpf/config.x86_64
+++ b/tools/testing/selftests/bpf/config.x86_64
@@ -125,7 +125,7 @@ CONFIG_LEGACY_VSYSCALL_NONE=y
CONFIG_LOG_BUF_SHIFT=21
CONFIG_LOG_CPU_MAX_BUF_SHIFT=0
CONFIG_LOGO=y
-CONFIG_LSM="selinux,bpf,integrity"
+CONFIG_LSM="landlock,selinux,bpf,integrity"
CONFIG_MAC_PARTITION=y
CONFIG_MAGIC_SYSRQ=y
CONFIG_MCORE2=y
diff --git a/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c
new file mode 100644
index 0000000000000..c91929f98e88e
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright © 2026 Justin Suess <utilityemal77@gmail.com> */
+
+#include <test_progs.h>
+#include <errno.h>
+#include <linux/landlock.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "lsm_policy_kfuncs_success.skel.h"
+#include "lsm_policy_kfuncs_failure.skel.h"
+
+/* Fallbacks for old system headers. */
+#ifndef LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
+#define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (1U << 1)
+#endif
+#ifndef LANDLOCK_RESTRICT_SELF_TSYNC
+#define LANDLOCK_RESTRICT_SELF_TSYNC (1U << 3)
+#endif
+
+static int create_ruleset(void)
+{
+ const struct landlock_ruleset_attr attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_WRITE_FILE,
+ };
+
+ return syscall(__NR_landlock_create_ruleset, &attr, sizeof(attr), 0);
+}
+
+static void reset_prog_state(struct lsm_policy_kfuncs_success *skel)
+{
+ skel->bss->called = false;
+ skel->bss->no_ruleset = false;
+ skel->bss->restrict_err = -1;
+ skel->bss->restrict2_err = -1;
+ skel->bss->kfunc_flags = 0;
+ skel->bss->double_call = false;
+}
+
+/*
+ * Runs the syscall program that acquires the ruleset from
+ * @ruleset_fd, in the runner's fd table, and parks it in the map kptr
+ * slot for the LSM program.
+ */
+static int load_ruleset_into_map(struct lsm_policy_kfuncs_success *skel)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ int err;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.load_ruleset),
+ &opts);
+ if (!ASSERT_OK(err, "load_ruleset_run"))
+ return -1;
+ if (!ASSERT_OK(opts.retval, "load_ruleset_retval"))
+ return -1;
+ ASSERT_TRUE(skel->bss->got_null_for_bad_fd, "bad_fd_null");
+ return 0;
+}
+
+/*
+ * Forks a child that execs "sh -c '<shell_cmd>'". The monitored pid
+ * is only known, and can only be published to the BPF program, once
+ * the child exists: the child waits on a pipe until the parent has
+ * updated it. Returns the child's exit status, or -1 on error.
+ */
+static int run_exec_child(struct lsm_policy_kfuncs_success *skel,
+ bool monitored, const char *shell_cmd)
+{
+ int pipe_fds[2], status;
+ char buf = 0;
+ pid_t pid;
+
+ if (!ASSERT_OK(pipe(pipe_fds), "pipe"))
+ return -1;
+
+ pid = fork();
+ if (!ASSERT_GE(pid, 0, "fork")) {
+ close(pipe_fds[0]);
+ close(pipe_fds[1]);
+ return -1;
+ }
+ if (pid == 0) {
+ char *argv[] = { "sh", "-c", (char *)shell_cmd, NULL };
+
+ close(pipe_fds[1]);
+ read(pipe_fds[0], &buf, 1);
+ close(pipe_fds[0]);
+ execv("/bin/sh", argv);
+ exit(127);
+ }
+ close(pipe_fds[0]);
+ skel->bss->monitored_pid = monitored ? pid : 0;
+ write(pipe_fds[1], &buf, 1);
+ close(pipe_fds[1]);
+
+ if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
+ return -1;
+ if (!ASSERT_TRUE(WIFEXITED(status), "child_exited"))
+ return -1;
+ return WEXITSTATUS(status);
+}
+
+/*
+ * Exit codes: 4 = unexpected write outcome, 0 = everything as
+ * expected.
+ */
+static void format_child_cmd(char *cmd, size_t len, bool expect_write_ok,
+ const char *tmp_path)
+{
+ if (expect_write_ok)
+ snprintf(cmd, len, "echo x > %s || exit 4; exit 0", tmp_path);
+ else
+ snprintf(cmd, len,
+ "if echo x > %s 2>/dev/null; then exit 4; fi; exit 0",
+ tmp_path);
+}
+
+static void test_restrict_binprm(void)
+{
+ struct lsm_policy_kfuncs_success *skel = NULL;
+ char tmp_path[] = "/tmp/lsm_policy_kfuncs_XXXXXX";
+ char cmd[256];
+ int ruleset_fd, tmp_fd, ret;
+
+ tmp_fd = mkstemp(tmp_path);
+ if (!ASSERT_GE(tmp_fd, 0, "mkstemp"))
+ return;
+ close(tmp_fd);
+
+ ruleset_fd = create_ruleset();
+ if (ruleset_fd < 0) {
+ if (errno == EOPNOTSUPP || errno == ENOSYS)
+ test__skip();
+ else
+ ASSERT_GE(ruleset_fd, 0, "landlock_create_ruleset");
+ goto out_unlink;
+ }
+
+ skel = lsm_policy_kfuncs_success__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+ goto out;
+ skel->bss->ruleset_fd = ruleset_fd;
+
+ if (!ASSERT_OK(lsm_policy_kfuncs_success__attach(skel), "skel_attach"))
+ goto out;
+
+ if (load_ruleset_into_map(skel))
+ goto out;
+
+ /* Control: an unmonitored execution may write to the tmp file. */
+ reset_prog_state(skel);
+ format_child_cmd(cmd, sizeof(cmd), true, tmp_path);
+ ret = run_exec_child(skel, false, cmd);
+ if (!ASSERT_EQ(ret, 0, "control_child_exit"))
+ goto out;
+ ASSERT_FALSE(skel->bss->called, "control_not_monitored");
+
+ /*
+ * A monitored execution starts landlocked: the ruleset handles
+ * LANDLOCK_ACCESS_FS_WRITE_FILE without any rule, so the write
+ * must fail.
+ */
+ reset_prog_state(skel);
+ format_child_cmd(cmd, sizeof(cmd), false, tmp_path);
+ ret = run_exec_child(skel, true, cmd);
+ if (!ASSERT_EQ(ret, 0, "restricted_child_exit"))
+ goto out;
+ ASSERT_TRUE(skel->bss->called, "lsm_prog_called");
+ ASSERT_FALSE(skel->bss->no_ruleset, "ruleset_in_map");
+ ASSERT_EQ(skel->bss->restrict_err, 0, "restrict_binprm");
+
+ /* The audit log flags of landlock_restrict_self(2) apply too. */
+ reset_prog_state(skel);
+ skel->bss->kfunc_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
+ format_child_cmd(cmd, sizeof(cmd), false, tmp_path);
+ ret = run_exec_child(skel, true, cmd);
+ if (!ASSERT_EQ(ret, 0, "log_flags_child_exit"))
+ goto out;
+ ASSERT_EQ(skel->bss->restrict_err, 0, "log_flags_restrict_binprm");
+
+ /*
+ * LANDLOCK_RESTRICT_SELF_TSYNC targets the calling threads, not
+ * an execution: the kfunc must reject it and the execution must
+ * stay unrestricted.
+ */
+ reset_prog_state(skel);
+ skel->bss->kfunc_flags = LANDLOCK_RESTRICT_SELF_TSYNC;
+ format_child_cmd(cmd, sizeof(cmd), true, tmp_path);
+ ret = run_exec_child(skel, true, cmd);
+ if (!ASSERT_EQ(ret, 0, "tsync_child_exit"))
+ goto out;
+ ASSERT_TRUE(skel->bss->called, "tsync_prog_called");
+ ASSERT_EQ(skel->bss->restrict_err, -EINVAL, "tsync_rejected");
+
+ /*
+ * A second call on the same execution replaces the staged
+ * domain (and releases the first one): the result is a single
+ * restriction, not an error.
+ */
+ reset_prog_state(skel);
+ skel->bss->double_call = true;
+ format_child_cmd(cmd, sizeof(cmd), false, tmp_path);
+ ret = run_exec_child(skel, true, cmd);
+ if (!ASSERT_EQ(ret, 0, "double_child_exit"))
+ goto out;
+ ASSERT_EQ(skel->bss->restrict_err, 0, "double_restrict_first");
+ ASSERT_EQ(skel->bss->restrict2_err, 0, "double_restrict_second");
+out:
+ lsm_policy_kfuncs_success__destroy(skel);
+ close(ruleset_fd);
+out_unlink:
+ unlink(tmp_path);
+}
+
+/*
+ * Checks that a staged restriction is discarded, and the staged
+ * domain released, when the execution fails after the bprm hook: the
+ * calling task must not end up landlocked.
+ */
+static void test_restrict_binprm_discard(void)
+{
+ struct lsm_policy_kfuncs_success *skel = NULL;
+ char tmp_path[] = "/tmp/lsm_policy_kfuncs_XXXXXX";
+ char garbage_path[] = "/tmp/lsm_policy_garbage_XXXXXX";
+ int ruleset_fd = -1, tmp_fd, garbage_fd, pipe_fds[2], status;
+ char buf = 0;
+ pid_t pid;
+
+ tmp_fd = mkstemp(tmp_path);
+ if (!ASSERT_GE(tmp_fd, 0, "mkstemp"))
+ return;
+ close(tmp_fd);
+
+ /*
+ * An executable file that no binfmt handler accepts: the exec
+ * fails with ENOEXEC after bprm_creds_for_exec() has run.
+ */
+ garbage_fd = mkstemp(garbage_path);
+ if (!ASSERT_GE(garbage_fd, 0, "mkstemp_garbage"))
+ goto out_unlink;
+ if (!ASSERT_EQ(write(garbage_fd, "junk\n", 5), 5, "write_garbage")) {
+ close(garbage_fd);
+ goto out_unlink;
+ }
+ if (!ASSERT_OK(fchmod(garbage_fd, 0700), "chmod_garbage")) {
+ close(garbage_fd);
+ goto out_unlink;
+ }
+ close(garbage_fd);
+
+ ruleset_fd = create_ruleset();
+ if (ruleset_fd < 0) {
+ if (errno == EOPNOTSUPP || errno == ENOSYS)
+ test__skip();
+ else
+ ASSERT_GE(ruleset_fd, 0, "landlock_create_ruleset");
+ goto out_unlink;
+ }
+
+ skel = lsm_policy_kfuncs_success__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+ goto out;
+ skel->bss->ruleset_fd = ruleset_fd;
+ reset_prog_state(skel);
+
+ if (!ASSERT_OK(lsm_policy_kfuncs_success__attach(skel), "skel_attach"))
+ goto out;
+
+ if (load_ruleset_into_map(skel))
+ goto out;
+
+ if (!ASSERT_OK(pipe(pipe_fds), "pipe"))
+ goto out;
+
+ pid = fork();
+ if (!ASSERT_GE(pid, 0, "fork"))
+ goto out;
+ if (pid == 0) {
+ char *argv[] = { "garbage", NULL };
+ int fd;
+
+ close(pipe_fds[1]);
+ read(pipe_fds[0], &buf, 1);
+ close(pipe_fds[0]);
+ execv(garbage_path, argv);
+ /*
+ * The failed execution must leave no trace: no
+ * Landlock domain, i.e. writing must still work
+ * (exit 6).
+ */
+ fd = open(tmp_path, O_WRONLY | O_TRUNC);
+ if (fd < 0)
+ exit(6);
+ close(fd);
+ exit(0);
+ }
+ close(pipe_fds[0]);
+ skel->bss->monitored_pid = pid;
+ write(pipe_fds[1], &buf, 1);
+ close(pipe_fds[1]);
+
+ if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
+ goto out;
+ if (!ASSERT_TRUE(WIFEXITED(status), "child_exited"))
+ goto out;
+ ASSERT_EQ(WEXITSTATUS(status), 0, "discard_child_exit");
+ ASSERT_TRUE(skel->bss->called, "lsm_prog_called");
+ ASSERT_EQ(skel->bss->restrict_err, 0, "restrict_binprm");
+out:
+ lsm_policy_kfuncs_success__destroy(skel);
+ if (ruleset_fd >= 0)
+ close(ruleset_fd);
+out_unlink:
+ unlink(garbage_path);
+ unlink(tmp_path);
+}
+
+void test_lsm_policy_kfuncs(void)
+{
+ if (test__start_subtest("restrict_binprm"))
+ test_restrict_binprm();
+ if (test__start_subtest("restrict_binprm_discard"))
+ test_restrict_binprm_discard();
+ RUN_TESTS(lsm_policy_kfuncs_failure);
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c
new file mode 100644
index 0000000000000..0335db5470400
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright © 2026 Justin Suess <utilityemal77@gmail.com> */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct bpf_landlock_ruleset;
+
+extern struct bpf_landlock_ruleset *
+bpf_landlock_get_ruleset_from_fd(int fd) __ksym;
+extern void
+bpf_landlock_put_ruleset(struct bpf_landlock_ruleset *ruleset) __ksym;
+extern int bpf_landlock_restrict_binprm(struct linux_binprm *bprm,
+ struct bpf_landlock_ruleset *ruleset,
+ u32 flags) __ksym;
+
+/*
+ * The LSM policy kfuncs are limited to LSM and syscall programs by
+ * the BPF-side kfunc filter: a tracing program calling one must fail
+ * verification.
+ */
+SEC("tp_btf/task_newtask")
+__failure __msg("calling kernel function bpf_landlock_get_ruleset_from_fd is not allowed")
+int BPF_PROG(tracing_prog, struct task_struct *task, u64 clone_flags)
+{
+ struct bpf_landlock_ruleset *ruleset;
+
+ ruleset = bpf_landlock_get_ruleset_from_fd(-1);
+ if (ruleset)
+ bpf_landlock_put_ruleset(ruleset);
+ return 0;
+}
+
+/*
+ * A ruleset fd is only meaningful in the fd table of the task that
+ * set the ruleset up: the acquire kfunc is exclusive to syscall
+ * programs and must be rejected in an LSM program, even on an
+ * allowed hook.
+ */
+SEC("lsm.s/bprm_creds_for_exec")
+__failure __msg("calling kernel function bpf_landlock_get_ruleset_from_fd is not allowed")
+int BPF_PROG(lsm_get, struct linux_binprm *bprm)
+{
+ struct bpf_landlock_ruleset *ruleset;
+
+ ruleset = bpf_landlock_get_ruleset_from_fd(-1);
+ if (ruleset)
+ bpf_landlock_put_ruleset(ruleset);
+ return 0;
+}
+
+/*
+ * Enforcement needs an execution to restrict: the enforcement kfunc
+ * is exclusive to the sleepable bprm LSM hooks and must be rejected
+ * in a syscall program.
+ */
+SEC("syscall")
+__failure __msg("calling kernel function bpf_landlock_restrict_binprm is not allowed")
+int syscall_restrict(void *ctx)
+{
+ return bpf_landlock_restrict_binprm(NULL, NULL, 0);
+}
+
+/*
+ * Any LSM attach point other than the sleepable bprm hooks must be
+ * rejected.
+ */
+SEC("lsm.s/file_open")
+__failure __msg("calling kernel function bpf_landlock_put_ruleset is not allowed")
+int BPF_PROG(wrong_hook, struct file *file)
+{
+ bpf_landlock_put_ruleset(NULL);
+ return 0;
+}
+
+/*
+ * The kfuncs may sleep: a non-sleepable program on an allowed hook
+ * must be rejected.
+ */
+SEC("lsm/bprm_creds_for_exec")
+__failure
+__msg("program must be sleepable to call sleepable kfunc bpf_landlock_put_ruleset")
+int BPF_PROG(nonsleepable_prog, struct linux_binprm *bprm)
+{
+ bpf_landlock_put_ruleset(NULL);
+ return 0;
+}
+
+/* An acquired ruleset reference must be released before returning. */
+SEC("syscall")
+__failure __msg("Unreleased reference")
+int leak_ruleset(void *ctx)
+{
+ bpf_landlock_get_ruleset_from_fd(-1);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c
new file mode 100644
index 0000000000000..2107206ae144d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright © 2026 Justin Suess <utilityemal77@gmail.com> */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+struct bpf_landlock_ruleset;
+
+extern struct bpf_landlock_ruleset *
+bpf_landlock_get_ruleset_from_fd(int fd) __ksym;
+extern void
+bpf_landlock_put_ruleset(struct bpf_landlock_ruleset *ruleset) __ksym;
+extern int bpf_landlock_restrict_binprm(struct linux_binprm *bprm,
+ struct bpf_landlock_ruleset *ruleset,
+ u32 flags) __ksym;
+
+struct ruleset_slot {
+ struct bpf_landlock_ruleset __kptr *ruleset;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct ruleset_slot);
+} ruleset_map SEC(".maps");
+
+int monitored_pid;
+int ruleset_fd;
+u32 kfunc_flags;
+bool double_call;
+bool got_null_for_bad_fd;
+bool no_ruleset;
+int restrict_err;
+int restrict2_err;
+bool called;
+
+/*
+ * Runs in the test runner's context through BPF_PROG_RUN:
+ * @ruleset_fd is resolved in the runner's fd table and the acquired
+ * ruleset is handed to the LSM program through the map kptr slot.
+ */
+SEC("syscall")
+int load_ruleset(void *ctx)
+{
+ struct bpf_landlock_ruleset *ruleset, *old;
+ struct ruleset_slot *slot;
+ int key = 0;
+
+ slot = bpf_map_lookup_elem(&ruleset_map, &key);
+ if (!slot)
+ return 1;
+
+ /* A fd that is not a Landlock ruleset must resolve to NULL. */
+ ruleset = bpf_landlock_get_ruleset_from_fd(-1);
+ if (!ruleset)
+ got_null_for_bad_fd = true;
+ else
+ bpf_landlock_put_ruleset(ruleset);
+
+ ruleset = bpf_landlock_get_ruleset_from_fd(ruleset_fd);
+ if (!ruleset)
+ return 2;
+
+ old = bpf_kptr_xchg(&slot->ruleset, ruleset);
+ if (old)
+ bpf_landlock_put_ruleset(old);
+ return 0;
+}
+
+SEC("lsm.s/bprm_creds_for_exec")
+int BPF_PROG(restrict_exec, struct linux_binprm *bprm)
+{
+ struct bpf_landlock_ruleset *ruleset, *old;
+ struct ruleset_slot *slot;
+ int key = 0;
+
+ if (monitored_pid != (bpf_get_current_pid_tgid() >> 32))
+ return 0;
+
+ called = true;
+
+ slot = bpf_map_lookup_elem(&ruleset_map, &key);
+ if (!slot)
+ return 0;
+
+ ruleset = bpf_kptr_xchg(&slot->ruleset, NULL);
+ if (!ruleset) {
+ no_ruleset = true;
+ return 0;
+ }
+
+ restrict_err = bpf_landlock_restrict_binprm(bprm, ruleset, kfunc_flags);
+ if (double_call)
+ /* Replaces the domain staged by the first call. */
+ restrict2_err = bpf_landlock_restrict_binprm(bprm, ruleset,
+ kfunc_flags);
+
+ /* Keep the ruleset for the next monitored execution. */
+ old = bpf_kptr_xchg(&slot->ruleset, ruleset);
+ if (old)
+ bpf_landlock_put_ruleset(old);
+ return 0;
+}
|
| 3/2 | 2026/08/01 11:07 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22947 output: 28 thoughts: 427 Thoughts: **Fuzzing Plan for Landlock BPF Kfuncs** Okay, so I've got this patch introducing BPF kfuncs for Landlock LSM, which is a pretty significant addition. My first thought is, "This needs fuzzing, and it needs fuzzing *thoroughly*." I see we're adding these new BPF kfuncs: `bpf_landlock_get_ruleset_from_fd`, `bpf_landlock_put_ruleset`, `bpf_landlock_restrict_binprm`, and `bpf_landlock_put_ruleset_dtor`. Plus, there are these LSM hooks now: `security_policy_kptr_from_fd`, `security_policy_kptr_put`, and `security_bprm_enforce_policy_kptr`. These all need to be a priority for testing. The functional aspects of `landlock_prepare_restriction` and `landlock_apply_restriction` are key to understand with regard to interaction with the new kfuncs and hooks. Let's nail down the fuzzing targets. From a code perspective, I need to focus on those new kfuncs and hooks. I should probably include `landlock_prepare_restriction` and `landlock_apply_restriction` in the fuzzing scope. I need to make sure the build environment is set up correctly. The patch modifies `security/landlock/Makefile` so `landlock-$(CONFIG_BPF_LSM) += bpf.o`. So, `CONFIG_BPF_LSM` is definitely required. It also makes sense that `CONFIG_SECURITY_LANDLOCK` is needed, and, of course, `CONFIG_BPF_SYSCALL` for the BPF functionality itself. Got it. Now, let's verify these functions... |
| 4/2 | 2026/08/01 11:07 | tool | grepper |
0mArgs:{
"Expression": "bpf_landlock_get_ruleset_from_fd"
}Results: {
"Output": "Documentation/security/landlock.rst=150=exception of ``LANDLOCK_RESTRICT_SELF_TSYNC``.\n--\nDocumentation/security/landlock.rst-152-.. kernel-doc:: kernel/bpf/bpf_lsm.c\nDocumentation/security/landlock.rst:153: :identifiers: bpf_landlock_get_ruleset_from_fd\nDocumentation/security/landlock.rst-154- bpf_landlock_put_ruleset\n--\nkernel/bpf/bpf_lsm.c=457=int bpf_lsm_get_retval_range(const struct bpf_prog *prog,\n--\nkernel/bpf/bpf_lsm.c-482- * Opaque handle for a Landlock ruleset. Only\nkernel/bpf/bpf_lsm.c:483: * bpf_landlock_get_ruleset_from_fd() produces one, and only Landlock\nkernel/bpf/bpf_lsm.c-484- * resolves it.\n--\nkernel/bpf/bpf_lsm.c=497=__bpf_kfunc_start_defs();\n--\nkernel/bpf/bpf_lsm.c-499-/**\nkernel/bpf/bpf_lsm.c:500: * bpf_landlock_get_ruleset_from_fd - Get a Landlock ruleset from a fd\nkernel/bpf/bpf_lsm.c-501- * @fd: file descriptor of a Landlock ruleset, resolved in the file\n--\nkernel/bpf/bpf_lsm.c=515=__bpf_kfunc struct bpf_landlock_ruleset *\nkernel/bpf/bpf_lsm.c:516:bpf_landlock_get_ruleset_from_fd(int fd)\nkernel/bpf/bpf_lsm.c-517-{\n--\nkernel/bpf/bpf_lsm.c-528- *\nkernel/bpf/bpf_lsm.c:529: * Release a reference acquired with bpf_landlock_get_ruleset_from_fd().\nkernel/bpf/bpf_lsm.c-530- */\n--\nkernel/bpf/bpf_lsm.c=585=BTF_KFUNCS_START(bpf_landlock_kfunc_ids)\nkernel/bpf/bpf_lsm.c:586:BTF_ID_FLAGS(func, bpf_landlock_get_ruleset_from_fd,\nkernel/bpf/bpf_lsm.c-587-\t KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)\n--\nkernel/bpf/bpf_lsm.c=596=BTF_ID_LIST_SINGLE(bpf_landlock_get_ruleset_ids, func,\nkernel/bpf/bpf_lsm.c:597:\t\t bpf_landlock_get_ruleset_from_fd)\nkernel/bpf/bpf_lsm.c-598-BTF_ID_LIST_SINGLE(bpf_landlock_restrict_binprm_ids, func,\n--\nkernel/bpf/bpf_lsm.c-605- * in the fd table of the task that set the ruleset up, so\nkernel/bpf/bpf_lsm.c:606: * bpf_landlock_get_ruleset_from_fd() is exclusive to syscall\nkernel/bpf/bpf_lsm.c-607- * programs, which run in that task's context; an LSM program runs in\n--\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c=13=extern struct bpf_landlock_ruleset *\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c:14:bpf_landlock_get_ruleset_from_fd(int fd) __ksym;\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c-15-extern void\n--\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c=26=SEC(\"tp_btf/task_newtask\")\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c:27:__failure __msg(\"calling kernel function bpf_landlock_get_ruleset_from_fd is not allowed\")\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c-28-int BPF_PROG(tracing_prog, struct task_struct *task, u64 clone_flags)\n--\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c-31-\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c:32:\truleset = bpf_landlock_get_ruleset_from_fd(-1);\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c-33-\tif (ruleset)\n--\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c=44=SEC(\"lsm.s/bprm_creds_for_exec\")\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c:45:__failure __msg(\"calling kernel function bpf_landlock_get_ruleset_from_fd is not allowed\")\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c-46-int BPF_PROG(lsm_get, struct linux_binprm *bprm)\n--\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c-49-\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c:50:\truleset = bpf_landlock_get_ruleset_from_fd(-1);\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c-51-\tif (ruleset)\n--\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c=96=int leak_ruleset(void *ctx)\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c-97-{\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c:98:\tbpf_landlock_get_ruleset_from_fd(-1);\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c-99-\treturn 0;\n--\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c=12=extern struct bpf_landlock_ruleset *\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c:13:bpf_landlock_get_ruleset_from_fd(int fd) __ksym;\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c-14-extern void\n--\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c=47=int load_ruleset(void *ctx)\n--\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c-57-\t/* A fd that is not a Landlock ruleset must resolve to NULL. */\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c:58:\truleset = bpf_landlock_get_ruleset_from_fd(-1);\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c-59-\tif (!ruleset)\n--\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c-63-\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c:64:\truleset = bpf_landlock_get_ruleset_from_fd(ruleset_fd);\ntools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c-65-\tif (!ruleset)\n"
} |
| 5/2 | 2026/08/01 11:07 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25973 output: 19 thoughts: 319 Thoughts: **Analyzing the Landlock BPF Patch** Okay, so this patch introduces some pretty significant functional changes to Landlock, specifically with the addition of new BPF kfuncs. I see we have: * `bpf_landlock_get_ruleset_from_fd` * `bpf_landlock_put_ruleset` * `bpf_landlock_restrict_binprm` * `bpf_landlock_put_ruleset_dtor` These are key. That set strongly suggests they're aiming for deeper integration with BPF for Landlock policy management. I need to understand exactly how they intend to use these in relation to the existing Landlock system. Beyond the kfuncs, it seems we also have updates to the LSM hooks. The code is implementing hooks for both generic LSM (presumably the BPF based implementation) and the Landlock specific operations: * `security_policy_kptr_from_fd`, `security_policy_kptr_put`, `security_bprm_enforce_policy_kptr` * `hook_policy_kptr_from_fd`, `hook_policy_kptr_put`, `hook_bprm_enforce_policy_kptr`, `hook_bprm_committing_creds`, `landlock_prepare_restriction`, `landlock_apply_restriction` This confirms that BPF is meant to allow Landlock to be even more fine-grained in its enforcement, and possibly to permit for dynamic modification of policies. I need to pay close attention to the mechanics of `*_kptr` operations, and how they relate to the underlying policy structures. Alright, before I dive into the implementation details, let's get the configuration requirements sorted. From a quick glance, I know that: * `CONFIG_BPF_LSM` is essential for the kfuncs to be registered, which makes sense, since they are BPF. * `CONFIG_SECURITY_LANDLOCK` is obviously needed to enable Landlock itself. * `CONFIG_BPF_SYSCALL` is a requirement for BPF syscall support, so probably used to call the kfuncs. * `CONFIG_SECURITY` is required for LSM functionality in general. Now, I'm going to consult `security/landlock/Kconfig` to see if there are any other dependencies, like maybe some other Landlock features or helper code. I'm especially interested if there are any new configuration options for this BPF integration. |
| 6/2 | 2026/08/01 11:07 | tool | grepper |
0mArgs:{
"Expression": "config SECURITY_LANDLOCK"
}Results: {
"Output": "security/landlock/Kconfig-2-\nsecurity/landlock/Kconfig:3:config SECURITY_LANDLOCK\nsecurity/landlock/Kconfig-4-\tbool \"Landlock support\"\n--\nsecurity/landlock/Kconfig-23-\nsecurity/landlock/Kconfig:24:config SECURITY_LANDLOCK_KUNIT_TEST\nsecurity/landlock/Kconfig-25-\tbool \"KUnit tests for Landlock\" if !KUNIT_ALL_TESTS\n"
} |
| 7/2 | 2026/08/01 11:07 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26981 output: 33 thoughts: 16 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Analyzing the Landlock Kconfig Dependencies** Alright, time to dive in. I need to understand the dependencies for the `security/landlock/Kconfig` file. I'm thinking, this isn't just a casual check. I'm going to look beyond the obvious immediate dependencies and consider how Landlock's configuration interacts with the kernel's overall security posture. What features is it contingent on? Are there any subtle interactions with other security subsystems, maybe SELinux or AppArmor, that I need to be aware of to ensure proper co-existence and avoid any configuration conflicts? It's crucial I grasp the full scope of how Landlock is integrated and where its configuration knobs can potentially influence the rest of the security landscape. This Kconfig file holds the key to configuring Landlock correctly, and understanding the surrounding environment is paramount. This should give me a good handle on its role in the system. |
| 8/2 | 2026/08/01 11:07 | tool | read-file |
0mArgs:{
"File": "security/landlock/Kconfig",
"FirstLine": 1,
"LineCount": 20
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\t\n 3:\tconfig SECURITY_LANDLOCK\n 4:\t\tbool \"Landlock support\"\n 5:\t\tdepends on SECURITY\n 6:\t\tselect SECURITY_NETWORK\n 7:\t\tselect SECURITY_PATH\n 8:\t\thelp\n 9:\t\t Landlock is a sandboxing mechanism that enables processes to restrict\n 10:\t\t themselves (and their future children) by gradually enforcing\n 11:\t\t tailored access control policies. A Landlock security policy is a\n 12:\t\t set of access rights (e.g. open a file in read-only, make a\n 13:\t\t directory, etc.) tied to a file hierarchy. Such policy can be\n 14:\t\t configured and enforced by any processes for themselves using the\n 15:\t\t dedicated system calls: landlock_create_ruleset(),\n 16:\t\t landlock_add_rule(), and landlock_restrict_self().\n 17:\t\n 18:\t\t See Documentation/userspace-api/landlock.rst for further information.\n 19:\t\n 20:\t\t If you are unsure how to answer this question, answer N. Otherwise,\n"
} |
| 9/2 | 2026/08/01 11:07 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27548 output: 20 thoughts: 28 Thoughts: Okay, here's my interpretation of those thoughts, summarized from my perspective as someone well-versed in kernel security: **Thinking About Landlock and BPF LSM** Alright, so the core of what's on my mind right now is the `CONFIG_SECURITY_LANDLOCK` configuration option. That's the primary focus, the central piece of this puzzle. It's the one I need to get right. But of course, you can't just enable Landlock in isolation. I also know that `CONFIG_BPF_LSM` is a prerequisite, that's critical to make Landlock work as expected. So I need to make sure that `CONFIG_BPF_LSM` is enabled alongside `CONFIG_SECURITY_LANDLOCK`, these two components are closely related. |
| 10/2 | 2026/08/01 11:07 | tool | grepper |
0mArgs:{
"Expression": "config BPF_LSM"
}Results: {
"Output": "kernel/bpf/Kconfig=90=source \"kernel/bpf/preload/Kconfig\"\nkernel/bpf/Kconfig-91-\nkernel/bpf/Kconfig:92:config BPF_LSM\nkernel/bpf/Kconfig-93-\tbool \"Enable BPF LSM Instrumentation\"\n"
} |
| 11/2 | 2026/08/01 11:07 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27850 output: 34 thoughts: 21 Thoughts: Here's my thought process, summarized as if I were considering this myself: **Investigating BPF LSM Configuration** Okay, so I need to figure out the configuration related to BPF LSM. My first instinct is to dive into the core of the kernel configuration. Let's start by checking `kernel/bpf/Kconfig`. That's the most logical place to begin, since it seems to be where all things BPF-related are defined. I expect `BPF_LSM` should be defined or referenced in there if it's a configurable option for using BPF with LSM (Linux Security Modules). From here, I can examine any dependencies, defaults, and help text associated with it. This should give me a solid foundation for understanding how `BPF_LSM` is set up and used within the larger context of BPF and kernel security. |
| 12/2 | 2026/08/01 11:07 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/Kconfig",
"FirstLine": 90,
"LineCount": 20
}Results: {
"Contents": " 90:\tsource \"kernel/bpf/preload/Kconfig\"\n 91:\t\n 92:\tconfig BPF_LSM\n 93:\t\tbool \"Enable BPF LSM Instrumentation\"\n 94:\t\tdepends on BPF_EVENTS\n 95:\t\tdepends on BPF_SYSCALL\n 96:\t\tdepends on SECURITY\n 97:\t\tdepends on BPF_JIT\n 98:\t\thelp\n 99:\t\t Enables instrumentation of the security hooks with BPF programs for\n 100:\t\t implementing dynamic MAC and Audit Policies.\n 101:\t\n 102:\t\t If you are unsure how to answer this question, answer N.\n 103:\t\n 104:\tendmenu # \"BPF subsystem\"\n"
} |
| 13/2 | 2026/08/01 11:07 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28290 output: 34 thoughts: 226 Thoughts: **My Analysis of BPF LSM and Landlock Fuzzing Targets** Okay, so I'm diving into the dependency landscape here. It looks like the `CONFIG_BPF_LSM` config option relies on `BPF_EVENTS`, `BPF_SYSCALL`, `SECURITY`, and `BPF_JIT`. Good to know. Moving forward, I'm going to make sure that I enable `BPF_LSM`, `SECURITY_LANDLOCK`, `BPF_SYSCALL`, and `SECURITY` in my configurations. That's the baseline. Now, the real focus – identifying the key functions for fuzzing. After a quick look, the prime candidates seem to be: `bpf_landlock_get_ruleset_from_fd`, `bpf_landlock_put_ruleset`, `bpf_landlock_restrict_binprm`, and `bpf_landlock_put_ruleset_dtor`, along with `security_policy_kptr_from_fd`, `security_policy_kptr_put`, `security_bprm_enforce_policy_kptr`, `landlock_prepare_restriction`, and `landlock_apply_restriction`. These all seem like likely entry points for testing the BPF integration with Landlock and the security policies. Before I go further, I should also perform a quick sweep of the entire `security/landlock/bpf.c` file to ensure I haven't missed any other potential candidates. It's always good to be thorough and identify all relevant attack surfaces. |
| 14/2 | 2026/08/01 11:08 | tool | grepper |
0mArgs:{
"Expression": "^static.*hook_",
"PathPrefix": "security/landlock/bpf.c"
}Results: {
"Output": "security/landlock/bpf.c-25-\nsecurity/landlock/bpf.c:26:static int hook_policy_kptr_from_fd(int fd, union lsm_policy_kptr *policy)\nsecurity/landlock/bpf.c-27-{\n--\nsecurity/landlock/bpf.c-37-\nsecurity/landlock/bpf.c:38:static void hook_policy_kptr_put(union lsm_policy_kptr *policy)\nsecurity/landlock/bpf.c-39-{\n--\nsecurity/landlock/bpf.c=52=static struct mem_cgroup *get_bpf_memcg(void)\n--\nsecurity/landlock/bpf.c-60-\nsecurity/landlock/bpf.c:61:static int hook_bprm_enforce_policy_kptr(struct linux_binprm *bprm,\nsecurity/landlock/bpf.c-62-\t\t\t\t\t union lsm_policy_kptr *policy,\n--\nsecurity/landlock/bpf.c-104-\nsecurity/landlock/bpf.c:105:static void hook_bprm_committing_creds(const struct linux_binprm *bprm)\nsecurity/landlock/bpf.c-106-{\n--\nsecurity/landlock/bpf.c-118-\nsecurity/landlock/bpf.c:119:static struct security_hook_list landlock_hooks[] __ro_after_init = {\nsecurity/landlock/bpf.c-120-\tLSM_HOOK_INIT(policy_kptr_from_fd, hook_policy_kptr_from_fd),\n"
} |
| 15/2 | 2026/08/01 11:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29316 output: 36 thoughts: 312 Thoughts: **Analyzing Landlock BPF Function Usage** Okay, let's break down these Landlock BPF functions. I'm looking at `security/landlock/bpf.c` and immediately see these static functions: `hook_policy_kptr_from_fd`, `hook_policy_kptr_put`, `hook_bprm_enforce_policy_kptr`, and `hook_bprm_committing_creds`. The fact that they're static *could* mean they're inlined, or lack distinct symbols. However, I know from experience that they're passed as function pointers to `LSM_HOOK_INIT`, which means they *won't* be inlined. They'll have their own unique symbol addresses. Now, the prompt specifically favors non-static, non-inlined API entrypoints over internal helpers. So, I need to identify those entrypoints. Okay, here they are: `bpf_landlock_get_ruleset_from_fd`, `bpf_landlock_put_ruleset`, `bpf_landlock_restrict_binprm`, `bpf_landlock_put_ruleset_dtor`, `security_policy_kptr_from_fd`, `security_policy_kptr_put`, `security_bprm_enforce_policy_kptr`, `landlock_prepare_restriction`, and `landlock_apply_restriction`. I'll need to double check the latter two, `landlock_prepare_restriction` and `landlock_apply_restriction`, so I'll go over to `security/landlock/cred.c` to confirm where they are defined. |
| 16/2 | 2026/08/01 11:08 | tool | grepper |
0mArgs:{
"Expression": "^int landlock_prepare_restriction",
"PathPrefix": "security/landlock/cred.c"
}Results: {
"Output": "security/landlock/cred.c-41- */\nsecurity/landlock/cred.c:42:int landlock_prepare_restriction(\nsecurity/landlock/cred.c-43-\tconst struct landlock_cred_security *const llcred,\n"
} |
| 17/2 | 2026/08/01 11:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30102 output: 36 thoughts: 9 Thoughts: Okay, here's my summary, taking on the persona you've described: **Analyzing the `landlock_apply_restriction` Function** Alright, let's break this down. My initial thought with `landlock_apply_restriction` is to immediately understand its role in enforcing security boundaries, specifically concerning file system access. Given my expertise, I'm already anticipating that it's probably the core of Landlock's restriction mechanisms. I'm keen to dissect how it interacts with the kernel and the various Landlock rules. I'm looking for a clear description of the parameters, expecting something like a restriction type, perhaps a file descriptor or path, and some kind of access permissions bitmask or flag. I’m also going to be examining the potential error conditions and how they are handled, such as what happens if the restriction fails, or if the parameters are invalid. I'll expect to see documentation or comments outlining potential security implications and any caveats associated with its use, especially concerning race conditions or side-channel attacks. Understanding its interactions with other Landlock functions is also essential, considering it’s a key piece of the Landlock puzzle. |
| 18/2 | 2026/08/01 11:08 | tool | grepper |
0mArgs:{
"Expression": "^void landlock_apply_restriction",
"PathPrefix": "security/landlock/cred.c"
}Results: {
"Output": "security/landlock/cred.c=42=int landlock_prepare_restriction(\n--\nsecurity/landlock/cred.c-98- */\nsecurity/landlock/cred.c:99:void landlock_apply_restriction(struct landlock_cred_security *const llcred,\nsecurity/landlock/cred.c-100-\t\t\t\tstruct landlock_restriction *const restriction)\n"
} |
| 19/2 | 2026/08/01 11:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30494 output: 37 thoughts: 32 Thoughts: Okay, here's my take on those thoughts, framed as my own: **Analyzing Potential Security Implications** Alright, so we're looking at functions that *aren't* static. That immediately piques my interest from a security perspective. It means these functions are potentially exposed in a way that static ones aren't, making them interesting targets for analysis. My immediate next step is to drill down. I need to understand how these non-static functions are used and, specifically, if they're interacting with anything security-sensitive. Let's start by looking at `security/security.c`. I'm specifically going to hunt for `security_policy_kptr_from_fd`. That function name suggests it might be involved in deriving kernel pointers from file descriptors, which, if mishandled, could be a serious vulnerability. Understanding its role is now my primary focus. I want to see if this function introduces any pathways for unintended kernel memory access or privilege escalation. This investigation is now top priority. |
| 20/2 | 2026/08/01 11:08 | tool | grepper |
0mArgs:{
"Expression": "^int security_policy_kptr_from_fd",
"PathPrefix": "security/security.c"
}Results: {
"Output": "security/security.c=5439=int security_bpf_token_capable(const struct bpf_token *token, int cap)\n--\nsecurity/security.c-5468- */\nsecurity/security.c:5469:int security_policy_kptr_from_fd(u64 lsmid, int fd,\nsecurity/security.c-5470-\t\t\t\t union lsm_policy_kptr *policy)\n"
} |
| 21/2 | 2026/08/01 11:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30893 output: 35 thoughts: 11 Thoughts: Okay, here's my interpretation of those thoughts, crafted with the understanding that I'm speaking to a fellow expert: **Deep Dive on `security_policy_kptr_put`** Right, so we're looking at `security_policy_kptr_put`, and immediately I'm thinking about the kernel pointer management, specifically from a security perspective. This function is the cleanup counterpart to something like a `security_policy_kptr_get`, so my mind immediately jumps to resource management and preventing memory leaks or use-after-free scenarios, right? We're dealing with potentially sensitive kernel pointers here, likely associated with some security context or policy data. The design implications are pretty clear. The presence of `_put` implies a corresponding `_get`, and that the kernel pointer is protected by reference counting. This is the common pattern. The goal here is to make sure we don't leak this information. It also prevents dangling pointers, which could lead to information leaks or even exploitable vulnerabilities. So, what specific security policies is this function associated with? We're talking about a kernel-internal data structure so the policies have to be about making sure that the data stays within the system, without leakage or misuse. Now, I'm already starting to think about potential edge cases and race conditions. What happens if this `_put` is called prematurely, or in the wrong sequence? Is there proper synchronization to prevent concurrent access issues? Are we using any atomic operations to manage the reference count safely? I'm mentally tracing the possible code paths to identify potential vulnerabilities. Think about it - a subtle bug here could be a goldmine for an attacker! Beyond simple reference count issues, I'm also considering the implications of the data pointed to by the kernel pointer. What kind of information is contained within that data structure? Is it sensitive? Is it structured in a way that allows us to reason clearly about how to protect it? We're looking at policy data, so the answer is likely yes, in both instances. So, the implications of a vulnerability in this function are very serious. Finally, I'm pondering the context where this function is called. Is it called synchronously, from a single thread, or is it invoked within an interrupt context or a multi-threaded environment? The answer to that will greatly influence my assessment of the function's safety and resilience. The synchronization primitives used will be of great interest to me as well. And I bet there are audit points, too, somewhere down the line... let's dig in. |
| 22/2 | 2026/08/01 11:08 | tool | grepper |
0mArgs:{
"Expression": "^void security_policy_kptr_put",
"PathPrefix": "security/security.c"
}Results: {
"Output": "security/security.c=5469=int security_policy_kptr_from_fd(u64 lsmid, int fd,\n--\nsecurity/security.c-5494- */\nsecurity/security.c:5495:void security_policy_kptr_put(u64 lsmid, union lsm_policy_kptr *policy)\nsecurity/security.c-5496-{\n"
} |
| 23/2 | 2026/08/01 11:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31584 output: 39 thoughts: 15 Thoughts: Okay, here's my summary, presented as a first-person account reflecting on the thought process related to that function: **Analyzing `security_bprm_enforce_policy_kptr`** Right, let me think about this `security_bprm_enforce_policy_kptr` function. Its very name screams "security" and "policy enforcement" related to the `bprm` structure, which I know intimately. It’s definitely a crucial piece when we consider how a new process comes to life in the kernel, and how we keep it compliant with our overall security posture. The `kptr` suffix likely indicates this isn't the raw function but something used via kernel pointer, indicating a certain degree of indirection and potential for security checks or dynamic dispatch. My initial thought is to break down what it does within the context of process execution. It's almost certainly involved in validating the executable's legitimacy and the permissions required to actually *start* that process. The "policy" part suggests it integrates with, or is the entry point for, security modules like SELinux, AppArmor, or maybe even custom LSMs. I need to understand the data flow, how it receives the information about the executable, its context, and the requesting process. I expect it's given access to the `bprm` structure, but what specific data fields is it accessing within there? Is it checking the file path, the permissions on the executable file, any extended attributes (like security labels)? What about the calling process's credentials and capabilities? Does this function manage any state? For example, does it cache or pre-calculate information for subsequent checks? I'm now thinking about potential vulnerabilities. A subtle bug here could allow a malicious actor to bypass security restrictions, by exploiting a race condition, a faulty data check, or by providing crafted input. The impact could be devastating, leading to privilege escalation or arbitrary code execution. I need to be thinking about ways the function could be tricked, such as handling paths with symbolic links, or even memory corruption possibilities. I'm immediately considering how it fits into the broader picture of process execution. Is it called early in the `execve` path, or later on? Are there other security checks that happen before or after this? What's the error handling like? How does it signal a policy violation – does it return an error code, log a message, or trigger a kernel panic? Understanding the integration point with other subsystems is key. My focus has to be: data it uses, and how that data is checked, and how the call is managed in the context of the whole process creation. |
| 24/2 | 2026/08/01 11:08 | tool | grepper |
0mArgs:{
"Expression": "^int security_bprm_enforce_policy_kptr",
"PathPrefix": "security/security.c"
}Results: {
"Output": "security/security.c=5495=void security_policy_kptr_put(u64 lsmid, union lsm_policy_kptr *policy)\n--\nsecurity/security.c-5535- */\nsecurity/security.c:5536:int security_bprm_enforce_policy_kptr(u64 lsmid, struct linux_binprm *bprm,\nsecurity/security.c-5537-\t\t\t\t union lsm_policy_kptr *policy, u32 flags)\n"
} |
| 25/2 | 2026/08/01 11:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32331 output: 244 thoughts: 308 Thoughts: **My Assessment of the Landlock BPF Patch** Okay, so this Landlock BPF patch is interesting. From what I can gather, the core of this change introduces BPF kfuncs that interface with the Landlock LSM. This is a significant addition, as it enables BPF programs to actively enforce Landlock rulesets during process execution. This means we have *new* reachable code paths within both the BPF and Landlock subsystems. That's a clear signal for fuzzing. My primary focus, in terms of symbol coverage, needs to be on these kfuncs: `bpf_landlock_get_ruleset_from_fd`, `bpf_landlock_put_ruleset`, `bpf_landlock_restrict_binprm`, `bpf_landlock_put_ruleset_dtor`, `security_policy_kptr_from_fd`, `security_policy_kptr_put`, `security_bprm_enforce_policy_kptr`, `landlock_prepare_restriction`, and `landlock_apply_restriction`. These are the critical entry points and control flow paths related to the new functionality. Of course, the necessary kernel configurations are also important: `BPF_LSM`, `SECURITY_LANDLOCK`, `BPF_SYSCALL`, and `SECURITY`. However, given the dependencies of `BPF_LSM`, I need to double-check for other relevant configs to ensure comprehensive coverage. Ah, yes, `CONFIG_BPF_LSM` depends on `BPF_EVENTS`, `BPF_SYSCALL`, `SECURITY`, and `BPF_JIT`. Thus, `BPF_EVENTS`, `BPF_JIT` are very important, and need to be included in any fuzzing setup. I think I have a good handle on what needs to be covered and how to structure the fuzzing campaign. I'm going to call set-results. |
| 26/2 | 2026/08/01 11:08 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BPF_LSM",
"SECURITY_LANDLOCK",
"BPF_SYSCALL",
"SECURITY",
"BPF_EVENTS",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_landlock_get_ruleset_from_fd",
"bpf_landlock_put_ruleset",
"bpf_landlock_restrict_binprm",
"bpf_landlock_put_ruleset_dtor",
"security_policy_kptr_from_fd",
"security_policy_kptr_put",
"security_bprm_enforce_policy_kptr",
"landlock_prepare_restriction",
"landlock_apply_restriction"
],
"Reasoning": "The patch introduces BPF kfuncs for Landlock LSM, allowing BPF programs to enforce Landlock rulesets on executions. This is a functional change to the kernel that introduces new reachable code paths in the BPF and Landlock subsystems, which should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BPF_LSM",
"SECURITY_LANDLOCK",
"BPF_SYSCALL",
"SECURITY",
"BPF_EVENTS",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_landlock_get_ruleset_from_fd",
"bpf_landlock_put_ruleset",
"bpf_landlock_restrict_binprm",
"bpf_landlock_put_ruleset_dtor",
"security_policy_kptr_from_fd",
"security_policy_kptr_put",
"security_bprm_enforce_policy_kptr",
"landlock_prepare_restriction",
"landlock_apply_restriction"
],
"Reasoning": "The patch introduces BPF kfuncs for Landlock LSM, allowing BPF programs to enforce Landlock rulesets on executions. This is a functional change to the kernel that introduces new reachable code paths in the BPF and Landlock subsystems, which should be fuzzed.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|