diff mbox series

[1/3] bpf: Add bpf_check_signature

Message ID 20250528215037.2081066-2-bboscaccy@linux.microsoft.com
State New
Headers show
Series BPF signature verification | expand

Commit Message

Blaise Boscaccy May 28, 2025, 9:49 p.m. UTC
This introduces signature verification for eBPF programs inside of the
bpf subsystem. Two signature validation schemes are included, one that
only checks the instruction buffer, and another that checks over a
hash chain constructed from the program and a list of maps. The
alternative algorithm is designed to provide support to scenarios
where having self-aborting light-skeletons or signature checking
living outside the kernel-proper is insufficient or undesirable.

An abstract hash method is introduced to allow calculating the hash of
maps, only arrays are implemented at this time.

A simple UAPI is introduced to provide passing signature information.

The signature check is performed before the call to
security_bpf_prog_load. This allows the LSM subsystem to be clued into
the result of the signature check, whilst granting knowledge of the
method and apparatus which was employed.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 include/linux/bpf.h            |   2 +
 include/linux/verification.h   |   1 +
 include/uapi/linux/bpf.h       |   4 ++
 kernel/bpf/arraymap.c          |  11 ++-
 kernel/bpf/syscall.c           | 123 ++++++++++++++++++++++++++++++++-
 tools/include/uapi/linux/bpf.h |   4 ++
 6 files changed, 143 insertions(+), 2 deletions(-)

Comments

Paul Moore June 2, 2025, 10:40 p.m. UTC | #1
On Wed, May 28, 2025 at 5:50 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
>
> This introduces signature verification for eBPF programs inside of the
> bpf subsystem. Two signature validation schemes are included, one that
> only checks the instruction buffer, and another that checks over a
> hash chain constructed from the program and a list of maps. The
> alternative algorithm is designed to provide support to scenarios
> where having self-aborting light-skeletons or signature checking
> living outside the kernel-proper is insufficient or undesirable.
>
> An abstract hash method is introduced to allow calculating the hash of
> maps, only arrays are implemented at this time.
>
> A simple UAPI is introduced to provide passing signature information.
>
> The signature check is performed before the call to
> security_bpf_prog_load. This allows the LSM subsystem to be clued into
> the result of the signature check, whilst granting knowledge of the
> method and apparatus which was employed.
>
> Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
> ---
>  include/linux/bpf.h            |   2 +
>  include/linux/verification.h   |   1 +
>  include/uapi/linux/bpf.h       |   4 ++
>  kernel/bpf/arraymap.c          |  11 ++-
>  kernel/bpf/syscall.c           | 123 ++++++++++++++++++++++++++++++++-
>  tools/include/uapi/linux/bpf.h |   4 ++
>  6 files changed, 143 insertions(+), 2 deletions(-)

...

> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 64c3393e8270..7dc35681d3f8 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -2753,8 +2764,113 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
>         }
>  }
>
> +static int bpf_check_signature(struct bpf_prog *prog, union bpf_attr *attr, bpfptr_t uattr,
> +                              __u32 uattr_size)
> +{
> +       u64 hash[4];
> +       u64 buffer[8];
> +       int err;
> +       char *signature;
> +       int *used_maps;
> +       int n;
> +       int map_fd;
> +       struct bpf_map *map;
> +
> +       if (!attr->signature)
> +               return 0;
> +
> +       signature = kmalloc(attr->signature_size, GFP_KERNEL);
> +       if (!signature) {
> +               err = -ENOMEM;
> +               goto out;
> +       }
> +
> +       if (copy_from_bpfptr(signature,
> +                            make_bpfptr(attr->signature, uattr.is_kernel),
> +                            attr->signature_size) != 0) {
> +               err = -EINVAL;
> +               goto free_sig;
> +       }
> +
> +       if (!attr->signature_maps_size) {
> +               sha256((u8 *)prog->insnsi, prog->len * sizeof(struct bpf_insn), (u8 *)&hash);
> +               err = verify_pkcs7_signature(hash, sizeof(hash), signature, attr->signature_size,
> +                                    VERIFY_USE_SECONDARY_KEYRING,
> +                                    VERIFYING_EBPF_SIGNATURE,
> +                                    NULL, NULL);
> +       } else {
> +               used_maps = kmalloc_array(attr->signature_maps_size,
> +                                         sizeof(*used_maps), GFP_KERNEL);
> +               if (!used_maps) {
> +                       err = -ENOMEM;
> +                       goto free_sig;
> +               }
> +               n = attr->signature_maps_size;
> +               n--;
> +
> +               err = copy_from_bpfptr_offset(&map_fd, make_bpfptr(attr->fd_array, uattr.is_kernel),
> +                                             used_maps[n] * sizeof(map_fd),
> +                                             sizeof(map_fd));
> +               if (err < 0)
> +                       goto free_maps;
> +
> +               /* calculate the terminal hash */
> +               CLASS(fd, f)(map_fd);
> +               map = __bpf_map_get(f);
> +               if (IS_ERR(map)) {
> +                       err = PTR_ERR(map);
> +                       goto free_maps;
> +               }
> +               if (__map_get_hash(map, (u8 *)hash)) {
> +                       err = -EINVAL;
> +                       goto free_maps;
> +               }
> +
> +               n--;
> +               /* calculate a link in the hash chain */
> +               while (n >= 0) {
> +                       memcpy(buffer, hash, sizeof(hash));
> +                       err = copy_from_bpfptr_offset(&map_fd,
> +                                                     make_bpfptr(attr->fd_array, uattr.is_kernel),
> +                                                     used_maps[n] * sizeof(map_fd),
> +                                                     sizeof(map_fd));
> +                       if (err < 0)
> +                               goto free_maps;
> +
> +                       CLASS(fd, f)(map_fd);
> +                       map = __bpf_map_get(f);
> +                       if (!map) {
> +                               err = -EINVAL;
> +                               goto free_maps;
> +                       }
> +                       if (__map_get_hash(map, (u8 *)buffer+4)) {
> +                               err = -EINVAL;
> +                               goto free_maps;
> +                       }
> +                       sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash);

James' comment about using the hash from the PKCS7 data makes a lot of
sense.  I'm not a kernel crypto expert, but if looks like if you call
pkcs7_parse_message() you should be able to get the hash algorithm
from pkcs7_message->signed_infos->sig->hash_algo.

I imagine there might be user/admin concerns over which algorithms
would be considered acceptable for a signature verification, but I
suppose one could make the argument that if you don't trust that
algorithm it shouldn't be enabled in the kernel.

> +                       n--;
> +               }
> +               /* calculate the root hash and verify it's signature */
> +               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 = verify_pkcs7_signature(hash, sizeof(hash), signature, attr->signature_size,
> +                                    VERIFY_USE_SECONDARY_KEYRING,
> +                                    VERIFYING_EBPF_SIGNATURE,
> +                                    NULL, NULL);
> +free_maps:
> +               kfree(used_maps);
> +       }
> +
> +free_sig:
> +       kfree(signature);
> +out:
> +       prog->aux->signature_verified = !err;

Considering this code supports two signature schemes, signed loader
(with implied loader verification of maps) and signed loader + maps,
it seems like it might be a good idea to have two flags to indicate
what has been verified in bpf_check_signature(); a "prog_sig_verified"
(or similar) flag to indicate the program has been verified and a
"maps_sig_verified" (or similar) to indicate the maps have been
verified.

Beyond that, I wanted to talk a bit about the two different signature
schemes and why I think there is value in supporting both.  The
discussion was happening in patch 0/3, but it looks like KP wanted to
move the discussion away from the cover letter and into that patch, so
I'm doing that here ...

