[PATCH bpf-next 01/13] lsm: Add LSM hook security_policy_kptr_from_fd

Justin Suess utilityemal77 at gmail.com
Fri Jul 31 02:20:34 UTC 2026


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 at paul-moore.com>
Cc: Casey Schaufler <casey at schaufler-ca.com>
Signed-off-by: Justin Suess <utilityemal77 at 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




More information about the Linux-security-module-archive mailing list