From patchwork Wed May 28 21:49:03 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Blaise Boscaccy X-Patchwork-Id: 893402 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 3148B221268; Wed, 28 May 2025 21:50:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=13.77.154.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1748469053; cv=none; b=bUWifxK3ctBYmQec0bprgdH/kWUNt8V6vCybeSh2YHdNlKhZQxKjsPCFl/ibmmOn8w96HBH2hn3b81Mfi0WoPHPb1Qxe/XFLqYz22JUishTjV1WfXYnrLfPMrkcDTQB2PK+vb4HmoGyAyX397QvJ2iHwrxfPZRPQuFdx2eZj9fE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1748469053; c=relaxed/simple; bh=rDkEcxMkbT0DuRk/YrqYeaewibwN4ZOIrRLRLt+cS3M=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kgKxYV49JR/O7p7L7lYi9HYTEGnSIgS+a65MQJzeHMjfb+AUbtkAKAj9CKvsl5z8P6fIxU51yMNkzGtjB7DH9crrhLStWlGYW3c2kLm5qTovw14uzcFHS8Qva6wzznqYz8BQ6Sq2ziHrJsbtgN9NiYqnnjNeUE6b6uZ0MtfktxI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com; spf=pass smtp.mailfrom=linux.microsoft.com; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b=lETjtr8y; arc=none smtp.client-ip=13.77.154.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b="lETjtr8y" Received: from narnia.corp.microsoft.com (unknown [40.78.13.173]) by linux.microsoft.com (Postfix) with ESMTPSA id 8E43A2068337; Wed, 28 May 2025 14:50:48 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 8E43A2068337 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1748469051; bh=T5/5RHou0NeTf3Q95/w/eEbUSGteDobbOkyPaHO3kaU=; h=From:To:Subject:Date:In-Reply-To:References:From; b=lETjtr8y/qfQwCdFbafxuIwVDkxAcafRHrh2HZYGD2Su4s6eVafBnwoZPoQKVFdFN rsvPdte1aQ83hOKiG8y4YR5M09U3jS8V2o0AOaU05zQWtTvRuCJYCiHWwmZz6yLAp7 gzHeS2r7yfalR2WgLjlOjdKG8nrKj+7v+t9qYlCE= From: Blaise Boscaccy To: Paul Moore , bboscaccy@linux.microsoft.com, jarkko@kernel.org, zeffron@riotgames.com, xiyou.wangcong@gmail.com, kysrinivasan@gmail.com, code@tyhicks.com, linux-security-module@vger.kernel.org, roberto.sassu@huawei.com, James.Bottomley@hansenpartnership.com, Alexei Starovoitov , Daniel Borkmann , John Fastabend , Andrii Nakryiko , Martin KaFai Lau , Eduard Zingerman , Song Liu , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , David Howells , Lukas Wunner , Ignat Korchagin , Quentin Monnet , Jason Xing , Willem de Bruijn , Anton Protopopov , Jordan Rome , Martin Kelly , Alan Maguire , Matteo Croce , bpf@vger.kernel.org, linux-kernel@vger.kernel.org, keyrings@vger.kernel.org, linux-crypto@vger.kernel.org Subject: [PATCH 1/3] bpf: Add bpf_check_signature Date: Wed, 28 May 2025 14:49:03 -0700 Message-ID: <20250528215037.2081066-2-bboscaccy@linux.microsoft.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250528215037.2081066-1-bboscaccy@linux.microsoft.com> References: <20250528215037.2081066-1-bboscaccy@linux.microsoft.com> Precedence: bulk X-Mailing-List: linux-crypto@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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/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 #include #include +#include #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 #include #include +#include +#include #include #include @@ -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 */