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. Tested-by: syzbot@syzkaller.appspotmail.com Signed-off-by: Blaise Boscaccy --- include/uapi/linux/bpf.h | 6 +++ kernel/bpf/syscall.c | 73 ++++++++++++++++++++++++++++++++-- tools/include/uapi/linux/bpf.h | 6 +++ 3 files changed, 81 insertions(+), 4 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 a48fa86f82a7f..f728f663765c4 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2802,14 +2802,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); @@ -2830,16 +2851,60 @@ 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 (IS_ERR(maps)) { + err = PTR_ERR(maps); + 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/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 */ -- 2.48.1 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 f00587d4ede68..ae03fa9001a61 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) -- 2.48.1 Add a new mode of operation for program loading which supports the generation of signed hash chains for light skeletons, using the new signed map hash chain UAPI additions. e.g bpftool prog load -S -M -k -i fentry_test.bpf.o The -M or --sign-maps command line switch is introduced. It generates a hash chain such that: H(program, maps) = sha256(sha256(program), sha256(map[0])) Signed-off-by: Blaise Boscaccy --- .../bpf/bpftool/Documentation/bpftool-gen.rst | 7 ++++- tools/bpf/bpftool/bash-completion/bpftool | 2 +- tools/bpf/bpftool/gen.c | 27 ++++++++++++++++++- tools/bpf/bpftool/main.c | 9 ++++++- tools/bpf/bpftool/main.h | 1 + tools/bpf/bpftool/sign.c | 16 ++++++++--- tools/lib/bpf/libbpf.h | 3 ++- tools/lib/bpf/skel_internal.h | 6 ++++- 8 files changed, 62 insertions(+), 9 deletions(-) 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/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool index 53bcfeb1a76e6..f8c217f09989c 100644 --- a/tools/bpf/bpftool/bash-completion/bpftool +++ b/tools/bpf/bpftool/bash-completion/bpftool @@ -262,7 +262,7 @@ _bpftool() # Deal with options if [[ ${words[cword]} == -* ]]; then local c='--version --json --pretty --bpffs --mapcompat --debug \ - --use-loader --base-btf --sign -i -k' + --use-loader --base-btf --sign --sign-maps -i -k' COMPREPLY=( $( compgen -W "$c" -- "$cur" ) ) return 0 fi 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 b34f74d210e9c..6338f0309cd91 100644 --- a/tools/bpf/bpftool/sign.c +++ b/tools/bpf/bpftool/sign.c @@ -23,6 +23,7 @@ #include #include +#include #include "main.h" @@ -130,8 +131,17 @@ 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); + libbpf_sha256(opts->data, opts->data_sz, hash + SHA256_DIGEST_LENGTH); + libbpf_sha256(hash, sizeof(hash), 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; @@ -172,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 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"); -- 2.48.1