[PATCH v2 08/13] bpf: Implement signature verification for BPF programs
Paul Moore
paul at paul-moore.com
Wed Aug 13 02:20:21 UTC 2025
On Tue, Aug 5, 2025 at 2:28 PM Blaise Boscaccy
<bboscaccy at linux.microsoft.com> wrote:
> KP Singh <kpsingh at kernel.org> writes:
> > This patch extends the BPF_PROG_LOAD command by adding three new fields
> > to `union bpf_attr` in the user-space API:
> >
> > - signature: A pointer to the signature blob.
> > - signature_size: The size of the signature blob.
> > - keyring_id: The serial number of a loaded kernel keyring (e.g.,
> > the user or session keyring) containing the trusted public keys.
> >
> > When a BPF program is loaded with a signature, the kernel:
> >
> > 1. Retrieves the trusted keyring using the provided `keyring_id`.
> > 2. Verifies the supplied signature against the BPF program's
> > instruction buffer.
> > 3. If the signature is valid and was generated by a key in the trusted
> > keyring, the program load proceeds.
> > 4. If no signature is provided, the load proceeds as before, allowing
> > for backward compatibility. LSMs can chose to restrict unsigned
> > programs and implement a security policy.
> > 5. If signature verification fails for any reason,
> > the program is not loaded.
> [...]
>
> The following is what we propose to build on top of this to implement
> in-kernel hash chain verification. This allows for signature
> verification of arbitrary maps and isn't coupled to light-skeletons or
> any specific implementation.
>
>
> From: Blaise Boscaccy <bboscaccy at linux.microsoft.com>
> Date: Mon, 28 Jul 2025 08:14:57 -0700
> Subject: bpf: Add hash chain signature support for arbitrary maps
>
> This patch introduces hash chain support for signature verification of
> arbitrary bpf map objects which was described here:
> https://lore.kernel.org/linux-security-module/20250721211958.1881379-1-kpsingh@kernel.org/
>
> The UAPI is extended to allow for in-kernel checking of maps passed in
> via the fd_array. A hash chain is constructed from the maps, in order
> specified by the signature_maps field. The hash chain is terminated
> with the hash of the program itself.
>
> Signed-off-by: Blaise Boscaccy <bboscaccy at linux.microsoft.com>
> ---
> include/uapi/linux/bpf.h | 6 +++
> kernel/bpf/syscall.c | 75 ++++++++++++++++++++++++++++++++--
> tools/include/uapi/linux/bpf.h | 6 +++
> 3 files changed, 83 insertions(+), 4 deletions(-)
Some minor comments below, but in general I think this approach is
good in that it preserves the signature scheme in KP's patchset while
also supporting a scheme that is not reliant on the lskel or
verification, with the user loading the program/lskel choosing which
signature scheme to use. Unless something has changed since we've
discussed this last, I believe this should provide the basic BPF
infrastructure needed to satisfy all the different requirements
already described.
Thoughts on this KP (as well as any others who have been following along)?
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 10fd3ea5d91fd..f7e9bcabd9dcc 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -2780,15 +2780,36 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
> }
> }
>
> +static inline int bpf_map_get_hash(int map_fd, void *buffer)
> +{
> + struct bpf_map *map;
> +
> + CLASS(fd, f)(map_fd);
> + map = __bpf_map_get(f);
> + if (IS_ERR(map))
> + return PTR_ERR(map);
> +
> + if (!map->ops->map_get_hash)
> + return -EINVAL;
> +
> + return map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, buffer);
> +}
It would be nice to see some agility on the hash algorithm, but it's
probably not critical for a first effort. I can easily see an
algorithm field being added to bpf_attr, using the same digest
algorithm as specified in the PKCS7 signature, or something else
sufficiently clever.
> static noinline int bpf_prog_verify_signature(struct bpf_prog *prog,
> union bpf_attr *attr,
> bool is_kernel)
> {
> bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
> - struct bpf_dynptr_kern sig_ptr, insns_ptr;
> + bpfptr_t umaps;
> + struct bpf_dynptr_kern sig_ptr, insns_ptr, hash_ptr;
> struct bpf_key *key = NULL;
> void *sig;
> + int *maps;
> + int map_fd;
> int err = 0;
> + u64 buffer[8];
> + u64 hash[4];
It would be good to replace the magic numbers above with something
that is a bit more self documenting, e.g. 'u64 hash[SHA256_DIGEST_SIZE
/ sizeof(u64)]'. The same goes for some of the pointer offset math
below, assuming the result isn't too ugly. If the resulting code is
pretty awful to look at, a quick comment or two might be a good idea.
> + int n;
>
> if (system_keyring_id_check(attr->keyring_id) == 0)
> key = bpf_lookup_system_key(attr->keyring_id);
> @@ -2808,16 +2829,62 @@ static noinline int bpf_prog_verify_signature(struct bpf_prog *prog,
> bpf_dynptr_init(&insns_ptr, prog->insnsi, BPF_DYNPTR_TYPE_LOCAL, 0,
> prog->len * sizeof(struct bpf_insn));
>
> - err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&insns_ptr,
> - (struct bpf_dynptr *)&sig_ptr, key);
> + if (!attr->signature_maps_size) {
> + err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&insns_ptr,
> + (struct bpf_dynptr *)&sig_ptr, key);
> + } else {
> + bpf_dynptr_init(&hash_ptr, hash, BPF_DYNPTR_TYPE_LOCAL, 0,
> + sizeof(hash));
> + umaps = make_bpfptr(attr->signature_maps, is_kernel);
> + maps = kvmemdup_bpfptr(umaps, attr->signature_maps_size * sizeof(*maps));
> + if (!maps) {
> + err = -ENOMEM;
> + goto out;
> + }
> + n = attr->signature_maps_size - 1;
> + err = copy_from_bpfptr_offset(&map_fd, make_bpfptr(attr->fd_array, is_kernel),
> + maps[n] * sizeof(map_fd),
> + sizeof(map_fd));
> + if (err < 0)
> + goto free_maps;
> +
> + err = bpf_map_get_hash(map_fd, hash);
> + if (err != 0)
> + goto free_maps;
> +
> + n--;
> + while (n >= 0) {
> + memcpy(buffer, hash, sizeof(hash));
> + err = copy_from_bpfptr_offset(&map_fd,
> + make_bpfptr(attr->fd_array, is_kernel),
> + maps[n] * sizeof(map_fd),
> + sizeof(map_fd));
> + if (err < 0)
> + goto free_maps;
> +
> + err = bpf_map_get_hash(map_fd, buffer+4);
> + if (err != 0)
> + goto free_maps;
> + sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash);
> + n--;
> + }
> + sha256((u8 *)prog->insnsi, prog->len * sizeof(struct bpf_insn), (u8 *)&buffer);
> + memcpy(buffer+4, hash, sizeof(hash));
> + sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash);
> + err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&hash_ptr,
> + (struct bpf_dynptr *)&sig_ptr, key);
>
> +free_maps:
> + kvfree(maps);
> + }
> +out:
> bpf_key_put(key);
> kvfree(sig);
> return err;
> }
>
> /* last field in 'union bpf_attr' used by this command */
> -#define BPF_PROG_LOAD_LAST_FIELD keyring_id
> +#define BPF_PROG_LOAD_LAST_FIELD signature_maps_size
>
> static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
> {
--
paul-moore.com
More information about the Linux-security-module-archive
mailing list