* [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets
@ 2026-07-31 2:20 Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 01/13] lsm: Add LSM hook security_policy_kptr_from_fd Justin Suess
` (12 more replies)
0 siblings, 13 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess
Howdy,
This series lets BPF programs apply an existing, userspace-created
Landlock ruleset to a program during exec. The goal is unchanged from
the RFC [1]: BPF does not create, inspect, or mutate Landlock policy,
it only decides whether a ruleset that was already created and
validated through Landlock's existing userspace API should be applied,
based on runtime exec context.
Motivation
---
Deploying Landlock today requires the sandboxed program's
cooperation. A process can only restrict itself, so applying a
policy system-wide means wrapping every launch path with a helper
that calls landlock_restrict_self(2) before exec, and anything
spawned outside those wrappers runs unconfined. Supervising exec
from userspace instead (ptrace, seccomp user notifications) is racy
and slow. Meanwhile, the tools that do enforce system-wide policy
with BPF LSM programs today (container security agents such as
KubeArmor and Tetragon) end up reimplementing path-based access
control in BPF, fraught with horrors of path reconstruction, bind
mounts, rename races, and worse, which is exactly the problem Landlock
already solves in the kernel, with maintained and versioned semantics.
This series composes BPF and Landlock along their natural grain. The
intended deployment: a supervisor creates one ruleset per policy
class through the existing syscalls, a syscall BPF program parks
them in map kptr fields, and an LSM BPF program picks which (if
any) to apply to an execution based on its runtime context. The
policy semantics stay Landlock's; BPF contributes only the
programmable decision of when and to whom. Deciding inside the
exec path closes the race a userspace supervisor cannot: the
policy is in place before the first instruction of the new program
runs.
Addressing RFC feedback
---
The original thread at [1] received useful feedback from the BPF,
LSM and Landlock maintainers. I've hopefully been able to address
all of it here.
The interface is redesigned around the conclusions of the RFC thread.
While it's been over a week since sending [2] detailing some of the
redesign, I figure it's better to show the code.
Four main takeaways came out of the RFC feedback:
1) no kernel or BPF code may call directly into an individual LSM,
every LSM interface must go through the LSM framework.
2) LSMs interact with the kernel subsystems through LSM hooks. kfuncs
are just hooks from the LSM perspective.
3) A new map type is untenable; new maps will not be added for single
consumers. Especially because new map types become ABI.
4) We must implement a generic interface usable by all LSM, without
forgoing the type safety guarantees provided by BPF or making
a ioctl-like multiplexer.
This series does all, as proposed in [2]: the BPF subsystem owns
per-LSM, strongly typed kfuncs in kernel/bpf/bpf_lsm.c, and every
kfunc call passes through a generic LSM hook. Individual LSMs
implement ordinary LSM hooks and define no BPF interfaces at all.
The three generic hooks carry the policy reference across the LSM
boundary in union lsm_policy_kptr, tagged by the LSM_ID_* value passed
alongside. The shims dispatch only to the LSM matching that id
(following the setprocattr pattern) and return -EOPNOTSUPP when it is
compiled out or not enabled, so program loading stays independent of
the boot-time LSM configuration and kernel/bpf/ has no build-time
dependency on Landlock.
The LSM hooks
===
security_policy_kptr_from_fd(lsmid, fd, &policy)
security_policy_kptr_put(lsmid, &policy)
security_bprm_enforce_policy_kptr(lsmid, bprm, &policy, flags)
Landlock implements them as ordinary LSM hooks. The from_fd hook
validates a ruleset fd the same way as the Landlock syscalls; the
enforce hook stages a restriction on the credentials prepared for an
execution, computed by the same helpers as landlock_restrict_self(2);
and a bprm_committing_creds() hook applies it past the exec point of
no return, where nothing can fail. An execution either starts
confined by the domain or leaves the calling task untouched: a failed
execution releases the staged restriction when its aborted credentials
are freed, so no domain is created for an execution that never
happens.
The Landlock kfuncs
===
bpf_landlock_get_ruleset_from_fd(fd) KF_ACQUIRE | KF_RET_NULL
bpf_landlock_restrict_binprm(bprm, ruleset, flags)
bpf_landlock_put_ruleset(ruleset) KF_RELEASE
All sleepable-only. The acquire kfunc is exclusive to syscall
programs, which run in the context of the task whose fd table gives a
ruleset fd meaning; enforcement is exclusive to LSM programs attached
to bprm_creds_for_exec or bprm_creds_from_file; release works in both.
A kptr destructor is registered so acquired rulesets can be stashed in
ordinary map kptr fields, which is why the put hook must tolerate
non-sleepable contexts (the free is deferred).
The selftests exercise the deployment described above end to end:
a syscall program acquires the supervisor's ruleset and parks it in
a map kptr field; the LSM program takes it from the map and enforces
it on the executions it monitors. The landlock_restrict_self(2)
flags apply with their usual semantics, except
LANDLOCK_RESTRICT_SELF_TSYNC, which targets the calling threads
rather than the execution and is rejected with -EINVAL.
Changes since the RFC (quite big):
- every kfunc call now passes through a generic LSM hook; the kfuncs
moved from security/landlock to kernel/bpf/bpf_lsm.c and nothing
calls directly into Landlock
- the BPF-facing interface stays strongly typed per LSM; the tagged
union only travels across the LSM boundary. This is superior to
the void* model proposed in [2].
- BPF_MAP_TYPE_LANDLOCK_RULESET is nixed: the acquire kfunc plus an
ordinary map kptr field with a registered destructor replaces the
dedicated map type
- the whole restriction is now staged in the bprm credentials and
applied at bprm_committing_creds(), where nothing can fail, instead
of only staging the no_new_privs bit
- domain allocations on the kfunc path no longer charge the mediated
task's memcg: the enforce hook computes the restriction in a root
memcg charging scope
- LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS was split into its own series
[3] and is not carried here; this series only stages the
Landlock domain. Nothing about it was necessary for this series.
- no new UAPI: no map type, no new flag, no ABI bump (since this
series touches no Landlock ABI directly and the kfunc/syscall
side share common code paths).
Subsystem Summary
---
To LSM maintainers: This was the biggest change. There are three new
generic hooks and the union lsm_policy_kptr to abstract the lsm-specific
features declared next to lsm_prop in security.h with one member per
participating LSM. The hooks are ordinary, LSM-agnostic entry points
usable by any kernel-internal caller, with targeted dispatch by lsm_id
following the setprocattr precedent. It will return -EOPNOTSUPP when the
named LSM is compiled out or disabled. Nothing in security/ knows about
BPF, and BPF knows nothing about the enabled LSMs.
To Landlock maintainers: there is no ABI or behavior change for
existing users. The hook implementations reuse the syscall code
paths: ruleset fd validation is shared with the Landlock syscalls,
and the restriction is computed by the same helpers as
landlock_restrict_self(2), factored into prepare/apply steps so
future restrict_self features can hopefully work for both entry
points. What is new is the entry point: a restriction staged on the
bprm credentials and committed at bprm_committing_creds(), past the
point of no return. The memory accounting issue pointed out by
Mickaël is addressed as well. [4]
To BPF maintainers: BPF owns the three strongly typed kfuncs in
kernel/bpf/bpf_lsm.c, gated per program type (acquire in syscall
programs, enforcement in sleepable LSM programs on the two bprm
hooks), with a registered kptr destructor so rulesets can live in
ordinary map kptr fields. No new map type, no BPF internals touched.
kernel/bpf/ has no build-time dependency on Landlock or any other LSM
since it just calls the LSM hooks.
Patches 1-3 add the LSM hooks, 4-5 prepare Landlock (fd lookup
exposure, the restrict_self refactoring), 6 implements the hooks,
7-10 add the BPF-owned kfuncs, 11 selftests (end-to-end enforcement,
TSYNC rejection, staged-restriction replacement and discard on a
failed exec, plus six verifier rejection cases), 12-13 documentation.
Tested with a BPF CI run and manual run of the Landlock selftests.
Based on bpf-next/master, but applies cleanly to lsm/next.
[1] https://lore.kernel.org/linux-security-module/20260407200157.3874806-1-utilityemal77@gmail.com/
[2] https://lore.kernel.org/linux-security-module/al_TBtXYUNGLZHC2@zenbox/
[3] https://lore.kernel.org/linux-security-module/20260717220320.1030123-1-utilityemal77@gmail.com/
[4] https://lore.kernel.org/linux-security-module/20260701.ze4eph1eKo7a@digikod.net/
Justin Suess (13):
lsm: Add LSM hook security_policy_kptr_from_fd
lsm: Add LSM hook security_policy_kptr_put
lsm: Add LSM hook security_bprm_enforce_policy_kptr
landlock: Expose the ruleset fd lookup to the rest of Landlock
landlock: Factor the credential restriction out of
landlock_restrict_self()
landlock: Implement the LSM policy kptr hooks
bpf: Add the LSM policy kfunc infrastructure
bpf: Add the bpf_landlock_put_ruleset kfunc and ruleset destructor
bpf: Add the bpf_landlock_get_ruleset_from_fd kfunc
bpf: Add the bpf_landlock_restrict_binprm kfunc
selftests/bpf: Add tests for the Landlock policy kfuncs
landlock: Document the BPF kfunc interface
lsm: Document the LSM policy kptr hooks
Documentation/security/landlock.rst | 25 ++
Documentation/security/lsm-development.rst | 27 ++
include/linux/lsm_hook_defs.h | 5 +
include/linux/security.h | 43 +++
kernel/bpf/bpf_lsm.c | 202 +++++++++++
security/landlock/Makefile | 2 +
security/landlock/bpf.c | 130 +++++++
security/landlock/bpf.h | 21 ++
security/landlock/cred.c | 116 +++++-
security/landlock/cred.h | 51 +++
security/landlock/limits.h | 4 +
security/landlock/ruleset.c | 2 +-
security/landlock/ruleset.h | 3 +
security/landlock/setup.c | 2 +
security/landlock/syscalls.c | 70 +---
security/security.c | 106 ++++++
tools/testing/selftests/bpf/config | 1 +
tools/testing/selftests/bpf/config.x86_64 | 2 +-
.../bpf/prog_tests/lsm_policy_kfuncs.c | 329 ++++++++++++++++++
.../bpf/progs/lsm_policy_kfuncs_failure.c | 100 ++++++
.../bpf/progs/lsm_policy_kfuncs_success.c | 107 ++++++
21 files changed, 1292 insertions(+), 56 deletions(-)
create mode 100644 security/landlock/bpf.c
create mode 100644 security/landlock/bpf.h
create mode 100644 tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c
create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c
create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c
base-commit: f0e80dee4e32fd11e6ee1b714b75f681c7cafd3e
--
2.54.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH bpf-next 01/13] lsm: Add LSM hook security_policy_kptr_from_fd
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 02/13] lsm: Add LSM hook security_policy_kptr_put Justin Suess
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess,
Casey Schaufler
Add a generic LSM hook handing out a reference to an LSM policy
object on behalf of a kernel-internal caller:
security_policy_kptr_from_fd(lsmid, fd, &policy)
together with union lsm_policy_kptr, the tagged payload carrying such
a reference across the LSM boundary.
The union holds one per-LSM member; which member is valid is
determined by the lsmid the caller passes. The pointers the
members hold are the BTF-visible handles whose provenance the BPF
verifier guarantees, i.e. referenced kptrs, hence the hook naming:
the reference stays strongly typed from the BPF program through the
hook to the owning LSM, which resolves the handle to its internal
policy representation.
This hook backs BPF kfuncs: it lets an LSM hand out a reference to
one of its policy objects (identified by a file descriptor created
through the LSM's own userspace API) without exporting any symbol or
defining any BPF interface itself. The reference is released
through security_policy_kptr_put(), added by the next patch.
The hook uses targeted dispatch: the shim walks the hook list and only
calls the implementation registered by the LSM matching @lsmid,
following the security_getprocattr()/security_setprocattr() patterns
for generic hooks carrying LSM-specific payloads. When no active LSM
matches, the shim returns -EOPNOTSUPP: a kfunc call for an LSM that is
compiled out or not enabled fails at runtime rather than being hidden
from the BPF program at verification time. For the same reason the
union members are not guarded by the LSMs' CONFIG options: the
callers are built independently of any individual LSM.
The hook is excluded from the "bpf" LSM's attachment points. The
targeted dispatch would only reach a program attached there for calls
with LSM_ID_BPF, which no caller passes, so the attachment point would
be dead.
Cc: Paul Moore <paul@paul-moore.com>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Notes:
The hook naming choice of policy_kptr_from_fd and the other
hooks is up in the air for me. I decided to include the kptr
part in the name because it's relevant to the task being
performed: we are simply getting a pointer to a kernel policy
object from a file descriptor.
I'm open to better ideas for the name...
include/linux/lsm_hook_defs.h | 2 ++
include/linux/security.h | 25 +++++++++++++++++++++++
kernel/bpf/bpf_lsm.c | 1 +
security/security.c | 38 +++++++++++++++++++++++++++++++++++
4 files changed, 66 insertions(+)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 65c9609ec207..afd5b3f932a9 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -452,6 +452,8 @@ 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)
#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 153e9043058f..db807e61d310 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,8 @@ 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);
#else
static inline int security_bpf(int cmd, union bpf_attr *attr,
unsigned int size, bool kernel)
@@ -2365,6 +2384,12 @@ 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;
+}
#endif /* CONFIG_SECURITY */
#endif /* CONFIG_BPF_SYSCALL */
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 3983b4ce73c8..9fa514204fb5 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -56,6 +56,7 @@ 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_SET_END(bpf_lsm_disabled_hooks)
/* List of LSM hooks that should operate on 'current' cgroup regardless
diff --git a/security/security.c b/security/security.c
index 71aea8fdf014..14fd8b878cd0 100644
--- a/security/security.c
+++ b/security/security.c
@@ -5441,6 +5441,44 @@ 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_bpf_map_free() - Free a bpf map's LSM blob
* @map: bpf map
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 02/13] lsm: Add LSM hook security_policy_kptr_put
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 01/13] lsm: Add LSM hook security_policy_kptr_from_fd Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 03/13] lsm: Add LSM hook security_bprm_enforce_policy_kptr Justin Suess
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess,
Casey Schaufler
Add the generic LSM hook releasing a reference obtained through
security_policy_kptr_from_fd():
security_policy_kptr_put(lsmid, &policy)
The shim uses the same targeted dispatch by @lsmid as the get hook:
only the implementation registered by the matching LSM is called, and
it only ever reads its own member of union lsm_policy_kptr, so a
policy object only ever travels back to the LSM that produced it.
The hook is void: releasing a reference cannot fail. A reference can
only come from the matching LSM's policy_kptr_from_fd hook, so a
dispatch miss means a caller passed the wrong lsmid or an LSM
implemented the get hook without the put hook; the shim warns instead
of silently leaking the reference.
An implementation must support being called from a context that cannot
sleep: the release of BPF managed references may be driven from object
destructors.
Like the get hook, this hook is excluded from the "bpf" LSM's
attachment points, as the targeted dispatch makes an attachment there
unreachable.
Cc: Paul Moore <paul@paul-moore.com>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
include/linux/lsm_hook_defs.h | 1 +
include/linux/security.h | 6 ++++++
kernel/bpf/bpf_lsm.c | 1 +
security/security.c | 27 +++++++++++++++++++++++++++
4 files changed, 35 insertions(+)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index afd5b3f932a9..0800622e317f 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -454,6 +454,7 @@ 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)
#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 db807e61d310..5017a335918c 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -2331,6 +2331,7 @@ extern int security_bpf_token_cmd(const struct bpf_token *token, enum bpf_cmd cm
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);
#else
static inline int security_bpf(int cmd, union bpf_attr *attr,
unsigned int size, bool kernel)
@@ -2390,6 +2391,11 @@ static inline int security_policy_kptr_from_fd(u64 lsmid, int fd,
{
return -EOPNOTSUPP;
}
+
+static inline void security_policy_kptr_put(u64 lsmid,
+ union lsm_policy_kptr *policy)
+{
+}
#endif /* CONFIG_SECURITY */
#endif /* CONFIG_BPF_SYSCALL */
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 9fa514204fb5..e9059d43e92c 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -57,6 +57,7 @@ BTF_ID(func, bpf_lsm_xfrm_decode_session)
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_SET_END(bpf_lsm_disabled_hooks)
/* List of LSM hooks that should operate on 'current' cgroup regardless
diff --git a/security/security.c b/security/security.c
index 14fd8b878cd0..fd535bd00c24 100644
--- a/security/security.c
+++ b/security/security.c
@@ -5479,6 +5479,33 @@ int security_policy_kptr_from_fd(u64 lsmid, int fd,
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_bpf_map_free() - Free a bpf map's LSM blob
* @map: bpf map
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 03/13] lsm: Add LSM hook security_bprm_enforce_policy_kptr
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 01/13] lsm: Add LSM hook security_policy_kptr_from_fd Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 02/13] lsm: Add LSM hook security_policy_kptr_put Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 04/13] landlock: Expose the ruleset fd lookup to the rest of Landlock Justin Suess
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess,
Casey Schaufler
Add a generic LSM hook enforcing an LSM policy object on the
credentials prepared for an execution:
security_bprm_enforce_policy_kptr(lsmid, bprm, &policy, flags)
The policy object is obtained from the owning LSM through
security_policy_kptr_from_fd(), travels in that LSM's member of
union lsm_policy_kptr, and is handed back only to that same LSM: the
shim uses the same targeted dispatch by @lsmid as the policy kptr
lifetime hooks, and returns -EOPNOTSUPP when no active LSM matches.
This is the first policy operation backed by the BPF-owned LSM
kfuncs: it lets a sleepable LSM BPF program attached to
bprm_creds_for_exec() or bprm_creds_from_file() arrange for the
executed task to start confined by a policy created through the LSM's
own userspace API, e.g. a Landlock ruleset applied to a binprm. The
BPF-facing kfunc keeps the policy pointer strongly BTF-typed all the
way to the union member the implementing LSM reads back.
The hook contract is LSM agnostic: any LSM with a notion of a
per-task policy object can implement it, with its own semantics for
how the policy composes with restrictions the credentials already
carry and for the meaning of @flags, unsupported values of which it
must reject with -EINVAL. Implementations can rely on being called
only between the preparation and the commitment of the bprm's
credentials.
Like the policy kptr lifetime hooks, this hook is excluded from the
"bpf" LSM's attachment points, as the targeted dispatch makes an
attachment there unreachable.
Cc: Paul Moore <paul@paul-moore.com>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
include/linux/lsm_hook_defs.h | 2 ++
include/linux/security.h | 12 ++++++++++
kernel/bpf/bpf_lsm.c | 1 +
security/security.c | 41 +++++++++++++++++++++++++++++++++++
4 files changed, 56 insertions(+)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 0800622e317f..a70edbd7b761 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -455,6 +455,8 @@ 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 5017a335918c..40dfa96b6a71 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -2332,6 +2332,10 @@ 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)
@@ -2396,6 +2400,14 @@ 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 e9059d43e92c..d847a180489f 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -58,6 +58,7 @@ 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
diff --git a/security/security.c b/security/security.c
index fd535bd00c24..e9d8c9492bdb 100644
--- a/security/security.c
+++ b/security/security.c
@@ -5506,6 +5506,47 @@ void security_policy_kptr_put(u64 lsmid, union lsm_policy_kptr *policy)
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
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 04/13] landlock: Expose the ruleset fd lookup to the rest of Landlock
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
` (2 preceding siblings ...)
2026-07-31 2:20 ` [PATCH bpf-next 03/13] lsm: Add LSM hook security_bprm_enforce_policy_kptr Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 05/13] landlock: Factor the credential restriction out of landlock_restrict_self() Justin Suess
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess
Rename get_ruleset_from_fd() to landlock_get_ruleset_from_fd() and
give it external linkage within Landlock, declared in ruleset.h next
to the other ruleset lifetime helpers.
A following commit implements the LSM kfunc policy hooks, which need
to translate a ruleset fd into a landlock_ruleset reference from
outside syscalls.c. No behavioral change.
Cc: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
security/landlock/ruleset.h | 3 +++
security/landlock/syscalls.c | 9 +++++----
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 0437adf17428..b65d1c07e192 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/syscalls.c b/security/landlock/syscalls.c
index 36b02892c62f..9af2407274b2 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);
@@ -564,7 +564,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);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 05/13] landlock: Factor the credential restriction out of landlock_restrict_self()
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
` (3 preceding siblings ...)
2026-07-31 2:20 ` [PATCH bpf-next 04/13] landlock: Expose the ruleset fd lookup to the rest of Landlock Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 06/13] landlock: Implement the LSM policy kptr hooks Justin Suess
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess
Split the core of landlock_restrict_self() into two credential
helpers:
landlock_prepare_restriction() - translate the
landlock_restrict_self(2) flags, merge the ruleset with the
credentials' domain, and configure the new domain's audit log
state, producing a struct landlock_restriction: the complete
new state that the enforcement gives to a credential.
landlock_apply_restriction() - enforce a computed restriction on
credentials exclusively owned by the caller. This step cannot
fail, so a caller may run it past its last point of failure.
The syscall behaves exactly as before: prepare and apply run back to
back on the prepared credentials. The no_new_privs/CAP_SYS_ADMIN
precheck, the flag mask check, and the TSYNC handling are syscall
policy and stay in place.
The point of the split is that application is decoupled from
computation: a following commit restricts an execution from a BPF
kfunc by staging a prepared restriction in the binprm credentials and
applying it at the exec point of no return. With the flag
translation, domain merge, and audit log configuration in one shared
place.
Cc: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
security/landlock/cred.c | 100 +++++++++++++++++++++++++++++++++++
security/landlock/cred.h | 33 ++++++++++++
security/landlock/syscalls.c | 61 +++++----------------
3 files changed, 147 insertions(+), 47 deletions(-)
diff --git a/security/landlock/cred.c b/security/landlock/cred.c
index cc419de75cd6..13b3952c31c5 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)
{
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
index f287c56b5fd4..1d5039b46ce7 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
*
@@ -153,6 +178,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/syscalls.c b/security/landlock/syscalls.c
index 9af2407274b2..899601af7c4e 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -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
@@ -575,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;
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 06/13] landlock: Implement the LSM policy kptr hooks
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
` (4 preceding siblings ...)
2026-07-31 2:20 ` [PATCH bpf-next 05/13] landlock: Factor the credential restriction out of landlock_restrict_self() Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 07/13] bpf: Add the LSM policy kfunc infrastructure Justin Suess
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess
Implement the generic LSM hooks backing the BPF-owned Landlock
kfuncs. The new code is gated on CONFIG_BPF_LSM, the only
configuration where the kfuncs calling the hooks exist.
The policy objects travel in the Landlock member of union
lsm_policy_kptr as struct bpf_landlock_ruleset handles, the
BTF-visible type the verifier tracks; bpf.c is the only place
converting between the handle and struct landlock_ruleset.
- policy_kptr_from_fd() translates a ruleset fd, created with
landlock_create_ruleset(2) and populated with landlock_add_rule(2),
into an owned landlock_ruleset reference. The fd is validated the
same way as for the Landlock syscalls (ruleset file type,
FMODE_CAN_READ).
- policy_kptr_put() releases such a reference. The free is always
deferred as the hook may be reached from BPF object destructors
that cannot sleep.
- bprm_enforce_policy_kptr() shares the landlock_restrict_self(2)
path: it calls landlock_prepare_restriction() on the credentials
prepared in the binprm and stages the computed restriction in
their Landlock blob. Nothing is applied at this point, and there
is no flag logic of its own: a new landlock_restrict_self(2)
feature implemented in the shared helpers works here as well. The
only divergence is the flag mask: LANDLOCK_RESTRICT_SELF_TSYNC is
rejected with -EINVAL because the restriction targets the
execution, not the calling threads. The restriction is computed in
a root memcg charging scope: the domain confines the execution on
behalf of the BPF program, so its GFP_KERNEL_ACCOUNT allocations
are not charged to the mediated task.
The staged restriction is enforced by a bprm_committing_creds() hook
with the same landlock_apply_restriction() call as the syscall, past
the exec point of no return: a failed execution can no longer return
to the calling program at that point and the application cannot
fail, so an execution either starts confined by the domain or leaves
the calling task untouched. The applied layer is accounted in
domain_exec: for audit, the confined execution is the one that
enforced the domain, so the LOG_SAME_EXEC and LOG_NEW_EXEC flags
follow the executed program. There is no no_new_privs/CAP_SYS_ADMIN
precondition here: gating who may load a policy-applying BPF program
is the BPF attachment's privilege model.
The staged restriction's lifetime is fully covered: a second
bprm_enforce_policy_kptr() call on the same execution releases and
replaces the previously staged restriction. An execution failing
before the point of no return releases it through hook_cred_free()
when the prepared credentials are aborted, and the application
clears the staging field so committed task credentials never carry
a staged restriction. The credential copy helpers
(hook_cred_transfer(), landlock_cred_copy()) uphold that invariant by
never copying a staged restriction.
Cc: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
security/landlock/Makefile | 2 +
security/landlock/bpf.c | 130 ++++++++++++++++++++++++++++++++++++
security/landlock/bpf.h | 21 ++++++
security/landlock/cred.c | 16 ++++-
security/landlock/cred.h | 18 +++++
security/landlock/limits.h | 4 ++
security/landlock/ruleset.c | 2 +-
security/landlock/setup.c | 2 +
8 files changed, 191 insertions(+), 4 deletions(-)
create mode 100644 security/landlock/bpf.c
create mode 100644 security/landlock/bpf.h
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index ffa7646d99f3..9ba28c06b5ac 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 000000000000..cf89062d35a7
--- /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 000000000000..e57729e6a564
--- /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 13b3952c31c5..efbfd0c20475 100644
--- a/security/landlock/cred.c
+++ b/security/landlock/cred.c
@@ -124,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,
@@ -135,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 1d5039b46ce7..74f8c9808dc4 100644
--- a/security/landlock/cred.h
+++ b/security/landlock/cred.h
@@ -60,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
@@ -100,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)
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 08d5f2f6d321..0bedfe650ea0 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 4dd09ea22c84..176c626f9f10 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/setup.c b/security/landlock/setup.c
index 47dac1736f10..3b7e18edadfb 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");
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 07/13] bpf: Add the LSM policy kfunc infrastructure
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
` (5 preceding siblings ...)
2026-07-31 2:20 ` [PATCH bpf-next 06/13] landlock: Implement the LSM policy kptr hooks Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 08/13] bpf: Add the bpf_landlock_put_ruleset kfunc and ruleset destructor Justin Suess
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess
Prepare kernel/bpf/bpf_lsm.c to host kfuncs that let BPF programs
apply a userspace-created Landlock ruleset to an execution. The
kfuncs will be thin front ends to the generic LSM policy kptr hooks
(security_policy_kptr_from_fd(), security_policy_kptr_put(),
security_bprm_enforce_policy_kptr()), invoked with LSM_ID_LANDLOCK
so that the LSM framework's targeted dispatch only ever reaches
Landlock's hook implementations.
Because of the hook indirection, kernel/bpf/ has no build-time
dependency on Landlock: the kfuncs are registered whenever
CONFIG_BPF_LSM is enabled, and calling them while Landlock is
compiled out or not enabled in the LSM order fails at runtime with
-EOPNOTSUPP through the dispatch miss, keeping BPF program loading
independent of the boot-time LSM configuration.
Add the section hosting the kfuncs: struct bpf_landlock_ruleset, the
opaque BTF-typed handle for a Landlock ruleset that only Landlock
resolves; the kfunc id set, registered for both BPF_PROG_TYPE_LSM and
BPF_PROG_TYPE_SYSCALL; and the kfunc filter. The two program types
share their kfunc lookup buckets with other program types, so
restricting the kfuncs to them requires a filter. The set starts
empty and the filter has no per-kfunc rules yet; the following
patches add the kfuncs together with their filter rules.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Notes:
I decided to put the kfunc implementations in kernel/bpf to better
delineate the separation between the BPF facing interface and the
LSM framework. Since this file contains things like the BPF contexts
the kfuncs are allowed to be called from, it's important for BPF to
control that aspect.
I'm open to moving it if there is a better preferred location for
these under kernel/bpf/ other than kernel/bpf/bpf_lsm.c.
kernel/bpf/bpf_lsm.c | 50 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index d847a180489f..dd58c5bd0119 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -473,3 +473,53 @@ int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
}
return 0;
}
+
+/* LSM policy kfuncs */
+
+/*
+ * Opaque handle for a Landlock ruleset. Only Landlock resolves it.
+ */
+struct bpf_landlock_ruleset {};
+
+BTF_KFUNCS_START(bpf_landlock_kfunc_ids)
+BTF_KFUNCS_END(bpf_landlock_kfunc_ids)
+
+/*
+ * 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.
+ */
+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:
+ return 0;
+ case BPF_PROG_TYPE_LSM:
+ 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)
+{
+ int ret;
+
+ ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM,
+ &bpf_landlock_kfunc_set);
+ if (ret)
+ return ret;
+
+ return register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
+ &bpf_landlock_kfunc_set);
+}
+late_initcall(bpf_lsm_policy_kfunc_init);
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 08/13] bpf: Add the bpf_landlock_put_ruleset kfunc and ruleset destructor
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
` (6 preceding siblings ...)
2026-07-31 2:20 ` [PATCH bpf-next 07/13] bpf: Add the LSM policy kfunc infrastructure Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 09/13] bpf: Add the bpf_landlock_get_ruleset_from_fd kfunc Justin Suess
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess
Add the release kfunc for Landlock ruleset references:
bpf_landlock_put_ruleset(ruleset) KF_RELEASE
It is a thin front end to security_policy_kptr_put(), invoked with
LSM_ID_LANDLOCK; the handle travels in the Landlock member of union
lsm_policy_kptr, staying typed end to end.
A ruleset reference is meant to be handed over through a map kptr
field, so also register a destructor for struct bpf_landlock_ruleset:
map-held references are dropped on map teardown. The release path
may thus run from a context that cannot sleep, which the
policy_kptr_put() hook contract requires implementations to support.
The release kfunc is available to both program types the kfunc set is
registered for. For BPF_PROG_TYPE_LSM, the filter only accepts
programs attached to the bprm_creds_for_exec() or
bprm_creds_from_file() hooks, where the upcoming enforcement kfunc is
specified to operate, and rejects BPF_LSM_CGROUP programs, which run
under classic RCU; KF_SLEEPABLE limits the callers to sleepable
programs.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
kernel/bpf/bpf_lsm.c | 67 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 66 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index dd58c5bd0119..877dd0352607 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
@@ -481,9 +483,49 @@ int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
*/
struct bpf_landlock_ruleset {};
+/*
+ * The sleepable LSM hooks 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_put_ruleset - Put a Landlock ruleset
+ * @ruleset: Landlock ruleset to put
+ *
+ * Release an acquired reference on a Landlock ruleset.
+ */
+__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);
+}
+
+/* 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_put_ruleset, KF_RELEASE | 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)
+
/*
* BPF_PROG_TYPE_LSM and BPF_PROG_TYPE_SYSCALL share their kfunc
* lookup buckets with other program types, so restricting the LSM
@@ -498,6 +540,17 @@ static int bpf_landlock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
case BPF_PROG_TYPE_SYSCALL:
return 0;
case BPF_PROG_TYPE_LSM:
+ /*
+ * 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;
@@ -512,6 +565,12 @@ static const struct btf_kfunc_id_set bpf_landlock_kfunc_set = {
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,
@@ -519,7 +578,13 @@ static int __init bpf_lsm_policy_kfunc_init(void)
if (ret)
return ret;
- return register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
+ 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);
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 09/13] bpf: Add the bpf_landlock_get_ruleset_from_fd kfunc
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
` (7 preceding siblings ...)
2026-07-31 2:20 ` [PATCH bpf-next 08/13] bpf: Add the bpf_landlock_put_ruleset kfunc and ruleset destructor Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 10/13] bpf: Add the bpf_landlock_restrict_binprm kfunc Justin Suess
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess
Add the acquire kfunc for Landlock rulesets:
bpf_landlock_get_ruleset_from_fd(fd) KF_ACQUIRE|KF_RET_NULL
It acquires a reference on the Landlock ruleset referred to by @fd,
as created by landlock_create_ruleset(2) and populated with
landlock_add_rule(2), through security_policy_kptr_from_fd() invoked
with LSM_ID_LANDLOCK. When Landlock is compiled out or not enabled
in the LSM order, the call returns NULL.
A ruleset fd is only meaningful in the fd table of the process that
set the ruleset up, while an LSM program runs in the context of the
task it mediates, so the filter makes this kfunc exclusive to syscall
programs (BPF_PROG_TYPE_SYSCALL), which run in the context of the
task invoking them. The acquired ruleset is meant to be handed over
through a map kptr field to an enforcement program, and must be
released with bpf_landlock_put_ruleset().
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
kernel/bpf/bpf_lsm.c | 46 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 43 insertions(+), 3 deletions(-)
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 877dd0352607..9ff1c35fcd6e 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -479,7 +479,9 @@ int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
/* LSM policy kfuncs */
/*
- * Opaque handle for a Landlock ruleset. Only Landlock resolves it.
+ * Opaque handle for a Landlock ruleset. Only
+ * bpf_landlock_get_ruleset_from_fd() produces one, and only Landlock
+ * resolves it.
*/
struct bpf_landlock_ruleset {};
@@ -494,11 +496,37 @@ 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 an acquired reference on a Landlock ruleset.
+ * Release a reference acquired with bpf_landlock_get_ruleset_from_fd().
*/
__bpf_kfunc void bpf_landlock_put_ruleset(struct bpf_landlock_ruleset *ruleset)
{
@@ -519,6 +547,8 @@ 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_KFUNCS_END(bpf_landlock_kfunc_ids)
@@ -526,10 +556,17 @@ 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)
+
/*
* 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.
+ * 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.
*/
static int bpf_landlock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
{
@@ -540,6 +577,9 @@ static int bpf_landlock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
case BPF_PROG_TYPE_SYSCALL:
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.
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 10/13] bpf: Add the bpf_landlock_restrict_binprm kfunc
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
` (8 preceding siblings ...)
2026-07-31 2:20 ` [PATCH bpf-next 09/13] bpf: Add the bpf_landlock_get_ruleset_from_fd kfunc Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 11/13] selftests/bpf: Add tests for the Landlock policy kfuncs Justin Suess
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess
Add the enforcement kfunc for Landlock rulesets:
bpf_landlock_restrict_binprm(bprm, ruleset, flags)
It restricts the credentials prepared in @bprm with @ruleset, so that
the executed task starts confined by it, through
security_bprm_enforce_policy_kptr() invoked with LSM_ID_LANDLOCK.
When Landlock is compiled out or not enabled in the LSM order, the
call fails with -EOPNOTSUPP.
The kfunc keeps the BPF-facing interface strongly BTF-typed: with
kfunc arguments trusted by default, both the binprm (from the LSM
hook context) and the ruleset (an acquired reference) must be trusted
pointers whose provenance the verifier guarantees, not merely
type-matching ones.
The flags take the landlock_restrict_self(2) flags with their usual
semantics. Only LANDLOCK_RESTRICT_SELF_TSYNC is rejected (-EINVAL),
as it targets the calling threads rather than the execution.
The filter makes enforcement exclusive to the sleepable LSM programs
attached to the bprm_creds_for_exec() or bprm_creds_from_file()
hooks.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
kernel/bpf/bpf_lsm.c | 50 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 47 insertions(+), 3 deletions(-)
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 9ff1c35fcd6e..67aa902f1257 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -486,8 +486,8 @@ int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
struct bpf_landlock_ruleset {};
/*
- * The sleepable LSM hooks bpf_landlock_put_ruleset() may be called
- * from.
+ * 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)
@@ -535,6 +535,42 @@ __bpf_kfunc void bpf_landlock_put_ruleset(struct bpf_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)
{
@@ -550,6 +586,7 @@ 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)
@@ -558,6 +595,8 @@ 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
@@ -566,7 +605,10 @@ BTF_ID_LIST_SINGLE(bpf_landlock_get_ruleset_ids, func,
* 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.
+ * 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)
{
@@ -575,6 +617,8 @@ static int bpf_landlock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
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])
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 11/13] selftests/bpf: Add tests for the Landlock policy kfuncs
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
` (9 preceding siblings ...)
2026-07-31 2:20 ` [PATCH bpf-next 10/13] bpf: Add the bpf_landlock_restrict_binprm kfunc Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 12/13] landlock: Document the BPF kfunc interface Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 13/13] lsm: Document the LSM policy kptr hooks Justin Suess
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess
Exercise the LSM policy kfuncs end to end and cover the verifier-time
restrictions enforced by the BPF-side kfunc filter.
The success flow mirrors the intended deployment: a syscall program,
run by the test runner through BPF_PROG_RUN, acquires the ruleset
created by the runner from its fd (resolved in the runner's own fd
table) and parks it in a map kptr slot; a program attached to the
sleepable bprm_creds_for_exec() hook takes it from the map, enforces
it on the monitored execution with bpf_landlock_restrict_binprm()
with flags configured per scenario, and puts it back for the next
execution. The runner creates a real Landlock ruleset handling
LANDLOCK_ACCESS_FS_WRITE_FILE without any rule and checks that:
- a monitored child ends up landlocked (writing to a tmp file fails
while a control execution succeeds);
- the audit log flags are accepted;
- LANDLOCK_RESTRICT_SELF_TSYNC is rejected with -EINVAL and leaves
the execution unrestricted;
- a second bpf_landlock_restrict_binprm() call on the same execution
replaces the previously staged domain instead of failing or
stacking;
- a staged restriction is discarded when the execution fails after
the bprm hook: the child execs an ENOEXEC file with a restriction
staged, and after the failed execve(2) verifies that it is not
landlocked;
- a negative fd resolved through the acquire kfunc returns NULL.
The failure programs check that verification rejects:
- a tracing program calling the kfuncs (LSM and syscall programs
only),
- an LSM program calling the acquire kfunc (syscall programs only,
where the ruleset fd is meaningful),
- a syscall program calling the enforcement kfunc (sleepable bprm
LSM hooks only),
- an LSM program on a hook other than the sleepable bprm hooks,
- a non-sleepable LSM program on an allowed hook,
- a program leaking the acquired ruleset reference.
The test needs CONFIG_SECURITY_LANDLOCK and the landlock LSM enabled
in the test kernel; the runner skips if the Landlock syscalls are
unavailable.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
tools/testing/selftests/bpf/config | 1 +
tools/testing/selftests/bpf/config.x86_64 | 2 +-
.../bpf/prog_tests/lsm_policy_kfuncs.c | 329 ++++++++++++++++++
.../bpf/progs/lsm_policy_kfuncs_failure.c | 100 ++++++
.../bpf/progs/lsm_policy_kfuncs_success.c | 107 ++++++
5 files changed, 538 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c
create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c
create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index ea7044f30adc..2fa734497461 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 523e0d29bbd4..2c4d857f69f5 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 000000000000..c91929f98e88
--- /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 000000000000..0335db547040
--- /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 000000000000..2107206ae144
--- /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;
+}
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 12/13] landlock: Document the BPF kfunc interface
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
` (10 preceding siblings ...)
2026-07-31 2:20 ` [PATCH bpf-next 11/13] selftests/bpf: Add tests for the Landlock policy kfuncs Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 13/13] lsm: Document the LSM policy kptr hooks Justin Suess
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess
Describe the new BPF kfuncs for Landlock.
Cc: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Documentation/security/landlock.rst | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst
index c5186526e76f..01197c7580ec 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
=====
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH bpf-next 13/13] lsm: Document the LSM policy kptr hooks
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
` (11 preceding siblings ...)
2026-07-31 2:20 ` [PATCH bpf-next 12/13] landlock: Document the BPF kfunc interface Justin Suess
@ 2026-07-31 2:20 ` Justin Suess
12 siblings, 0 replies; 14+ messages in thread
From: Justin Suess @ 2026-07-31 2:20 UTC (permalink / raw)
To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
Cc: gnoack, jack, song, yonghong.song, martin.lau, m, bpf,
linux-security-module, linux-kernel, Justin Suess,
Casey Schaufler
Describe the split of responsibilities in lsm-development.rst: an LSM
exposes policy operations to any kernel-internal caller through
generic LSM hooks. The BPF subsystem owns the strongly typed kfuncs
built on top of them, and the providing LSM only implements ordinary
LSM hooks.
Cc: Paul Moore <paul@paul-moore.com>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Notes:
This attempts to clarify some of the conclusions in what an LSM
can and can't do from Paul and Casey's feedback and make it hard
documentation. Let me know if another place is more deserving of it.
Documentation/security/lsm-development.rst | 27 ++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/Documentation/security/lsm-development.rst b/Documentation/security/lsm-development.rst
index 5895e529da7f..fc3af206e795 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.
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-07-31 2:21 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 01/13] lsm: Add LSM hook security_policy_kptr_from_fd Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 02/13] lsm: Add LSM hook security_policy_kptr_put Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 03/13] lsm: Add LSM hook security_bprm_enforce_policy_kptr Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 04/13] landlock: Expose the ruleset fd lookup to the rest of Landlock Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 05/13] landlock: Factor the credential restriction out of landlock_restrict_self() Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 06/13] landlock: Implement the LSM policy kptr hooks Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 07/13] bpf: Add the LSM policy kfunc infrastructure Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 08/13] bpf: Add the bpf_landlock_put_ruleset kfunc and ruleset destructor Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 09/13] bpf: Add the bpf_landlock_get_ruleset_from_fd kfunc Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 10/13] bpf: Add the bpf_landlock_restrict_binprm kfunc Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 11/13] selftests/bpf: Add tests for the Landlock policy kfuncs Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 12/13] landlock: Document the BPF kfunc interface Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 13/13] lsm: Document the LSM policy kptr hooks Justin Suess
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox