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 */ From patchwork Wed May 28 21:49:04 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Blaise Boscaccy X-Patchwork-Id: 893015 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id DED69221D86; Wed, 28 May 2025 21:50:54 +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=1748469056; cv=none; b=U1mKw6kuRyYejRvm9hK5GxLqcjmquxiPc05QYqPSRndBHGeiB0HD14kz8Y1a/URmBl1nTauD57czRCY1jHzwbiz4QycTb9SAlbQmeVQiACxYh4YO0CT5w7f3dDQU1ntvlt5mhf9cEtmpttuHDIVzyznj2kAOD/5mF9RGEnAgE8k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1748469056; c=relaxed/simple; bh=Blo3ndz5JePi6aqSuyj+SYt3qoNJbS8rn9mKh/VSH3I=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pYYSoZzE7PbJHPcRW4DtVw5zV8+PmJwHSKjHZantm/n04bgbh/qv0lj6XU+5Eq03L19Afr+KWHziBsZrRy5uEggh6RjkD4vcvAZPMJsOej6wTBupp6UhYUe0a65nBonvpCy8IIuPNH/cwgZ/xijNZcQoKaSTa1Q92ZwGI+h7qfs= 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=ZgoSpT6e; 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="ZgoSpT6e" Received: from narnia.corp.microsoft.com (unknown [40.78.13.173]) by linux.microsoft.com (Postfix) with ESMTPSA id 175DF207860D; Wed, 28 May 2025 14:50:51 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 175DF207860D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1748469054; bh=WkTKL+7T37zKr2IHdBcaL/FQsk6lpPmT3/Y3bsH0FvA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=ZgoSpT6ezmYwryw3jFVrT9nlWoP7ZDY5vesrO4gzq5cr7I9j3QDLSm01gNUIvNlk3 FzwkH8Q/GOfCzUd5qzM9VOPBXz8uKMGtz2clO3/H/7g/b3eTLAp/OYdAQJxtEh8Jp0 Xk7GXyWQtjk1gIIEvTCfjeCtRd1hkRQT2B+kTtQM= 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 2/3] bpf: Support light-skeleton signatures in autogenerated code Date: Wed, 28 May 2025 14:49:04 -0700 Message-ID: <20250528215037.2081066-3-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 adds optional signature UAPI support to lskels. Additionally map freezing support is added as well. Signed-off-by: Blaise Boscaccy --- tools/lib/bpf/skel_internal.h | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h index 4d5fa079b5d6..106103b1edc7 100644 --- a/tools/lib/bpf/skel_internal.h +++ b/tools/lib/bpf/skel_internal.h @@ -61,8 +61,12 @@ struct bpf_load_and_run_opts { struct bpf_loader_ctx *ctx; const void *data; const void *insns; + const void *signature; + const void *signature_maps; __u32 data_sz; __u32 insns_sz; + __u32 signature_sz; + __u32 signature_maps_sz; const char *errstr; }; @@ -263,6 +267,17 @@ static inline int skel_map_delete_elem(int fd, const void *key) return skel_sys_bpf(BPF_MAP_DELETE_ELEM, &attr, attr_sz); } +static inline int skel_map_freeze(int fd) +{ + const size_t attr_sz = offsetofend(union bpf_attr, map_fd); + union bpf_attr attr; + + memset(&attr, 0, attr_sz); + attr.map_fd = fd; + + return skel_sys_bpf(BPF_MAP_FREEZE, &attr, attr_sz); +} + static inline int skel_map_get_fd_by_id(__u32 id) { const size_t attr_sz = offsetofend(union bpf_attr, flags); @@ -308,7 +323,7 @@ static inline int skel_link_create(int prog_fd, int target_fd, static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts) { - const size_t prog_load_attr_sz = offsetofend(union bpf_attr, fd_array); + const size_t prog_load_attr_sz = offsetofend(union bpf_attr, signature_maps_size); const size_t test_run_attr_sz = offsetofend(union bpf_attr, test); int map_fd = -1, prog_fd = -1, key = 0, err; union bpf_attr attr; @@ -327,6 +342,13 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts) goto out; } + err = skel_map_freeze(map_fd); + if (err < 0) { + opts->errstr = "failed to freeze map"; + set_err; + goto out; + } + memset(&attr, 0, prog_load_attr_sz); attr.prog_type = BPF_PROG_TYPE_SYSCALL; attr.insns = (long) opts->insns; @@ -338,6 +360,10 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts) attr.log_size = opts->ctx->log_size; attr.log_buf = opts->ctx->log_buf; attr.prog_flags = BPF_F_SLEEPABLE; + attr.signature = (long) opts->signature; + attr.signature_size = opts->signature_sz; + attr.signature_maps = (long) opts->signature_maps; + attr.signature_maps_size = opts->signature_maps_sz; err = prog_fd = skel_sys_bpf(BPF_PROG_LOAD, &attr, prog_load_attr_sz); if (prog_fd < 0) { opts->errstr = "failed to load loader prog"; From patchwork Wed May 28 21:49:05 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Blaise Boscaccy X-Patchwork-Id: 893401 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 95215221268; Wed, 28 May 2025 21:50:58 +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=1748469060; cv=none; b=ULDRkRH+StnS4779C5IR6ChTemXEpPdRLqO+9psfVEEjAPBfiT93Grpo3FcoQrTs9SM1GBT35x7a8AZMRiQVmgD5KHr7FwbwHfILyivrAP+uTHsX3XRaC8W7h4tijg1i0UXn18lLEkdruxCpVITDMew25D8/VekdNHluMsr4YIg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1748469060; c=relaxed/simple; bh=QKXLmeM2oyiRMNJR+WCzcUeqU86YQJ4nAcMTTGapJfA=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hLF80soz8GLqq4cJYMporJZTiDFLMSmMywLFbkBG85AeC3NGaer47CupMXEaDcC5Yc63GHaEKz792YP/5og7JDu9S/7wtt+hTFW3xD7oMx5Waee9j0K8/h4S2bxMWdhffvN/wFkjj89ooa7+je5kyNpQh7OJLPO5Xthetc8nCOY= 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=GUmaaosP; 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="GUmaaosP" Received: from narnia.corp.microsoft.com (unknown [40.78.13.173]) by linux.microsoft.com (Postfix) with ESMTPSA id E9C2A2078610; Wed, 28 May 2025 14:50:54 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com E9C2A2078610 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1748469058; bh=1BxLDLtBZo60Kp/D8p5d9bRrFm8Z8mtILTlhMSyJ2VU=; h=From:To:Subject:Date:In-Reply-To:References:From; b=GUmaaosPMYvCtCTsv2Kg4lPh/7MnOWk0/Vy1yUqhS30r0rmgOe9AknLajBOsoCgVe Y/1WmnXyd+7kp+swuCBwylLgmIXULEBhfMG2EolwB2+CmkH793u0hX55GvUb7wVmcZ JKhnvM6NXW4ac0o3MxzByA3Qi6tA7eYzlji5GPcQ= 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 3/3] bpftool: Allow signing of light-skeleton programs Date: Wed, 28 May 2025 14:49:05 -0700 Message-ID: <20250528215037.2081066-4-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 hash-chain signature support into bpftool. The signature generated code was adapted from sign-file and follows a similar set of command line switches. The algorithm used here supports the signature of both the loader program and it's map containing the target program. Signed-off-by: Blaise Boscaccy --- tools/bpf/bpftool/Makefile | 4 +- tools/bpf/bpftool/common.c | 204 +++++++++++++++++++++++++++++++++++++ tools/bpf/bpftool/gen.c | 66 +++++++++++- tools/bpf/bpftool/main.c | 24 ++++- tools/bpf/bpftool/main.h | 23 +++++ tools/lib/bpf/libbpf.h | 4 + 6 files changed, 321 insertions(+), 4 deletions(-) diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile index 9e9a5f006cd2..b17e295f4954 100644 --- a/tools/bpf/bpftool/Makefile +++ b/tools/bpf/bpftool/Makefile @@ -130,8 +130,8 @@ include $(FEATURES_DUMP) endif endif -LIBS = $(LIBBPF) -lelf -lz -LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz +LIBS = $(LIBBPF) -lelf -lz -lcrypto +LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz -lcrypto ifeq ($(feature-libelf-zstd),1) LIBS += -lzstd diff --git a/tools/bpf/bpftool/common.c b/tools/bpf/bpftool/common.c index ecfa790adc13..e6cfb855fc8a 100644 --- a/tools/bpf/bpftool/common.c +++ b/tools/bpf/bpftool/common.c @@ -5,6 +5,7 @@ #define _GNU_SOURCE #endif #include +#include #include #include #include @@ -31,6 +32,24 @@ #include /* libbpf_num_possible_cpus */ #include +#include +#include +#include +#include +#include +#include +#if OPENSSL_VERSION_MAJOR >= 3 +# define USE_PKCS11_PROVIDER +# include +# include +#else +# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) +# define USE_PKCS11_ENGINE +# include +# endif +#endif +#include "../../scripts/ssl-common.h" + #include "main.h" #ifndef BPF_FS_MAGIC @@ -1181,3 +1200,188 @@ int pathname_concat(char *buf, int buf_sz, const char *path, return 0; } + +static const char *key_pass; + +static int pem_pw_cb(char *buf, int len, int w, void *v) +{ + int pwlen; + + if (!key_pass) + return -1; + + pwlen = strlen(key_pass); + if (pwlen >= len) + return -1; + + strcpy(buf, key_pass); + + /* If it's wrong, don't keep trying it. */ + key_pass = NULL; + + return pwlen; +} + +static EVP_PKEY *read_private_key_pkcs11(const char *private_key_name) +{ + EVP_PKEY *pk = NULL; +#ifdef USE_PKCS11_PROVIDER + OSSL_STORE_CTX *store; + + if (!OSSL_PROVIDER_try_load(NULL, "pkcs11", true)) + ERR(1, "OSSL_PROVIDER_try_load(pkcs11)"); + if (!OSSL_PROVIDER_try_load(NULL, "default", true)) + ERR(1, "OSSL_PROVIDER_try_load(default)"); + + store = OSSL_STORE_open(private_key_name, NULL, NULL, NULL, NULL); + ERR(!store, "OSSL_STORE_open"); + + while (!OSSL_STORE_eof(store)) { + OSSL_STORE_INFO *info = OSSL_STORE_load(store); + + if (!info) { + drain_openssl_errors(__LINE__, 0); + continue; + } + if (OSSL_STORE_INFO_get_type(info) == OSSL_STORE_INFO_PKEY) { + pk = OSSL_STORE_INFO_get1_PKEY(info); + ERR(!pk, "OSSL_STORE_INFO_get1_PKEY"); + } + OSSL_STORE_INFO_free(info); + if (pk) + break; + } + OSSL_STORE_close(store); +#elif defined(USE_PKCS11_ENGINE) + ENGINE *e; + + ENGINE_load_builtin_engines(); + drain_openssl_errors(__LINE__, 1); + e = ENGINE_by_id("pkcs11"); + ERR(!e, "Load PKCS#11 ENGINE"); + if (ENGINE_init(e)) + drain_openssl_errors(__LINE__, 1); + else + ERR(1, "ENGINE_init"); + if (key_pass) + ERR(!ENGINE_ctrl_cmd_string(e, "PIN", key_pass, 0), "Set PKCS#11 PIN"); + pk = ENGINE_load_private_key(e, private_key_name, NULL, NULL); + ERR(!pk, "%s", private_key_name); +#else + fprintf(stderr, "no pkcs11 engine/provider available\n"); + exit(1); +#endif + return pk; +} + +EVP_PKEY *read_private_key(const char *private_key_name) +{ + if (!strncmp(private_key_name, "pkcs11:", 7)) { + return read_private_key_pkcs11(private_key_name); + } else { + EVP_PKEY *pk; + BIO *b; + + b = BIO_new_file(private_key_name, "rb"); + ERR(!b, "%s", private_key_name); + pk = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, + NULL); + ERR(!pk, "%s", private_key_name); + BIO_free(b); + + return pk; + } +} + +X509 *read_x509(const char *x509_name) +{ + unsigned char buf[2]; + X509 *x509_cert; + BIO *b; + int n; + + b = BIO_new_file(x509_name, "rb"); + ERR(!b, "%s", x509_name); + + /* Look at the first two bytes of the file to determine the encoding */ + n = BIO_read(b, buf, 2); + if (n != 2) { + if (BIO_should_retry(b)) { + fprintf(stderr, "%s: Read wanted retry\n", x509_name); + exit(1); + } + if (n >= 0) { + fprintf(stderr, "%s: Short read\n", x509_name); + exit(1); + } + ERR(1, "%s", x509_name); + } + + ERR(BIO_reset(b) != 0, "%s", x509_name); + + if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84) + /* Assume raw DER encoded X.509 */ + x509_cert = d2i_X509_bio(b, NULL); + else + /* Assume PEM encoded X.509 */ + x509_cert = PEM_read_bio_X509(b, NULL, NULL, NULL); + + BIO_free(b); + ERR(!x509_cert, "%s", x509_name); + + return x509_cert; +} + +BIO* generate_signature(const void *buffer, size_t length) +{ + const EVP_MD *digest_algo; + unsigned int use_signed_attrs; +#ifndef USE_PKCS7 + CMS_ContentInfo *cms = NULL; + unsigned int use_keyid = 0; +#else + PKCS7 *pkcs7 = NULL; +#endif + BIO *mem = BIO_new_mem_buf(buffer, length); + BIO *bd = BIO_new(BIO_s_mem()); + +#ifndef USE_PKCS7 + use_signed_attrs = CMS_NOATTR; +#else + use_signed_attrs = PKCS7_NOATTR; +#endif + /* Digest the module data. */ + OpenSSL_add_all_digests(); + drain_openssl_errors(__LINE__, 0); + digest_algo = EVP_get_digestbyname(hash_algo); + ERR(!digest_algo, "EVP_get_digestbyname"); + +#ifndef USE_PKCS7 + /* Load the signature message from the digest buffer. */ + cms = CMS_sign(NULL, NULL, NULL, NULL, + CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | + CMS_DETACHED | CMS_STREAM); + ERR(!cms, "CMS_sign"); + + ERR(!CMS_add1_signer(cms, x509, private_key, digest_algo, + CMS_NOCERTS | CMS_BINARY | + CMS_NOSMIMECAP | use_keyid | + use_signed_attrs), + "CMS_add1_signer"); + ERR(CMS_final(cms, mem, NULL, CMS_NOCERTS | CMS_BINARY) != 1, + "CMS_final"); + +#else + pkcs7 = PKCS7_sign(x509, private_key, NULL, mem, + PKCS7_NOCERTS | PKCS7_BINARY | + PKCS7_DETACHED | use_signed_attrs); + ERR(!pkcs7, "PKCS7_sign"); +#endif + +#ifndef USE_PKCS7 + ERR(i2d_CMS_bio_stream(bd, cms, NULL, 0) != 1, "%s", "bpftool"); +#else + ERR(i2d_PKCS7_bio(bd, pkcs7) != 1, "%s", "bpftool"); +#endif + return bd; +} diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c index 67a60114368f..318b1f36d869 100644 --- a/tools/bpf/bpftool/gen.c +++ b/tools/bpf/bpftool/gen.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "json_writer.h" #include "main.h" @@ -493,6 +494,30 @@ static size_t bpf_map_mmap_sz(const struct bpf_map *map) return map_sz; } +static int sign_loader_and_map(struct gen_loader_opts *opts) +{ + BIO *bo; + BUF_MEM *bptr; + unsigned char hash[SHA256_DIGEST_LENGTH * 2]; + unsigned char term[SHA256_DIGEST_LENGTH]; + + if (!x509) + return 0; + + SHA256((const unsigned char *)opts->insns, opts->insns_sz, hash); + SHA256((const unsigned char *)opts->data, opts->data_sz, hash + SHA256_DIGEST_LENGTH); + SHA256(hash, sizeof(hash), term); + + bo = generate_signature(term, sizeof(term)); + if (IS_ERR(bo)) + return -EINVAL; + BIO_get_mem_ptr(bo, &bptr); + opts->signature = bptr->data; + opts->signature_sz = bptr->length; + + return 0; +} + /* Emit type size asserts for all top-level fields in memory-mapped internal maps. */ static void codegen_asserts(struct bpf_object *obj, const char *obj_name) { @@ -701,6 +726,11 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h p_err("failed to load object file"); goto out; } + err = sign_loader_and_map(&opts); + if (err) { + p_err("failed to sign loader"); + goto out; + } /* If there was no error during load then gen_loader_opts * are populated with the loader program. */ @@ -778,20 +808,54 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h static const char opts_insn[] __attribute__((__aligned__(8))) = \"\\\n\ "); print_hex(opts.insns, opts.insns_sz); - codegen("\ + if (opts.signature) { + codegen("\ \n\ \"; \n\ + static const char opts_signature[] __attribute__((__aligned__(8))) = \"\\\n\ + "); + print_hex(opts.signature, opts.signature_sz); + codegen("\ + \n\ + \"; \n\ + static const int opts_signature_maps[1] __attribute__((__aligned__(8))) = {0}; \n\ + "); + codegen("\ + \n\ \n\ opts.ctx = (struct bpf_loader_ctx *)skel; \n\ opts.data_sz = sizeof(opts_data) - 1; \n\ opts.data = (void *)opts_data; \n\ opts.insns_sz = sizeof(opts_insn) - 1; \n\ opts.insns = (void *)opts_insn; \n\ + opts.signature_sz = sizeof(opts_signature) - 1; \n\ + opts.signature = (void *)opts_signature; \n\ + opts.signature_maps_sz = 1; \n\ + opts.signature_maps = (void *)opts_signature_maps; \n\ \n\ err = bpf_load_and_run(&opts); \n\ if (err < 0) \n\ return err; \n\ "); + + } else { + codegen("\ + \n\ + \"; \n\ + \n\ + opts.ctx = (struct bpf_loader_ctx *)skel; \n\ + opts.data_sz = sizeof(opts_data) - 1; \n\ + opts.data = (void *)opts_data; \n\ + opts.insns_sz = sizeof(opts_insn) - 1; \n\ + opts.insns = (void *)opts_insn; \n\ + opts.signature_sz = 0; \n\ + opts.signature = NULL; \n\ + \n\ + err = bpf_load_and_run(&opts); \n\ + if (err < 0) \n\ + return err; \n\ + "); + } bpf_object__for_each_map(map, obj) { const char *mmap_flags; diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c index cd5963cb6058..01020e5f37c2 100644 --- a/tools/bpf/bpftool/main.c +++ b/tools/bpf/bpftool/main.c @@ -33,6 +33,9 @@ bool relaxed_maps; bool use_loader; struct btf *base_btf; struct hashmap *refs_table; +const char *hash_algo; +EVP_PKEY *private_key; +X509 *x509; static void __noreturn clean_and_exit(int i) { @@ -473,7 +476,7 @@ int main(int argc, char **argv) bin_name = "bpftool"; opterr = 0; - while ((opt = getopt_long(argc, argv, "VhpjfLmndB:l", + while ((opt = getopt_long(argc, argv, "VhpjfLmndB:lH:lP:lX:l", options, NULL)) >= 0) { switch (opt) { case 'V': @@ -519,6 +522,25 @@ int main(int argc, char **argv) case 'L': use_loader = true; break; + case 'H': + hash_algo = optarg; + break; + case 'P': + private_key = read_private_key(optarg); + if (!private_key) { + p_err("failed to parse private key '%s': %d\n", + optarg, -errno); + return -1; + } + break; + case 'X': + x509 = read_x509(optarg); + if (!x509) { + p_err("failed to parse x509 '%s': %d\n", + optarg, -errno); + return -1; + } + break; default: p_err("unrecognized option '%s'", argv[optind - 1]); if (json_output) diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h index 9eb764fe4cc8..2f4aee1a8da7 100644 --- a/tools/bpf/bpftool/main.h +++ b/tools/bpf/bpftool/main.h @@ -16,6 +16,22 @@ #include #include +#include +#include +#include +#include +#include +#if OPENSSL_VERSION_MAJOR >= 3 +# define USE_PKCS11_PROVIDER +# include +# include +#else +# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) +# define USE_PKCS11_ENGINE +# include +# endif +#endif + #include "json_writer.h" /* Make sure we do not use kernel-only integer typedefs */ @@ -84,6 +100,9 @@ extern bool relaxed_maps; extern bool use_loader; extern struct btf *base_btf; extern struct hashmap *refs_table; +extern const char *hash_algo; +extern EVP_PKEY *private_key; +extern X509 *x509; void __printf(1, 2) p_err(const char *fmt, ...); void __printf(1, 2) p_info(const char *fmt, ...); @@ -271,4 +290,8 @@ int pathname_concat(char *buf, int buf_sz, const char *path, /* print netfilter bpf_link info */ void netfilter_dump_plain(const struct bpf_link_info *info); void netfilter_dump_json(const struct bpf_link_info *info, json_writer_t *wtr); + +X509 *read_x509(const char *x509_name); +EVP_PKEY *read_private_key(const char *private_key_name); +BIO *generate_signature(const void *buffer, size_t length); #endif diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index e0605403f977..c6c67e8931a6 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -1782,8 +1782,12 @@ struct gen_loader_opts { size_t sz; /* size of this struct, for forward/backward compatibility */ const char *data; const char *insns; + const char *signature; + const int *signature_maps; __u32 data_sz; __u32 insns_sz; + __u32 signature_sz; + __u32 signature_maps_sz; }; #define gen_loader_opts__last_field insns_sz