@@ -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;
@@ -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
};
@@ -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 */
@@ -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)
{
@@ -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;
@@ -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 */
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(-)