From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 1B62F2951DD; Tue, 5 Aug 2025 18:28:22 +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=1754418504; cv=none; b=G31v3ytwvpZx8Oqo+LN2L8Y1yE4+yAFrAoey1V4Wo08eZrqVZz+u7dV7Z3UIyJErKbg9bp3ieUsJ4MLqhv9uUw6nnnp/KqbRmBxq9fCIm/3C9NBUlH4RleOqOQ5fFnLDM0kV8ae0/pFejo1kw598LXuIyrwaTLugQKqk4sq+/Xo= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1754418504; c=relaxed/simple; bh=f/UM9JLqI2Jc4ZU+EZVhHs5u5pZuurKc9RQzZPhr+fM=; h=From:To:Cc:Subject:In-Reply-To:References:Date:Message-ID: MIME-Version:Content-Type; b=FfQXxcDZ9NWmcX4tuCIRq/XGR/8N1GSSJQGwZGoGMU8rVn9VRNEPimAxYv+JCi1MD9vOmqqVOnkVlymMpQDCDW8A3fy6sqzbNtWsXSFNq1BJPk9PTcKGiWb/CF7T4tiSPK8gCegfRnvbRD+rLTtXmQRCpY3elb2Zkw5byl93JeY= 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=ADh+JXQA; 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="ADh+JXQA" Received: from narnia (unknown [40.118.131.60]) by linux.microsoft.com (Postfix) with ESMTPSA id 9D9F3202189A; Tue, 5 Aug 2025 11:28:21 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 9D9F3202189A DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1754418502; bh=3KeCsWFKQKXPFRMBs18dddePu2U+xw5tInmf8PLQlAM=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=ADh+JXQAARJkGxcYgiul0PAr6Ei2X6T0AaklgXhRHEJRqfTSPaueM4NH77gDpei2T 9TkO6tUBTJBTy9W0LEBWd2R+9K/j2WuWskVn0m+MLNKw0FnslGAIizmrf3Re9x+uYx 032JJgaBHLfHbJUDs2u48ZIvNocy+oa7a3hcyK2E= From: Blaise Boscaccy To: KP Singh , bpf@vger.kernel.org, linux-security-module@vger.kernel.org Cc: paul@paul-moore.com, kys@microsoft.com, ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, KP Singh , James.Bottomley@HansenPartnership.com, wufan@kernel.org Subject: Re: [PATCH v2 08/13] bpf: Implement signature verification for BPF programs In-Reply-To: <20250721211958.1881379-9-kpsingh@kernel.org> References: <20250721211958.1881379-1-kpsingh@kernel.org> <20250721211958.1881379-9-kpsingh@kernel.org> Date: Tue, 05 Aug 2025 11:28:20 -0700 Message-ID: <87sei58vy3.fsf@microsoft.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain KP Singh 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 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 --- 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(-) diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index b42c3740e053e..c83f2a34674fd 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -1617,6 +1617,12 @@ union bpf_attr { * verification. */ __u32 keyring_id; + /* Pointer to a buffer containing the maps used in the signature + * hash chain of the BPF program. + */ + __aligned_u64 signature_maps; + /* Size of the signature maps buffer. */ + __u32 signature_maps_size; }; struct { /* anonymous struct used by BPF_OBJ_* commands */ 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); +} + 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]; + 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) { diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index b42c3740e053e..c83f2a34674fd 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -1617,6 +1617,12 @@ union bpf_attr { * verification. */ __u32 keyring_id; + /* Pointer to a buffer containing the maps used in the signature + * hash chain of the BPF program. + */ + __aligned_u64 signature_maps; + /* Size of the signature maps buffer. */ + __u32 signature_maps_size; }; struct { /* anonymous struct used by BPF_OBJ_* commands */ -- 2.48.1 And here is a reference usage implementation: From: Blaise Boscaccy Date: Mon, 28 Jul 2025 21:30:56 -0700 Subject: libbpf: Add hash chain signing support to light skeletons. This patch introduces a hash chain signing support for light-skeleton assets. A new flag '-M' is added which constructs a hash chain with the loader program and the target payload. Signed-off-by: Blaise Boscaccy --- tools/bpf/bpftool/gen.c | 25 +++++++++++++++++++++++++ tools/bpf/bpftool/main.c | 8 +++++++- tools/bpf/bpftool/main.h | 1 + tools/bpf/bpftool/sign.c | 17 ++++++++++++++--- tools/lib/bpf/libbpf.h | 3 ++- tools/lib/bpf/skel_internal.h | 6 +++++- 6 files changed, 54 insertions(+), 6 deletions(-) diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c index ab6fc86598ad3..e660fbc701c5d 100644 --- a/tools/bpf/bpftool/gen.c +++ b/tools/bpf/bpftool/gen.c @@ -699,6 +699,9 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h if (sign_progs) opts.gen_hash = true; + if (sign_maps) + opts.sign_maps = true; + err = bpf_object__gen_loader(obj, &opts); if (err) return err; @@ -793,6 +796,8 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h if (sign_progs) { sopts.insns = opts.insns; sopts.insns_sz = opts.insns_sz; + sopts.data = opts.data; + sopts.data_sz = opts.data_sz; sopts.excl_prog_hash = prog_sha; sopts.excl_prog_hash_sz = sizeof(prog_sha); sopts.signature = sig_buf; @@ -821,6 +826,13 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h \n\ \";\n"); + if (sign_maps) { + codegen("\ + \n\ + static const int opts_signature_maps[1] __attribute__((__aligned__(8))) = {0}; \n\ + "); + } + codegen("\ \n\ opts.signature = (void *)opts_sig; \n\ @@ -829,6 +841,19 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h opts.excl_prog_hash_sz = sizeof(opts_excl_hash) - 1; \n\ opts.keyring_id = KEY_SPEC_SESSION_KEYRING; \n\ "); + if (sign_maps) { + codegen("\ + \n\ + opts.signature_maps = (void *)opts_signature_maps; \n\ + opts.signature_maps_sz = 1; \n\ + "); + } else { + codegen("\ + \n\ + opts.signature_maps = (void *)NULL; \n\ + opts.signature_maps_sz = 0; \n\ + "); + } } codegen("\ diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c index fc25bb390ec71..287e8205494cb 100644 --- a/tools/bpf/bpftool/main.c +++ b/tools/bpf/bpftool/main.c @@ -34,6 +34,7 @@ bool use_loader; struct btf *base_btf; struct hashmap *refs_table; bool sign_progs; +bool sign_maps; const char *private_key_path; const char *cert_path; @@ -477,7 +478,7 @@ int main(int argc, char **argv) bin_name = "bpftool"; opterr = 0; - while ((opt = getopt_long(argc, argv, "VhpjfLmndSi:k:B:l", + while ((opt = getopt_long(argc, argv, "VhpjfLmndSMi:k:B:l", options, NULL)) >= 0) { switch (opt) { case 'V': @@ -527,6 +528,11 @@ int main(int argc, char **argv) sign_progs = true; use_loader = true; break; + case 'M': + sign_maps = true; + sign_progs = true; + use_loader = true; + break; case 'k': private_key_path = optarg; break; diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h index f921af3cda87f..805c3d87a1330 100644 --- a/tools/bpf/bpftool/main.h +++ b/tools/bpf/bpftool/main.h @@ -92,6 +92,7 @@ extern bool use_loader; extern struct btf *base_btf; extern struct hashmap *refs_table; extern bool sign_progs; +extern bool sign_maps; extern const char *private_key_path; extern const char *cert_path; diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c index f0b5dd10a46b2..d5514b7d2b82d 100644 --- a/tools/bpf/bpftool/sign.c +++ b/tools/bpf/bpftool/sign.c @@ -22,6 +22,7 @@ #include #include +#include #include "main.h" @@ -129,8 +130,18 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts) long actual_sig_len = 0; X509 *x509 = NULL; int err = 0; - - bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz); + unsigned char hash[SHA256_DIGEST_LENGTH * 2]; + unsigned char term[SHA256_DIGEST_LENGTH]; + + if (sign_maps) { + libbpf_sha256(opts->insns, opts->insns_sz, hash, SHA256_DIGEST_LENGTH); + libbpf_sha256(opts->data, opts->data_sz, hash + SHA256_DIGEST_LENGTH, + SHA256_DIGEST_LENGTH); + libbpf_sha256(hash, sizeof(hash), term, sizeof(term)); + bd_in = BIO_new_mem_buf(term, sizeof(term)); + } else { + bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz); + } if (!bd_in) { err = -ENOMEM; goto cleanup; @@ -171,7 +182,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts) EVP_Digest(opts->insns, opts->insns_sz, opts->excl_prog_hash, &opts->excl_prog_hash_sz, EVP_sha256(), NULL); - bd_out = BIO_new(BIO_s_mem()); + bd_out = BIO_new(BIO_s_mem()); if (!bd_out) { err = -ENOMEM; goto cleanup; diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index 7cad8470d9ebe..aad0288cd05e3 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -1827,9 +1827,10 @@ struct gen_loader_opts { __u32 data_sz; __u32 insns_sz; bool gen_hash; + bool sign_maps; }; -#define gen_loader_opts__last_field gen_hash +#define gen_loader_opts__last_field sign_maps LIBBPF_API int bpf_object__gen_loader(struct bpf_object *obj, struct gen_loader_opts *opts); diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h index 5b6d1b09dc8a6..c25a4f1308e44 100644 --- a/tools/lib/bpf/skel_internal.h +++ b/tools/lib/bpf/skel_internal.h @@ -74,6 +74,8 @@ struct bpf_load_and_run_opts { __u32 keyring_id; void * excl_prog_hash; __u32 excl_prog_hash_sz; + const int *signature_maps; + __u32 signature_maps_sz; }; long kern_sys_bpf(__u32 cmd, void *attr, __u32 attr_size); @@ -351,7 +353,7 @@ static inline int skel_map_freeze(int 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, keyring_id); + 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; @@ -394,6 +396,8 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts) #ifndef __KERNEL__ 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; #else if (opts->signature || opts->signature_sz) pr_warn("signatures are not supported from bpf_preload\n"); -- 2.48.1 -blaise