[PATCH bpf-next v4 3/9] bpf: Verify signed loader metadata at load time

Paul Moore paul at paul-moore.com
Mon Jul 6 17:16:10 UTC 2026


On Mon, Jul 6, 2026 at 9:56 AM Daniel Borkmann <daniel at iogearbox.net> wrote:
>
> A signed gen_loader program carries the programs, maps and relocations
> it installs in a metadata array map. The loader instructions are covered
> by the PKCS#7 signature, but the metadata map is not: Today the loader
> compares the map contents from within BPF against a hash baked into its
> (signed) instructions, using the kernel-cached map hash. The kernel
> itself never actually attests that the metadata the loader installs is
> the metadata that was signed.
>
> This split is the core of the long-standing objection to the BPF signing
> scheme from the LSM / integrity side: the integrity check of a light
> skeleton only completes once the loader program runs, that is, after the
> security_bpf_prog_load() hook, so at admission time an LSM observes a
> program whose payload has not yet been verified. Auditing the chain
> link is also not a purely cryptographic operation: whoever signs or
> reviews an lskel has to disassemble the loader's preamble to convince
> themselves that the embedded hash check is present and correct [0][1].
> Two acceptable fixes were identified in those threads: Complete the
> integrity check before the admission hook fires, or add a second hook
> that collects the verification result after the loader ran [2]. Covering
> both the loader and its maps directly with the PKCS#7 signature is what
> Blaise Boscaccy's patchsets proposed in several forms. Let's implement
> the former, without growing the UAPI, and in particular as a single
> unified scheme where the signature spans the raw bytes rather than
> derived hashes.
>
> A signed loader binds its metadata map(s) through the existing fd_array,
> and an exclusive map is already bound to a program digest (excl_prog_hash).
> So when a signature is present, collect the exclusive maps from fd_array
> and append their frozen contents to the instructions before verification:
> The signature now covers insns || metadata_0 || metadata_1 || [...] in
> the fd_array order, and verification completes in bpf_check(), once the
> fd_array maps are resolved into used_maps, before the LSM admission hook
> and the rest of verification. A program is either BPF_SIG_UNSIGNED or
> BPF_SIG_VERIFIED, with nothing in between. While folding the fd_array
> maps, a non-exclusive map bound to a signed program is rejected, so every
> map folded into the signature is exclusive. A signed loader that fails
> to cover its metadata thus does not load, and BPF_SIG_VERIFIED always
> means the instructions and every exclusive map are authentic. The maps
> must be frozen so the hashed bytes cannot change before the loader runs;
> the map <-> program digest binding is enforced by the verifier for every
> used map. Binding maps through fd_array_cnt makes the verifier resolve
> and excl-check them (excl_prog_sha vs prog->digest) before it would
> otherwise compute the digest, so compute prog->digest up front in
> bpf_check(), over the unmodified instructions the signature covers, for
> a load that folds metadata.
>
> Unsigned programs are not affected by the signature path; for them the
> LSM admission hook merely moves below fd_array resolution, with minimal
> bounded work in between (see the previous commit). Note, signed loaders
> generated by older libbpf/bpftool versions need to be regenerated; some
> of the recent fixes we've had on the signed loader side require the
> latter already to close gaps.
>
> Finally, some remarks around the security_bpf_prog_load() placement
> given there was pushback, demanding either a new hook or a reuse of
> the existing security_bpf_prog() hook [3]: Just for loading a single
> BPF program it would then need to pass through four layers of LSM hooks:
>
>   1) security_bpf (cmd=PROG_LOAD): for gating various bpf subcmds
>   2) security_bpf_prog_load: historical admission hook (CAP/token,
>      prog_type, attach point), pre-verification
>   3) security_bpf_prog_verify_signature: newly asked admission hook,
>      same role as 2), plus the BPF signature verdict
>   4) security_bpf_prog: gate handing the prog fd back to userspace,
>      verification done & signature verified
>
> The use-cases of 2) and 3) conflate, thus BPF community prefers to just
> keep a total of 3 LSM hooks (as-is today): 3) makes 2) incoherent given
> they are the /same class/ of hook, that is, access-control admission on
> the load and split only by _what_ they can see. Worse, with the split,
> for a signed BPF program security_bpf_prog_load 2) admits a program whose
> signature has not been checked, so a policy gating at 2) is structurally
> unable to express "admit only verified" and every such policy is forced
> onto 3) *anyway*. In other words, one doesn't get two complementary hooks,
> but rather, one real admission hook aka 3) plus a now-degraded /legacy/
> hook 2) that can't answer the question operators actually want to ask.
>
> Reusing security_bpf_prog() 4) for admission is no alternative either:
> it fires only after the entire verifier (and JIT) pipeline ran, so
> denying a not-yet-verified program at that point burns exactly the
> work a denial is supposed to avoid, and by then the program has an id
> assigned and the kallsyms/perf/audit load events fired. Policies are
> free to also consume the signature verdict at 4), but admission control
> belongs into security_bpf_prog_load(). Hence the latter remains the only
> admission hook, merely moved past signature verification; with moving
> large allocations further down into the BPF verifier, there is now only
> minimal work between the old and new location.

See my comment on patch 0/9, but if we can combine this patch with the
vzalloc(progam) patch, I'll be happy to drop my NACK, and you can trim
the last half of this patch description ;)

I suspect that squashing the two patches should also quiet sashiko's
comments as it will be able to see the vzalloc() below the
security_bpf_prog_load() hook in this same patch.

> Signed-off-by: Daniel Borkmann <daniel at iogearbox.net>
> Nacked-by: Paul Moore <paul at paul-moore.com> # (don't move bpf_prog_load LSM hook)
> Link: https://lore.kernel.org/bpf/2f71d6c03698eb17d51f7247efde777627ee578a.camel@HansenPartnership.com [0]
> Link: https://lore.kernel.org/lkml/ecf0521ed302db672672ebfbc670ecfba36a6e00.camel@HansenPartnership.com [1]
> Link: https://lore.kernel.org/bpf/88703f00d5b7a779728451008626efa45e42db3d.camel@HansenPartnership.com [2]
> Link: https://lore.kernel.org/bpf/DJOFY21DYUI4.19WKQ3NPZ4H5R@gmail.com [3]
> ---
>  include/linux/bpf_verifier.h |   1 +
>  kernel/bpf/syscall.c         |  76 +--------------
>  kernel/bpf/verifier.c        | 175 +++++++++++++++++++++++++++++++++++
>  3 files changed, 178 insertions(+), 74 deletions(-)

-- 
paul-moore.com



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