Re: [PATCH v2 01/15] lsm: Add the LSM policy object lifetime hooks

From: Justin Suess

Date: Wed Sep 02 2026 - 09:45:31 EST


On Mon, Aug 31, 2026 at 10:58:43AM -0400, Justin Suess wrote:
> Add struct lsm_policy_object, the identity an LSM embeds in a policy
> object it shares with BPF programs, and the three hooks managing such
> an object's lifetime:
>
> policy_object_from_fd(fd, &object)
> policy_object_get(object)
> policy_object_put(object)
>
> The object records the owning LSM's LSM_ID_* value. The BPF kfuncs
> built on these hooks dispatch each call on an object to the one LSM
> matching its lsmid, which resolves the containing object with
> container_of(); the framework never interprets an object beyond its
> lsmid. The type field discriminates between the owning LSM's own
> policy object kinds and is private to it, with 0 reserved as "unset"
> so a zeroed, untagged object fails every type check.
>
> from_fd has no object to route by: the fd refers to a file set up
> through the owning LSM's own userspace interface, so the fd itself
> identifies its LSM. The framework offers the fd to every
> implementation in turn; an LSM declines a fd that is not one of its
> policy objects with -EOPNOTSUPP, and any other error is a definitive
> translation failure.
>
> The hooks back referenced BPF kptrs, which imposes the same lifetime
> contract on every implementation: from_fd returns a reference on a
> live object, get acquires with inc-not-zero semantics and fails with
> -ENOENT once the count dropped to zero, put may be called from
> contexts that cannot sleep (BPF drives it from map destructors), and
> the containing object is freed only after an RCU grace period, as
> programs load policy object kptrs from maps under RCU and may examine
> an object concurrently with its last put.
>
> The hooks are excluded from the "bpf" LSM's attachment points. The
> object-routed hooks are unreachable there, as LSM_ID_BPF policy
> objects cannot exist; for from_fd, whose walk visits every
> implementation, a BPF program cannot fill the object out parameter,
> so an attachment returning 0 would hand the caller an uninitialized
> pointer.
>
> Cc: Paul Moore <paul@xxxxxxxxxxxxxx>
> Cc: Casey Schaufler <casey@xxxxxxxxxxxxxxxx>
> Signed-off-by: Justin Suess <utilityemal77@xxxxxxxxx>
> ---
> include/linux/lsm_hook_defs.h | 4 ++++
> include/linux/security.h | 11 +++++++++++
> kernel/bpf/bpf_lsm.c | 3 +++
> 3 files changed, 18 insertions(+)
>
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 65c9609ec207..d7684407737a 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -452,6 +452,10 @@ 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_object_from_fd, int fd,
> + struct lsm_policy_object **object)
> +LSM_HOOK(int, -EOPNOTSUPP, policy_object_get, struct lsm_policy_object *object)
> +LSM_HOOK(void, LSM_RET_VOID, policy_object_put, struct lsm_policy_object *object)
> #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..5e423bea080e 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -168,6 +168,17 @@ struct lsm_prop {
> struct lsm_prop_bpf bpf;
> };
>
> +/*
> + * Identity of a policy object an LSM shares with BPF programs,
> + * embedded in the LSM's own object. @lsmid identifies the owning
> + * LSM; @type discriminates that LSM's policy object types, with 0
> + * reserved as "unset".
> + */
> +struct lsm_policy_object {
> + u64 lsmid;
> + u32 type;
> +};
For some clarity:

lsm_policy_object is just a handle to a refcounted lsm-private struct.

It can't be forged / created manually because it's a trusted kernel
pointer, so the only way to get it is through policy_object_from_fd.
And you cannot mutate any part of it from BPF.

But it's what enables the generic model. Calling it a "policy object"
may be short sighted though, that term is heavily overloaded in the LSM
space. I don't want to prescribe any restrictions on what an LSM can use
it for, after all some LSM have no notion of "policy" at all or have a
different meaning for it.

For SELinux, this "lsm_policy_object" could be an sid, for AppArmor an aa_label,
for Smack a label, etc. The intention was to allow writing programs that
don't care about any details of a particular LSM.

Justin
> +
> extern const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1];
>
> /* These functions are in security/commoncap.c */
> diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> index 1433809bb166..d06744d72e04 100644
> --- a/kernel/bpf/bpf_lsm.c
> +++ b/kernel/bpf/bpf_lsm.c
> @@ -56,6 +56,9 @@ BTF_ID(func, bpf_lsm_xfrm_decode_session)
> #endif
> BTF_ID(func, bpf_lsm_ismaclabel)
> BTF_ID(func, bpf_lsm_file_alloc_security)
> +BTF_ID(func, bpf_lsm_policy_object_from_fd)
> +BTF_ID(func, bpf_lsm_policy_object_get)
> +BTF_ID(func, bpf_lsm_policy_object_put)
> BTF_SET_END(bpf_lsm_disabled_hooks)
>
> /* List of LSM hooks that should operate on 'current' cgroup regardless
> --
> 2.55.0
>