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

Paul Moore <[email protected]>
Newsgroups org.kernel.vger.linux-security-module,org.kernel.vger.bpf
Message-ID <CAHC9VhRWG=Of4pKmo8KRjCYnVmfX5ZD7JPVa0D0N0Z=1ts4iQw@mail.gmail.com>
On Mon, Jul 6, 2026 at 9:56 AM Daniel Borkmann <[email protected]> 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 <[email protected]>
> Nacked-by: Paul Moore <[email protected]> # (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/[email protected] [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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.