BPF signing LSM hook change rejected
[LWN subscriber-only content]
Welcome to LWN.net
The following subscription-only content has been made available to you by an LWN subscriber. Thousands of subscribers depend on LWN for the best news from the Linux and free software communities. If you enjoy this article, please consider subscribing to LWN. Thank you for visiting LWN.net!
BPF lets users load programs into a running kernel. Even though BPF programs are checked by the verifier to ensure that they stay inside certain limits, some users would still like to ensure that only approved BPF programs are loaded. KP Singh's patches adding that capability to the kernel were accepted in version 6.18, but not everyone is satisfied with his implementation. Blaise Boscaccy, who has been working to get a version of BPF code signing with better auditability into the kernel for some time, posted a patch set on top of Singh's changes that alters the loading process to not invoke security module hooks until the entire loading process is complete. The discussion on the patch set is the continuation of a long-running disagreement over the interface for signed BPF programs.
One might hope that signing BPF programs would just be a matter of attaching a signature to the program, and then checking that signature. Alas, things are a bit more complicated. BPF uses "compile once — run everywhere" (CO-RE) relocations to let compiled programs run on multiple different kernel versions. Thus, the version of the BPF program on disk is not exactly the same as the version presented to the kernel for loading, which invalidates any signatures on the BPF binary.
Singh's patch set solves this problem by using a two-step process: first, user space loads a specialized BPF program, called a loader, that does not require relocations (and so can have its signature checked directly by the kernel). Then, the loader program verifies that the real program matches a hash stored in the loader. That hash covers the code of the real program (as well as some BPF maps containing configuration), so a correctly implemented loader won't load a program that has been tampered with. This design has the benefit of presenting a relatively minimal user-space interface, but moving part of the program-verification process out of the kernel proper and into BPF code is a potential downside.
Boscaccy's patch set adds support for verifying a BPF program's initial maps
(including the instructions of the real program)
alongside the loader program. That would simplify the loader program, because it
no longer needs to check a hash of the maps, but it also gives
Linux security modules (LSMs) more information about the loaded program.
Specifically, it lets LSMs see whether the loader program actually completes
successfully.
In his cover letter, Boscaccy says: "This approach addresses concerns from
users who require strict audit
trails and verification guarantees, especially in security-sensitive
environments.
"
This is not a new proposal. Boscaccy spoke at this year's Linux, Filesystem, Memory-Management, and BPF Summit about his attempts to put together a solution for BPF program signing that works for that use case. Since then, he has posted a number of patch sets implementing the same basic idea in different ways, none of which have been merged. BPF signing in general has been a topic of active discussion and development since Alexei Starovoitov (a maintainer of the BPF subsystem) introduced the concept of "light skeletons" in 2021 as an initial step toward this kind of program loader.
Paul Moore, the LSM maintainer, wrote in support of Boscaccy's most recent patch set, noting that it does not prevent users from using Singh's signature support if they want to. Instead, it adds a separate, compatible signature scheme for users who want the additional guarantees. In his view, Boscaccy's patch sets have not been given the due attention and review that they need, in favor of finalizing a solution that does not meet everyone's needs. He specifically called for Linus Torvalds to comment on the situation around Boscaccy's patch set, which Torvalds has not done.
Singh
replied that the lack of maintainer engagement with Boscaccy's patch sets
has been because Boscaccy has repeatedly ignored feedback from maintainers.
Singh called Boscaccy's approach "broken
", and opined that having
multiple signature schemes would not provide a good user experience. He also
linked to
part of the discussion of his patch set where he had attempted to refute
Boscaccy's concerns.
You keep mentioning having visibility in the LSM code and I again ask, to implement what specific security policy and there is no clear answer? On a system where you would like to only allow signed BPF programs, you can purely deny any programs where the signature is not provided and this can be implemented today.
Moore disagreed with that assessment, saying that Boscaccy's several patch sets have all been different approaches because of feedback he received from reviewers. He agreed that in a perfect world there would only be a singular BPF signature scheme — but if there are users who need the capabilities offered by Boscaccy's patch set, then their use cases should be enabled. Singh asked again for information on the specific use case that requires Boscaccy's patch set. Moore replied that there was not one, specific, use case that he had in mind. Rather, it is important to let users customize their policies with LSMs, which is only possible if the signature verification takes place before the LSM hook is called. Otherwise, it's possible for the process to fail after the LSM has already recorded a "success".
Singh reiterated his position, which has remained unchanged since he began work on BPF signing: since the loader is trusted, and it verifies the hash of the full program before loading it, verifying the signature on the loader is fully equivalent to verifying a signature made across every component. In fact, the cover letter for Singh's patch set explains that the loader programs are generated by libbpf, which is maintained as part of the kernel source, by the BPF maintainers — so if anyone felt unable to trust the verification code generated by libbpf, they would have bigger problems.
James Bottomley
said that the LSM issue isn't about signing, per se. It's "full
determination that all the integrity conditions [the LSM] is imposing are
satisfied by the time the hook is called.
" Singh did not reply, however; he
and Moore
agreed
to disagree, and the latter asked Starovoitov whether he intended to take
Boscaccy's patch set or not.
Starovoitov won't. Neither Bottomley nor Moore understand what Boscaccy's patch set actually does, he said. Starovoitov does understand the worries about asking LSMs to make decisions before the program loading process finishes, but that's also true of the kernel module loading process, he says. Both signed kernel modules and signed BPF programs need to have trusted build systems, or the signing is pointless.
Bottomley disagreed with that comparison; the ELF loader for kernel modules is built into the kernel. The loader programs for signed BPF programs are generated by libbpf, which is maintained in the kernel source tree, but they're not part of the kernel itself.
Integrity checking is not complete until the integrity of both has been verified. If you sign only the loader and embed the hash of the program into the loader that is a different way of doing things, but the integrity check is not complete until the loader does the hash verification which, as has been stated many times before, is after the load LSM hook has run.
Starovoitov did not believe that was true. In his opinion, the kernel's role in integrity checking is done as soon as the loader has been verified. He likened Singh's approach to a self-extracting zip archive: the actual cryptographic signature covers the whole archive, and so it doesn't really matter that the archive contains code that will be executed to create new, unzipped files that aren't covered by the signature. In the same way, once the kernel has verified the signature on the loader, there is no more signature verification from the kernel needed, since the trusted (and now verified) loader will handle the last steps.
Bottomley and Starovoitov failed to reach a conclusion; neither was convinced by the other's position. Moore continued to push Boscaccy's patch, but Starovoitov asked him to stop. With a working solution merged in the kernel, and Starovoitov strongly opposed to Boscaccy's approach, it seems unlikely that any future attempts with this approach from Boscaccy will be considered — although Boscaccy may keep trying. Whether the debate will end here, or will ultimately require a pronouncement from Torvalds to resolve remains to be seen.
Posted Oct 27, 2025 19:46 UTC (Mon)
by hailfinger (subscriber, #76962)
[Link]
This is like having Microsoft sign the shim which loads GRUB. That way, Microsoft can disclaim any responsibility for code run on such a system after shim was loaded. We have similar situations with intermediate certificate authorities which get delegated full power from the root CA to sign anything they want. Obviously, that increases flexibility as well as attack surface. We had rogue intermediate CAs in the past, let's see what delegating this responsibility gets us.
Posted Oct 27, 2025 23:28 UTC (Mon)
by muase (subscriber, #178466)
[Link]
After all, at least in the beginning, BPF programs are just bytes? So why can't we just read the program from disk into (kernel) memory, validate the signature over this blob, and then do the magic afterwards (per-version translation, relocation, ...)?
AFAIK the problem is probably not new – with dylibs, relocatable linkage, jar-files or other jittery stuff, and app-bundles, you already have payloads that are modified during runtime in ways the signature cannot cover. The solution is usually that you sign the precursor, because every runtime modification is either done by the OS (=trusted), or by the executed code from the precursor itself (=trusted).
So – what am I missing here? Where is my error? :D
Multi-level certificate authorities...
Why is that so complicated?