The loader (+ implicit loader verification of maps w/original program)
signature verification scheme has been requested by Alexei/KP, and
that's fine, the code is trivial and if the user/admin is satisfied
with that as a solution, great.  However, the loader + map signature
verification scheme has some advantages and helps satisfy some
requirements that are not satisfied by only verifying the loader and
relying on the loader to verify the original program stored in the
maps.  One obvious advantage is that the lskel loader is much simpler
in this case as it doesn't need to worry about verification of the
program maps as that has already been done in bpf_check_signature().
I'm sure there are probably some other obvious reasons, but beyond the
one mentioned above, the other advantages that I'm interested in are a
little less obvious, or at least I haven't seen them brought up yet.
As I mentioned in an earlier thread, it's important to have the LSM
hook that handles authorization of a BPF program load *after* the BPF
program's signature has been verified.  This is not simply because the
LSM implementation might want to enforce and access control on a BPF
program load due to the signature state (signature verified vs no
signature), but also because the LSM might want to measure system
state and/or provide a record of the operation.  If we only verify the
lskel loader, at the point in time that the security_bpf_prog_load()
hook is called, we haven't properly verified both the loader and the
original BPF program stored in the map, that doesn't happen until much
later when the lskel loader executes.  Yes, I understand that may
sound very pedantic and fussy, but there are users who care very much
about those details, and if they see an event in the logs that
indicates that the BPF program signature has been verified as "good",
they need that log event to be fully, 100% true, and not have an
asterix of "only the lskel loader has been verified, the original BPF
program will potentially be verified later without any additional
events being logged to indicate the verification".

Considering that Blaise has proposed something which satisfies both
the loader-only and loader+maps signature requirements, I don't
understand the objections.  KP described two technical objections in
his replies to patch 0/3:

1. "It does not align with most BPF use-cases out there as most
use-cases need a trusted loader."
2. "Locks us into a UAPI, whereas a signed LOADER allows us to
incrementally build signing for all use-cases without compromising the
security properties."

In response to objection #1, the approach Blaise has described here
fully supports signing only the lskel loader and leaving it to the
loader to verify the original BPF program maps.  The "trusted loader"
use cases are fully supported, as the loader+maps scheme does not
prevent the loader-only signature scheme.

In response to objection #2, honestly this seems like an extension to
objection #1.  The trusted loader-only signature scheme is fully
supported.  Yes, adding support for either signature schemes is an
extension to the BPF program loading UAPI, but both schemes are
optional and supporting both is very much in line with BPF's stated
philosophy of "flexibility and not locking the users into a rigid
in-kernel implementation and UAPI."

> +       return err;
> +}
> +
Jarkko Sakkinen June 4, 2025, 4:25 p.m. UTC | #2
On Wed, May 28, 2025 at 02:49:03PM -0700, Blaise Boscaccy wrote:
> This introduces signature verification for eBPF programs inside of the

s/This introduces signature verification/Introduce a signature verification ???/

I.e. Explain what sort of "thing" is "signature verification thing" ...

BR, Jarkko
diff mbox series

