On Sun, 2025-05-11 at 04:01 +0200, KP Singh wrote: [...] > > > 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. > > From an implementation standpoint: > > # Build > > bpftool (or some other tool in the user's build environment) knows > about the metadata (M_metadata) and the loader program (I_loader). It > first calculates H_meta = H(M_metadata). Then it constructs the > object > to be signed and computes the signature: > > Sig(I_loader || H_meta) > > # Loader > > bpftool generates the loader program. The initial instructions of > this loader program are designed to verify the SHA256 hash of the > metadata (M_metadata) that will be passed in a map. These > instructions effectively embed the precomputed H_meta as immediate > values. > > ld_imm64 r1, const_ptr_to_map // insn[0].src_reg == > BPF_PSEUDO_MAP_IDX > r2 = *(u64 *)(r1 + 0); > ld_imm64 r3, sha256_of_map_part1 // constant precomputed by > bpftool (part of H_meta) > if r2 != r3 goto out; > > r2 = *(u64 *)(r1 + 8); > ld_imm64 r3, sha256_of_map_part2 // (part of H_meta) > if r2 != r3 goto out; > > r2 = *(u64 *)(r1 + 16); > ld_imm64 r3, sha256_of_map_part3 // (part of H_meta) > if r2 != r3 goto out; > > r2 = *(u64 *)(r1 + 24); > ld_imm64 r3, sha256_of_map_part4 // (part of H_meta) > if r2 != r3 goto out; > ... > > This implicitly makes the payload equivalent to the signed block > (B_signed) > > I_loader || H_meta > > bpftool then generates the signature of this I_loader payload (which > now contains the expected H_meta) using a key (system or user) with > new flags that work in combination with bpftool -L
Could I just push back a bit on this. The theory of hash chains (which I've cut to shorten) is about pure data structures. The reason for that is that the entire hash chain is supposed to be easily independently verifiable in any environment because anything can compute the hashes of the blocks and links. This independent verification of the chain is key to formally proving hash chains to be correct. In your proposal we lose the easy verifiability because the link hash is embedded in the ebpf loader program which has to be disassembled to do the extraction of the hash and verify the loader is actually checking it. I was looking at ways we could use a pure hash chain (i.e. signature over loader and real map hash) and it does strike me that the above ebpf hash verification code is pretty invariant and easy to construct, so it could run as a separate BPF fragment that then jumps to the real loader. In that case, it could be constructed on the fly in a trusted environment, like the kernel, from the link hash in the signature and the signature could just be Sig(loader || map hash) which can then be easily verified without having to disassemble ebpf code. So we get the formal provability benefits of using a real hash chain while still keeping your verification in BPF. Regards, James