From dca4de3a631c8d647f5af83e25975c8ebcb2d967 Mon Sep 17 00:00:00 2001 From: Blaise Boscaccy Date: Mon, 28 Jul 2025 08:14:57 -0700 Subject: [PATCH 1/2] 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 | 71 +++++++++++++++++-- .../bpf/bpftool/Documentation/bpftool-gen.rst | 7 +- tools/bpf/bpftool/gen.c | 27 ++++++- tools/bpf/bpftool/main.c | 9 ++- tools/bpf/bpftool/main.h | 1 + tools/bpf/bpftool/sign.c | 17 ++++- tools/include/uapi/linux/bpf.h | 6 ++ tools/lib/bpf/libbpf.h | 3 +- tools/lib/bpf/skel_internal.h | 6 +- 10 files changed, 141 insertions(+), 12 deletions(-) diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index ae83d8649ef1c..a436a2ff49437 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -1621,6 +1621,12 @@ union bpf_attr { * verification. */ __s32 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 adb05d235011f..ce79de7f94001 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2800,14 +2800,35 @@ 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 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[SHA256_DIGEST_SIZE * 2 / sizeof(u64)]; + u64 hash[SHA256_DIGEST_SIZE / sizeof(u64)]; + int n; if (system_keyring_id_check(attr->keyring_id) == 0) key = bpf_lookup_system_key(attr->keyring_id); @@ -2828,16 +2849,58 @@ static int bpf_prog_verify_signature(struct bpf_prog *prog, union bpf_attr *attr 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; + } + /* Process the map array in reverse order to generate a hash chain + h(n) = sha256(h(n + 1), sha256(map(n))) + h(n_len) = sha256(map(n_len)) */ + for (n = attr->signature_maps_size - 1; n >= 0; n--) { + err = copy_from_bpfptr_offset(&map_fd, + make_bpfptr(attr->fd_array, is_kernel), + maps[n] * sizeof(map_fd), + sizeof(map_fd)); + if (err) + goto free_maps; + + if (n == attr->signature_maps_size - 1) + err = bpf_map_get_hash(map_fd, hash); + else { + memcpy(buffer, hash, sizeof(hash)); + err = bpf_map_get_hash(map_fd, buffer + ARRAY_SIZE(hash)); + sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash); + } + if (err) + goto free_maps; + } + /* Calculate final hash with program instructions + f_hash = sha256(sha256(prog), h(0)) */ + sha256((u8 *)prog->insnsi, prog->len * sizeof(struct bpf_insn), (u8 *)&buffer); + memcpy(buffer + ARRAY_SIZE(hash), 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/bpf/bpftool/Documentation/bpftool-gen.rst b/tools/bpf/bpftool/Documentation/bpftool-gen.rst index d0a36f442db72..b632ab87adf20 100644 --- a/tools/bpf/bpftool/Documentation/bpftool-gen.rst +++ b/tools/bpf/bpftool/Documentation/bpftool-gen.rst @@ -16,7 +16,7 @@ SYNOPSIS **bpftool** [*OPTIONS*] **gen** *COMMAND* -*OPTIONS* := { |COMMON_OPTIONS| | { **-L** | **--use-loader** } | [ { **-S** | **--sign** } {**-k** } **-i** ] } +*OPTIONS* := { |COMMON_OPTIONS| | { **-L** | **--use-loader** } | [ { **-S** | **--sign** } { **-M** | **--sign-maps** } {**-k** } **-i** ] } *COMMAND* := { **object** | **skeleton** | **help** } @@ -190,6 +190,11 @@ OPTIONS For skeletons, generate a signed skeleton. This option must be used with **-k** and **-i**. Using this flag implicitly enables **--use-loader**. +-M --sign-maps + For skeletons, generate a signed skeleton that includes a hash chain for the + skeletons maps. This option must be used with **-k** and **-i**. Using this + flag implicitly enables **--use-loader** and **--sign**. + -k Path to the private key file in PEM format, required for signing. diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c index 993c7d9484a46..1c4278e2a662b 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; @@ -822,6 +827,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\ @@ -830,6 +842,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 = skel->keyring_id; \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("\ @@ -1990,7 +2015,7 @@ static int do_help(int argc, char **argv) " %1$s %2$s help\n" "\n" " " HELP_SPEC_OPTIONS " |\n" - " {-L|--use-loader} | [ {-S|--sign } {-k} {-i} ]}\n" + " {-L|--use-loader} | [ {-S|--sign } {-M|--sign-maps } {-k} {-i} ]}\n" "", bin_name, "gen"); diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c index a829a6a49037a..47b14dcbae4ee 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; @@ -452,6 +453,7 @@ int main(int argc, char **argv) { "debug", no_argument, NULL, 'd' }, { "use-loader", no_argument, NULL, 'L' }, { "sign", no_argument, NULL, 'S' }, + { "sign-maps", no_argument, NULL, 'M' }, { "base-btf", required_argument, NULL, 'B' }, { 0 } }; @@ -478,7 +480,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': @@ -528,6 +530,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 1130299cede0b..d4e8b39d97746 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 b29d825bb1d41..50986c716292e 100644 --- a/tools/bpf/bpftool/sign.c +++ b/tools/bpf/bpftool/sign.c @@ -24,6 +24,7 @@ #include #include +#include #include "main.h" @@ -131,8 +132,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; @@ -173,7 +184,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/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index ae83d8649ef1c..a436a2ff49437 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -1621,6 +1621,12 @@ union bpf_attr { * verification. */ __s32 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/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index 5118d0a90e243..63946bdad41ad 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -1858,9 +1858,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 6a8f5c7a02eb9..11c2c19a5b2a4 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 { __s32 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); @@ -352,7 +354,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; @@ -395,6 +397,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"); From bcfed2b3655607a2045b747c9f85368f951015f2 Mon Sep 17 00:00:00 2001 From: Blaise Boscaccy Date: Tue, 23 Sep 2025 12:56:48 -0700 Subject: [PATCH 2/2] selftests/bpf: Enable map verification for some lskel tests Convert an existing signed lskel test to use the newly introduced map signature hash-chain support added to libbpf. Signed-off-by: Blaise Boscaccy --- tools/testing/selftests/bpf/Makefile | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index 0b6ee902bce51..dab70f6fa6ad8 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -500,12 +500,14 @@ LSKELS := fexit_sleep.c trace_printk.c trace_vprintk.c map_ptr_kern.c \ core_kern.c core_kern_overflow.c test_ringbuf.c \ test_ringbuf_n.c test_ringbuf_map_key.c test_ringbuf_write.c -LSKELS_SIGNED := fentry_test.c fexit_test.c atomics.c +LSKELS_SIGNED := fentry_test.c fexit_test.c + +LSKELS_SIGNED_MAPS := atomics.c # Generate both light skeleton and libbpf skeleton for these LSKELS_EXTRA := test_ksyms_module.c test_ksyms_weak.c kfunc_call_test.c \ kfunc_call_test_subprog.c -SKEL_BLACKLIST += $$(LSKELS) $$(LSKELS_SIGNED) +SKEL_BLACKLIST += $$(LSKELS) $$(LSKELS_SIGNED) $$(LSKELS_SIGNED_MAPS) test_static_linked.skel.h-deps := test_static_linked1.bpf.o test_static_linked2.bpf.o linked_funcs.skel.h-deps := linked_funcs1.bpf.o linked_funcs2.bpf.o @@ -537,6 +539,7 @@ HEADERS_FOR_BPF_OBJS := $(wildcard $(BPFDIR)/*.bpf.h) \ define DEFINE_TEST_RUNNER LSKEL_SIGN := -S -k $(PRIVATE_KEY) -i $(VERIFICATION_CERT) +LSKEL_SIGN_MAPS := -S -M -k $(PRIVATE_KEY) -i $(VERIFICATION_CERT) TRUNNER_OUTPUT := $(OUTPUT)$(if $2,/)$2 TRUNNER_BINARY := $1$(if $2,-)$2 TRUNNER_TEST_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.test.o, \ @@ -553,6 +556,7 @@ TRUNNER_BPF_SKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.skel.h, \ TRUNNER_BPF_LSKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS) $$(LSKELS_EXTRA)) TRUNNER_BPF_SKELS_LINKED := $$(addprefix $$(TRUNNER_OUTPUT)/,$(LINKED_SKELS)) TRUNNER_BPF_LSKELS_SIGNED := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS_SIGNED)) +TRUNNER_BPF_LSKELS_SIGNED_MAPS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS_SIGNED_MAPS)) TEST_GEN_FILES += $$(TRUNNER_BPF_OBJS) # Evaluate rules now with extra TRUNNER_XXX variables above already defined @@ -616,6 +620,15 @@ $(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT) $(Q)$$(BPFTOOL) gen skeleton $(LSKEL_SIGN) $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@ $(Q)rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o) +$(TRUNNER_BPF_LSKELS_SIGNED_MAPS): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT) + $$(call msg,GEN-SKEL,$(TRUNNER_BINARY) (signed),$$@) + $(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked1.o) $$< + $(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked2.o) $$(<:.o=.llinked1.o) + $(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked3.o) $$(<:.o=.llinked2.o) + $(Q)diff $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o) + $(Q)$$(BPFTOOL) gen skeleton $(LSKEL_SIGN_MAPS) $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@ + $(Q)rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o) + $(LINKED_BPF_OBJS): %: $(TRUNNER_OUTPUT)/% # .SECONDEXPANSION here allows to correctly expand %-deps variables as prerequisites @@ -666,6 +679,7 @@ $(TRUNNER_TEST_OBJS:.o=.d): $(TRUNNER_OUTPUT)/%.test.d: \ $(TRUNNER_BPF_SKELS) \ $(TRUNNER_BPF_LSKELS) \ $(TRUNNER_BPF_LSKELS_SIGNED) \ + $(TRUNNER_BPF_LSKELS_SIGNED_MAPS) \ $(TRUNNER_BPF_SKELS_LINKED) \ $$(BPFOBJ) | $(TRUNNER_OUTPUT)