Patch

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 3f0cc89c0622..298e0db34c28 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -109,6 +109,7 @@  struct bpf_map_ops {
 	long (*map_pop_elem)(struct bpf_map *map, void *value);
 	long (*map_peek_elem)(struct bpf_map *map, void *value);
 	void *(*map_lookup_percpu_elem)(struct bpf_map *map, void *key, u32 cpu);
+	int (*map_get_hash)(struct bpf_map *map, u8 *out);
 
 	/* funcs called by prog_array and perf_event_array map */
 	void *(*map_fd_get_ptr)(struct bpf_map *map, struct file *map_file,
@@ -1592,6 +1593,7 @@  struct bpf_prog_aux {
 #ifdef CONFIG_SECURITY
 	void *security;
 #endif
+	bool signature_verified;
 	struct bpf_token *token;
 	struct bpf_prog_offload *offload;
 	struct btf *btf;
diff --git a/include/linux/verification.h b/include/linux/verification.h
index 4f3022d081c3..812be8ad5f74 100644
--- a/include/linux/verification.h
+++ b/include/linux/verification.h
@@ -35,6 +35,7 @@  enum key_being_used_for {
 	VERIFYING_KEXEC_PE_SIGNATURE,
 	VERIFYING_KEY_SIGNATURE,
 	VERIFYING_KEY_SELF_SIGNATURE,
+	VERIFYING_EBPF_SIGNATURE,
 	VERIFYING_UNSPECIFIED_SIGNATURE,
 	NR__KEY_BEING_USED_FOR
 };
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index fd404729b115..f79af999c480 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1587,6 +1587,10 @@  union bpf_attr {
 		 * continuous.
 		 */
 		__u32		fd_array_cnt;
+		__aligned_u64	signature;	/* program signature */
+		__u32		signature_size;	/* size of program signature */
+		__aligned_u64	signature_maps;	/* maps used in signature */
+		__u32		signature_maps_size;	/* size of maps used in signature */
 	};
 
 	struct { /* anonymous struct used by BPF_OBJ_* commands */
diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
index eb28c0f219ee..5459ab6bf6e2 100644
--- a/kernel/bpf/arraymap.c
+++ b/kernel/bpf/arraymap.c
@@ -12,6 +12,7 @@ 
 #include <uapi/linux/btf.h>
 #include <linux/rcupdate_trace.h>
 #include <linux/btf_ids.h>
+#include <crypto/sha2.h>
 
 #include "map_in_map.h"
 
@@ -426,6 +427,14 @@  static long array_map_delete_elem(struct bpf_map *map, void *key)
 	return -EINVAL;
 }
 
+static int array_map_get_hash(struct bpf_map *map, u8 *out)
+{
+	struct bpf_array *array = container_of(map, struct bpf_array, map);
+
+	sha256(array->value, array->elem_size * array->map.max_entries, out);
+	return 0;
+}
+
 static void *array_map_vmalloc_addr(struct bpf_array *array)
 {
 	return (void *)round_down((unsigned long)array, PAGE_SIZE);
@@ -792,6 +801,7 @@  const struct bpf_map_ops array_map_ops = {
 	.map_lookup_elem = array_map_lookup_elem,
 	.map_update_elem = array_map_update_elem,
 	.map_delete_elem = array_map_delete_elem,
+	.map_get_hash = array_map_get_hash,
 	.map_gen_lookup = array_map_gen_lookup,
 	.map_direct_value_addr = array_map_direct_value_addr,
 	.map_direct_value_meta = array_map_direct_value_meta,
@@ -940,7 +950,6 @@  static long fd_array_map_delete_elem(struct bpf_map *map, void *key)
 {
 	return __fd_array_map_delete_elem(map, key, true);
 }
-
 static void *prog_fd_array_get_ptr(struct bpf_map *map,
 				   struct file *map_file, int fd)
 {
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 64c3393e8270..7dc35681d3f8 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -36,6 +36,8 @@ 
 #include <linux/memcontrol.h>
 #include <linux/trace_events.h>
 #include <linux/tracepoint.h>
+#include <crypto/pkcs7.h>
+#include <crypto/sha2.h>
 
 #include <net/netfilter/nf_bpf_link.h>
 #include <net/netkit.h>
@@ -2216,6 +2218,15 @@  static int map_freeze(const union bpf_attr *attr)
 	return err;
 }
 
+static int __map_get_hash(struct bpf_map *map, u8 *out)
+{
+	if (map->ops->map_get_hash) {
+		map->ops->map_get_hash(map, out);
+		return 0;
+	}
+	return -EINVAL;
+}
+
 static const struct bpf_prog_ops * const bpf_prog_types[] = {
 #define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) \
 	[_id] = & _name ## _prog_ops,
@@ -2753,8 +2764,113 @@  static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
 	}
 }
 
+static int bpf_check_signature(struct bpf_prog *prog, union bpf_attr *attr, bpfptr_t uattr,
+			       __u32 uattr_size)
+{
+	u64 hash[4];
+	u64 buffer[8];
+	int err;
+	char *signature;
+	int *used_maps;
+	int n;
+	int map_fd;
+	struct bpf_map *map;
+
+	if (!attr->signature)
+		return 0;
+
+	signature = kmalloc(attr->signature_size, GFP_KERNEL);
+	if (!signature) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	if (copy_from_bpfptr(signature,
+			     make_bpfptr(attr->signature, uattr.is_kernel),
+			     attr->signature_size) != 0) {
+		err = -EINVAL;
+		goto free_sig;
+	}
+
+	if (!attr->signature_maps_size) {
+		sha256((u8 *)prog->insnsi, prog->len * sizeof(struct bpf_insn), (u8 *)&hash);
+		err = verify_pkcs7_signature(hash, sizeof(hash), signature, attr->signature_size,
+				     VERIFY_USE_SECONDARY_KEYRING,
+				     VERIFYING_EBPF_SIGNATURE,
+				     NULL, NULL);
+	} else {
+		used_maps = kmalloc_array(attr->signature_maps_size,
+					  sizeof(*used_maps), GFP_KERNEL);
+		if (!used_maps) {
+			err = -ENOMEM;
+			goto free_sig;
+		}
+		n = attr->signature_maps_size;
+		n--;
+
+		err = copy_from_bpfptr_offset(&map_fd, make_bpfptr(attr->fd_array, uattr.is_kernel),
+					      used_maps[n] * sizeof(map_fd),
+					      sizeof(map_fd));
+		if (err < 0)
+			goto free_maps;
+
+		/* calculate the terminal hash */
+		CLASS(fd, f)(map_fd);
+		map = __bpf_map_get(f);
+		if (IS_ERR(map)) {
+			err = PTR_ERR(map);
+			goto free_maps;
+		}
+		if (__map_get_hash(map, (u8 *)hash)) {
+			err = -EINVAL;
+			goto free_maps;
+		}
+
+		n--;
+		/* calculate a link in the hash chain */
+		while (n >= 0) {
+			memcpy(buffer, hash, sizeof(hash));
+			err = copy_from_bpfptr_offset(&map_fd,
+						      make_bpfptr(attr->fd_array, uattr.is_kernel),
+						      used_maps[n] * sizeof(map_fd),
+						      sizeof(map_fd));
+			if (err < 0)
+				goto free_maps;
+
+			CLASS(fd, f)(map_fd);
+			map = __bpf_map_get(f);
+			if (!map) {
+				err = -EINVAL;
+				goto free_maps;
+			}
+			if (__map_get_hash(map, (u8 *)buffer+4)) {
+				err = -EINVAL;
+				goto free_maps;
+			}
+			sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash);
+			n--;
+		}
+		/* calculate the root hash and verify it's signature */
+		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 = verify_pkcs7_signature(hash, sizeof(hash), signature, attr->signature_size,
+				     VERIFY_USE_SECONDARY_KEYRING,
+				     VERIFYING_EBPF_SIGNATURE,
+				     NULL, NULL);
+free_maps:
+		kfree(used_maps);
+	}
+
+free_sig:
+	kfree(signature);
+out:
+	prog->aux->signature_verified = !err;
+	return err;
+}
+
 /* last field in 'union bpf_attr' used by this command */
-#define BPF_PROG_LOAD_LAST_FIELD fd_array_cnt
+#define BPF_PROG_LOAD_LAST_FIELD signature_maps_size
 
 static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 {
@@ -2963,6 +3079,11 @@  static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 	if (err < 0)
 		goto free_prog;
 
+	/* run eBPF signature verifier */
+	err = bpf_check_signature(prog, attr, uattr, uattr_size);
+	if (err < 0)
+		goto free_prog;
+
 	err = security_bpf_prog_load(prog, attr, token, uattr.is_kernel);
 	if (err)
 		goto free_prog_sec;
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index fd404729b115..f79af999c480 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1587,6 +1587,10 @@  union bpf_attr {
 		 * continuous.
 		 */
 		__u32		fd_array_cnt;
+		__aligned_u64	signature;	/* program signature */
+		__u32		signature_size;	/* size of program signature */
+		__aligned_u64	signature_maps;	/* maps used in signature */
+		__u32		signature_maps_size;	/* size of maps used in signature */
 	};
 
 	struct { /* anonymous struct used by BPF_OBJ_* commands */