#v1 -> v2 * Addressed feedback on excl maps and their implementation * fixed s390x and other tests that were failing in the CI. * using the kernel's sha256 API since it now uses acceleration if available * simple signing test case, this can be extended to inject a false SHA into the loader BPF Signing has gone over multiple discussions in various conferences with the kernel and BPF community and the following patch series is a culmination of the current of discussion and signed BPF programs. Once signing is implemented, the next focus would be to implement the right security policies for all BPF use-cases (dynamically generated bpf programs, simple non CO-RE programs). Signing also paves the way for allowing unrivileged users to load vetted BPF programs and helps in adhering to the principle of least privlege by avoiding unnecessary elevation of privileges to CAP_BPF and CAP_SYS_ADMIN (ofcourse, with the appropriate security policy active). A early version of this design was proposed in [1]: # General Idea: Trusted Hash Chain The key idea of the design is to use a signing algorithm that allows us to integrity-protect a number of future payloads, including their order, by creating a chain of trust. Consider that Alice needs to send messages M_1, M_2, ..., M_n to Bob. We define blocks of data such that: B_n = M_n || H(termination_marker) (Each block contains its corresponding message and the hash of the *next* block in the chain.) B_{n-1} = M_{n-1} || H(B_n) B_{n-2} = M_{n-2} || H(B_{n-1}) ... B_2 = M_2 || H(B_3) B_1 = M_1 || H(B_2) Alice does the following (e.g., on a build system where all payloads are available): * Assembles the blocks B_1, B_2, ..., B_n. * Calculates H(B_1) and signs it, yielding Sig(H(B_1)). Alice sends the following to Bob: M_1, H(B_2), Sig(H(B_1)) Bob receives this payload and does the following: * Reconstructs B_1 as B_1' using the received M_1 and H(B_2) (i.e., B_1' = M_1 || H(B_2)). * Recomputes H(B_1') and verifies the signature against the received Sig(H(B_1)). * If the signature verifies, it establishes the integrity of M_1 and H(B_2) (and transitively, the integrity of the entire chain). Bob now stores the verified H(B_2) until it receives the next message. * When Bob receives M_2 (and H(B_3) if n > 2), it reconstructs B_2' (e.g., B_2' = M_2 || H(B_3), or if n=2, B_2' = M_2 || H(termination_marker)). Bob then computes H(B_2') and compares it against the stored H(B_2) that was verified in the previous step. This process continues until the last block is received and verified. Now, applying this to the BPF signing use-case, we simplify to two messages: M_1 = I_loader (the instructions of the loader program) M_2 = M_metadata (the metadata for the loader program, passed in a map, which includes the programs to be loaded and other context) For this specific BPF case, we will directly sign a composite of the first message and the hash of the second. Let H_meta = H(M_metadata). The block to be signed is effectively: B_signed = I_loader || H_meta The signature generated is Sig(B_signed). The process then follows a similar pattern to the Alice and Bob model, where the kernel (Bob) verifies I_loader and H_meta using the signature. Then, the trusted I_loader is responsible for verifying M_metadata against the trusted H_meta.