Understanding the BPF Loader and Its Signature Schemes

This article provides an in‑depth technical walkthrough of the eBPF loader implementation, explains two signature schemes, details map creation, hash calculation, CO‑RE relocation handling, the use_loader mode, kernel‑side verification via Hornet LSM, and discusses the advantages, limitations, and TOCTOU concerns.

Linux Kernel Journey
Linux Kernel Journey
Linux Kernel Journey
Understanding the BPF Loader and Its Signature Schemes

This document is a technical deep‑dive into the eBPF loader and two signature schemes used to protect loaded programs. It starts with a brief introduction and references to related documents, then walks through the loader implementation step by step.

1. Loader Overview

The loader is built from the bpftool gen skeleton command. When the -L|--use-loader flag is supplied, the generated skeleton contains a loader program that runs in kernel space and loads the user eBPF program.

Command‑line Example

# strace -e bpf ./trace
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, key_size=4, value_size=32392, max_entries=1, map_name="__loader.map"}) = 3
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x7ffc63638090, value=0x404100, flags=BPF_ANY}, 32) = 0
bpf(BPF_MAP_FREEZE, {map_fd=3}, 4) = 0
bpf(BPF_OBJ_GET_INFO_BY_FD, {info={bpf_fd=3, info_len=104, info=0x7ffc63637f20}}, 16) = 0
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_SYSCALL, insn_cnt=158, insns=0x40bf90, license="Dual BSD/GPL", prog_name="__loader.prog", prog_flags=BPF_F_SLEEPABLE, ...}, 168) = 4
bpf(BPF_PROG_TEST_RUN, {test={prog_fd=4, retval=0, ctx_size_in=44, ctx_in=0x2071ceb0}}, 80) = 0
bpf(BPF_RAW_TRACEPOINT_OPEN, {raw_tracepoint={name="sched_wakeup_new", prog_fd=7}}, 12) = 3

2. Core Loader Functions

The loader consists of several functions generated by bpftool: bpf_gen__init – creates the loader map __loader.map and stores the program data. bpf_gen__load_btf – copies BTF, license, instruction, and relocation information into the loader attributes. bpf_gen__map_create – creates the map that holds the loader data. bpf_gen__prog_load – loads the loader program itself, passing the prepared attr structure to the kernel. bpf_gen__finish – cleans up, closes the map, and returns the loader program file descriptor.

Key code snippets (simplified) illustrate the flow:

static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts) {
    err = map_fd = skel_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4,
                                   opts->data_sz, 1,
                                   opts->excl_prog_hash, opts->excl_prog_hash_sz);
    attr.fd_array = (long)&map_fd; // map that stores .data
    err = skel_sys_bpf(BPF_PROG_RUN, &attr, test_run_attr_sz);
}

3. Signature Generation and Verification

The loader can embed a signature for the loader instructions ( opts.insns) and a hash of the user program data ( opts.data). The signing routine ( bpftool_prog_sign) computes a SHA‑256 hash of opts.insns and signs it with a private key:

int bpftool_prog_sign(struct bpf_load_and_run_opts *opts) {
    BIO *bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz);
    EVP_Digest(opts->insns, opts->insns_sz, opts->excl_prog_hash,
               &opts->excl_prog_hash_sz, EVP_sha256(), NULL);
    // ... sign with private key and store in opts.signature
}

During BPF_PROG_LOAD, the kernel LSM hook hornet_check_program extracts the signature, validates it against the trusted certificate store, and retrieves the hash array ( opts.excl_prog_hash) from the signed message. It then compares the stored hash with the hash of the map that holds the loader data:

static int hornet_verify_hashes(struct hornet_maps *maps,
                                 struct hornet_parse_context *ctx) {
    for (i = 0; i < ctx->hash_count; i++) {
        copy_from_bpfptr_offset(&map_fd, maps->fd_array,
                                ctx->indexes[i] * sizeof(map_fd),
                                sizeof(map_fd));
        map = fd_file(map_fd)->private_data;
        map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash);
        if (memcmp(hash, &ctx->hashes[ctx->indexes[i] * SHA256_DIGEST_SIZE],
                   SHA256_DIGEST_SIZE))
            return LSM_INT_VERDICT_BADSIG;
    }
    return 0;
}

4. CO‑RE Relocation Handling

When use_loader is enabled, the loader does not modify the eBPF instructions directly. Instead, it records CO‑RE relocation information in attr.core_relos. The kernel’s check_core_relo routine applies the relocations at load time, preserving the original program binary.

5. Advantages and Limitations of the -S (Signature) Mode

All effective content of the original .o (maps, BSS, programs) is covered by the .data hash, providing comprehensive integrity checking.

Works with CO‑RE programs without requiring libbpf to rewrite instructions.

Only a subset of loader‑generated functions are supported in use_loader mode.

6. Hornet LSM Overview

Hornet is an LSM that verifies signatures and hashes supplied via the attr.signature and attr.fd_array fields. It parses the PKCS#7 message, extracts the hash array, and validates each map’s hash against the stored values.

7. TOCTOU Concerns

The verification occurs during BPF_PROG_LOAD. After the program is loaded, the subsequent BPF_PROG_TEST_RUN phase can replace the map referenced by attr.fd_array, potentially bypassing the integrity check (a classic Time‑of‑Check‑to‑Time‑of‑Use issue). The article notes that adding a check in BPF_PROG_TEST_RUN is non‑trivial because the map file descriptor is stored inside the loader’s .data and not passed via a system call.

8. Summary of the Workflow

sequenceDiagram
    participant U as User Space
    participant K as Kernel
    U->>K: BPF_MAP_CREATE (create __loader.map)
    U->>K: BPF_MAP_UPDATE_ELEM (store .data)
    U->>K: BPF_MAP_FREEZE (freeze map)
    U->>K: BPF_OBJ_GET_INFO_BY_FD (compute SHA‑256 of .data)
    K->>U: Return hash for loader verification
    U->>K: BPF_PROG_LOAD (load loader program with signature)
    U->>K: BPF_PROG_TEST_RUN (loader runs, verifies .data hash, loads user program)
    U->>K: BPF_RAW_TRACEPOINT_OPEN (user program ready)

Overall, the article demonstrates how the BPF loader can be signed, how the kernel validates the signature and associated hashes, and highlights the security implications of the current design.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

eBPFsignature verificationCO-REBPF loaderHornet LSM
Linux Kernel Journey
Written by

Linux Kernel Journey

Linux Kernel Journey

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.