[PATCH bpf v3 2/2] bpf, libbpf: reject non-exclusive metadata maps in the signed loader

Alexei Starovoitov alexei.starovoitov at gmail.com
Fri May 29 15:01:01 UTC 2026


On Fri, May 29, 2026 at 5:25 AM Daniel Borkmann <daniel at iogearbox.net> wrote:
>
> On 5/23/26 5:12 PM, Alexei Starovoitov wrote:
> > On Fri, May 22, 2026 at 11:53 PM KP Singh <kpsingh at kernel.org> wrote:
> >>
> >> The loader verifies map->sha against the metadata hash in its
> >> instructions. map->sha is calculated when BPF_OBJ_GET_INFO_BY_FD is called
> >> on the frozen map.
> >>
> >> While the map is frozen, the loader must also ensure the map is
> >> exclusive, as, without exclusivity, another BPF program with map access
> >> can mutate the contents afterwards, so the check passes on stale data.
> >
> > Hold on. How is this an issue? excl_prog_sha guarantees
> > that only loader prog can use this map.
> > Are you saying the same loader prog will use the same map
> > for the 2nd time. Ok. I still don't see a problem.
> >
> >> Place excl_prog_sha right after sha[] in struct bpf_map and have
> >> gen_loader bail with -EINVAL when it is NULL, via BPF_PSEUDO_MAP_IDX at
> >> fixed offset 32. The 8-byte read of the pointer field limits this to
> >> 64-bit kernels; gen_loader needs target pointer size tracking to emit
> >> the right sized read on 32-bit (follow-up).
> >
> > I don't think we can go from maybe-racy to certainly-broken-on-32-bit.
> > So only applied patch 1.
>
> I've looked a bit more into it with regards to above question from Alexei
> as well as the __bpf_md_ptr issue.
>
> Imho, KP is correct that the extra check/enforcement is needed. So Alice
> as a trusted signer generates the loader program (loader_insns + data_blob)
> and signs it. The loader program contains the below enforcement to reject
> if the metadata map was not exclusive.
>
> Now the (untrusted) host that wants to load the program, it holds a signed
> loader where they can't change a byte of it without breaking the signature.
>
> However, it could simply omit excl_prog_hash on BPF_MAP_CREATE for the data
> map (which would "normally" be bound exclusively to the loader).
>
> Then check_map_prog_compatibility() enforcement is skipped on verifier side
> given excl_prog_sha is not set. The loader loads fine, the fingerprint check
> can then pass against a stale snapshot while a different program mangled the
> data_blob underneath.
>
> Regarding __bpf_md_ptr, I would solve it differently via fixed size, see below
> together with the excl check coming before the signature check in the loader
> and the build bug assertions, and a jmp not eq to 1.
>
>   include/linux/bpf.h        |  1 +
>   kernel/bpf/syscall.c       |  5 +++++
>   tools/lib/bpf/gen_loader.c | 17 +++++++++++++++++
>   3 files changed, 23 insertions(+)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index cd191c5fdb0a..487f4653d8a6 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -295,6 +295,7 @@ struct bpf_map_owner {
>
>   struct bpf_map {
>         u8 sha[SHA256_DIGEST_SIZE];
> +       u32 excl;
>         const struct bpf_map_ops *ops;
>         struct bpf_map *inner_map_meta;
>   #ifdef CONFIG_SECURITY
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 630d530782fe..37dacdbc5c01 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -1572,6 +1572,11 @@ static int map_create(union bpf_attr *attr, bpfptr_t uattr)
>                         err = -EFAULT;
>                         goto free_map;
>                 }
> +
> +               /* See libbpf: emit_signature_match() */
> +               BUILD_BUG_ON(offsetof(struct bpf_map, excl) != SHA256_DIGEST_SIZE);
> +               BUILD_BUG_ON(offsetof(struct bpf_map, sha)  != 0);
> +               map->excl = 1;
>         } else if (attr->excl_prog_hash_size) {
>                 err = -EINVAL;
>                 goto free_map;
> diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
> index bcea21c3b7bb..cd8d7df94ac7 100644
> --- a/tools/lib/bpf/gen_loader.c
> +++ b/tools/lib/bpf/gen_loader.c
> @@ -586,6 +586,23 @@ static void emit_signature_match(struct bpf_gen *gen)
>         __s64 off;
>         int i;
>
> +       /*
> +        * Reject if the metadata map is not exclusive. Without exclusivity
> +        * the cached map->sha[] verified above can be stale: another BPF
> +        * program with map access could have mutated the contents between
> +        * BPF_OBJ_GET_INFO_BY_FD and loader execution.
> +        */
> +       emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
> +                                        0, 0, 0, 0));
> +       emit(gen, BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, SHA256_DIGEST_LENGTH));
> +       off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
> +       if (is_simm16(off)) {
> +               emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
> +               emit(gen, BPF_JMP_IMM(BPF_JNE, BPF_REG_2, 1, off));
> +       } else {
> +               gen->error = -ERANGE;
> +       }

yeah. much cleaner. ship it.



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