Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bpftool: Add support for signing BPF programs

Two modes of operation being added:

Add two modes of operation:

* For prog load, allow signing a program immediately before loading. This
is essential for command-line testing and administration.

bpftool prog load -S -k <private_key> -i <identity_cert> fentry_test.bpf.o

* For gen skeleton, embed a pre-generated signature into the C skeleton
file. This supports the use of signed programs in compiled applications.

bpftool gen skeleton -S -k <private_key> -i <identity_cert> fentry_test.bpf.o

Generation of the loader program and its metadata map is implemented in
libbpf (bpf_obj__gen_loader). bpftool generates a skeleton that loads
the program and automates the required steps: freezing the map, creating
an exclusive map, loading, and running. Users can use standard libbpf
APIs directly or integrate loader program generation into their own
toolchains.

Signed-off-by: KP Singh <kpsingh@kernel.org>
Acked-by: Quentin Monnet <qmo@kernel.org>
Link: https://lore.kernel.org/r/20250921160120.9711-5-kpsingh@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

KP Singh and committed by
Alexei Starovoitov
40863f4d ea923080

+372 -11
+12 -1
tools/bpf/bpftool/Documentation/bpftool-gen.rst
··· 16 16 17 17 **bpftool** [*OPTIONS*] **gen** *COMMAND* 18 18 19 - *OPTIONS* := { |COMMON_OPTIONS| | { **-L** | **--use-loader** } } 19 + *OPTIONS* := { |COMMON_OPTIONS| | { **-L** | **--use-loader** } | [ { **-S** | **--sign** } {**-k** <private_key.pem>} **-i** <certificate.x509> ] } 20 20 21 21 *COMMAND* := { **object** | **skeleton** | **help** } 22 22 ··· 185 185 For skeletons, generate a "light" skeleton (also known as "loader" 186 186 skeleton). A light skeleton contains a loader eBPF program. It does not use 187 187 the majority of the libbpf infrastructure, and does not need libelf. 188 + 189 + -S, --sign 190 + For skeletons, generate a signed skeleton. This option must be used with 191 + **-k** and **-i**. Using this flag implicitly enables **--use-loader**. 192 + 193 + -k <private_key.pem> 194 + Path to the private key file in PEM format, required for signing. 195 + 196 + -i <certificate.x509> 197 + Path to the X.509 certificate file in PEM or DER format, required for 198 + signing. 188 199 189 200 EXAMPLES 190 201 ========
+13 -1
tools/bpf/bpftool/Documentation/bpftool-prog.rst
··· 18 18 19 19 *OPTIONS* := { |COMMON_OPTIONS| | 20 20 { **-f** | **--bpffs** } | { **-m** | **--mapcompat** } | { **-n** | **--nomount** } | 21 - { **-L** | **--use-loader** } } 21 + { **-L** | **--use-loader** } | [ { **-S** | **--sign** } **-k** <private_key.pem> **-i** <certificate.x509> ] } 22 22 23 23 *COMMANDS* := 24 24 { **show** | **list** | **dump xlated** | **dump jited** | **pin** | **load** | ··· 247 247 the **bpf_trace_printk**\ () helper to log each step of loading BTF, 248 248 creating the maps, and loading the programs (see **bpftool prog tracelog** 249 249 as a way to dump those messages). 250 + 251 + -S, --sign 252 + Enable signing of the BPF program before loading. This option must be 253 + used with **-k** and **-i**. Using this flag implicitly enables 254 + **--use-loader**. 255 + 256 + -k <private_key.pem> 257 + Path to the private key file in PEM format, required when signing. 258 + 259 + -i <certificate.x509> 260 + Path to the X.509 certificate file in PEM or DER format, required when 261 + signing. 250 262 251 263 EXAMPLES 252 264 ========
+3 -3
tools/bpf/bpftool/Makefile
··· 130 130 endif 131 131 endif 132 132 133 - LIBS = $(LIBBPF) -lelf -lz 134 - LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz 133 + LIBS = $(LIBBPF) -lelf -lz -lcrypto 134 + LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz -lcrypto 135 135 136 136 ifeq ($(feature-libelf-zstd),1) 137 137 LIBS += -lzstd ··· 194 194 195 195 BPFTOOL_BOOTSTRAP := $(BOOTSTRAP_OUTPUT)bpftool 196 196 197 - BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o) 197 + BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o sign.o) 198 198 $(BOOTSTRAP_OBJS): $(LIBBPF_BOOTSTRAP) 199 199 200 200 OBJS = $(patsubst %.c,$(OUTPUT)%.o,$(SRCS)) $(OUTPUT)disasm.o
+4
tools/bpf/bpftool/cgroup.c
··· 2 2 // Copyright (C) 2017 Facebook 3 3 // Author: Roman Gushchin <guro@fb.com> 4 4 5 + #undef GCC_VERSION 6 + #ifndef _GNU_SOURCE 7 + #define _GNU_SOURCE 8 + #endif 5 9 #define _XOPEN_SOURCE 500 6 10 #include <errno.h> 7 11 #include <fcntl.h>
+64 -4
tools/bpf/bpftool/gen.c
··· 688 688 static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *header_guard) 689 689 { 690 690 DECLARE_LIBBPF_OPTS(gen_loader_opts, opts); 691 + struct bpf_load_and_run_opts sopts = {}; 692 + char sig_buf[MAX_SIG_SIZE]; 693 + __u8 prog_sha[SHA256_DIGEST_LENGTH]; 691 694 struct bpf_map *map; 695 + 692 696 char ident[256]; 693 697 int err = 0; 698 + 699 + if (sign_progs) 700 + opts.gen_hash = true; 694 701 695 702 err = bpf_object__gen_loader(obj, &opts); 696 703 if (err) ··· 708 701 p_err("failed to load object file"); 709 702 goto out; 710 703 } 704 + 711 705 /* If there was no error during load then gen_loader_opts 712 706 * are populated with the loader program. 713 707 */ ··· 788 780 print_hex(opts.insns, opts.insns_sz); 789 781 codegen("\ 790 782 \n\ 791 - \"; \n\ 792 - \n\ 783 + \";\n"); 784 + 785 + if (sign_progs) { 786 + sopts.insns = opts.insns; 787 + sopts.insns_sz = opts.insns_sz; 788 + sopts.excl_prog_hash = prog_sha; 789 + sopts.excl_prog_hash_sz = sizeof(prog_sha); 790 + sopts.signature = sig_buf; 791 + sopts.signature_sz = MAX_SIG_SIZE; 792 + 793 + err = bpftool_prog_sign(&sopts); 794 + if (err < 0) { 795 + p_err("failed to sign program"); 796 + goto out; 797 + } 798 + 799 + codegen("\ 800 + \n\ 801 + static const char opts_sig[] __attribute__((__aligned__(8))) = \"\\\n\ 802 + "); 803 + print_hex((const void *)sig_buf, sopts.signature_sz); 804 + codegen("\ 805 + \n\ 806 + \";\n"); 807 + 808 + codegen("\ 809 + \n\ 810 + static const char opts_excl_hash[] __attribute__((__aligned__(8))) = \"\\\n\ 811 + "); 812 + print_hex((const void *)prog_sha, sizeof(prog_sha)); 813 + codegen("\ 814 + \n\ 815 + \";\n"); 816 + 817 + codegen("\ 818 + \n\ 819 + opts.signature = (void *)opts_sig; \n\ 820 + opts.signature_sz = sizeof(opts_sig) - 1; \n\ 821 + opts.excl_prog_hash = (void *)opts_excl_hash; \n\ 822 + opts.excl_prog_hash_sz = sizeof(opts_excl_hash) - 1; \n\ 823 + opts.keyring_id = skel->keyring_id; \n\ 824 + "); 825 + } 826 + 827 + codegen("\ 828 + \n\ 793 829 opts.ctx = (struct bpf_loader_ctx *)skel; \n\ 794 830 opts.data_sz = sizeof(opts_data) - 1; \n\ 795 831 opts.data = (void *)opts_data; \n\ ··· 1292 1240 err = -errno; 1293 1241 libbpf_strerror(err, err_buf, sizeof(err_buf)); 1294 1242 p_err("failed to open BPF object file: %s", err_buf); 1295 - goto out; 1243 + goto out_obj; 1296 1244 } 1297 1245 1298 1246 bpf_object__for_each_map(map, obj) { ··· 1405 1353 } 1406 1354 1407 1355 printf("\t} links;\n"); 1356 + } 1357 + 1358 + if (sign_progs) { 1359 + codegen("\ 1360 + \n\ 1361 + __s32 keyring_id; \n\ 1362 + "); 1408 1363 } 1409 1364 1410 1365 if (btf) { ··· 1611 1552 err = 0; 1612 1553 out: 1613 1554 bpf_object__close(obj); 1555 + out_obj: 1614 1556 if (obj_data) 1615 1557 munmap(obj_data, mmap_sz); 1616 1558 close(fd); ··· 1990 1930 " %1$s %2$s help\n" 1991 1931 "\n" 1992 1932 " " HELP_SPEC_OPTIONS " |\n" 1993 - " {-L|--use-loader} }\n" 1933 + " {-L|--use-loader} | [ {-S|--sign } {-k} <private_key.pem> {-i} <certificate.x509> ]}\n" 1994 1934 "", 1995 1935 bin_name, "gen"); 1996 1936
+25 -1
tools/bpf/bpftool/main.c
··· 33 33 bool use_loader; 34 34 struct btf *base_btf; 35 35 struct hashmap *refs_table; 36 + bool sign_progs; 37 + const char *private_key_path; 38 + const char *cert_path; 36 39 37 40 static void __noreturn clean_and_exit(int i) 38 41 { ··· 451 448 { "nomount", no_argument, NULL, 'n' }, 452 449 { "debug", no_argument, NULL, 'd' }, 453 450 { "use-loader", no_argument, NULL, 'L' }, 451 + { "sign", no_argument, NULL, 'S' }, 454 452 { "base-btf", required_argument, NULL, 'B' }, 455 453 { 0 } 456 454 }; ··· 478 474 bin_name = "bpftool"; 479 475 480 476 opterr = 0; 481 - while ((opt = getopt_long(argc, argv, "VhpjfLmndB:l", 477 + while ((opt = getopt_long(argc, argv, "VhpjfLmndSi:k:B:l", 482 478 options, NULL)) >= 0) { 483 479 switch (opt) { 484 480 case 'V': ··· 524 520 case 'L': 525 521 use_loader = true; 526 522 break; 523 + case 'S': 524 + sign_progs = true; 525 + use_loader = true; 526 + break; 527 + case 'k': 528 + private_key_path = optarg; 529 + break; 530 + case 'i': 531 + cert_path = optarg; 532 + break; 527 533 default: 528 534 p_err("unrecognized option '%s'", argv[optind - 1]); 529 535 if (json_output) ··· 547 533 argv += optind; 548 534 if (argc < 0) 549 535 usage(); 536 + 537 + if (sign_progs && (private_key_path == NULL || cert_path == NULL)) { 538 + p_err("-i <identity_x509_cert> and -k <private_key> must be supplied with -S for signing"); 539 + return -EINVAL; 540 + } 541 + 542 + if (!sign_progs && (private_key_path != NULL || cert_path != NULL)) { 543 + p_err("--sign (or -S) must be explicitly passed with -i <identity_x509_cert> and -k <private_key> to sign the programs"); 544 + return -EINVAL; 545 + } 550 546 551 547 if (version_requested) 552 548 ret = do_version(argc, argv);
+11
tools/bpf/bpftool/main.h
··· 6 6 7 7 /* BFD and kernel.h both define GCC_VERSION, differently */ 8 8 #undef GCC_VERSION 9 + #ifndef _GNU_SOURCE 10 + #define _GNU_SOURCE 11 + #endif 9 12 #include <stdbool.h> 10 13 #include <stdio.h> 14 + #include <errno.h> 11 15 #include <stdlib.h> 16 + #include <bpf/skel_internal.h> 12 17 #include <linux/bpf.h> 13 18 #include <linux/compiler.h> 14 19 #include <linux/kernel.h> ··· 57 52 }) 58 53 59 54 #define ERR_MAX_LEN 1024 55 + #define MAX_SIG_SIZE 4096 60 56 61 57 #define BPF_TAG_FMT "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" 62 58 ··· 91 85 extern bool use_loader; 92 86 extern struct btf *base_btf; 93 87 extern struct hashmap *refs_table; 88 + extern bool sign_progs; 89 + extern const char *private_key_path; 90 + extern const char *cert_path; 94 91 95 92 void __printf(1, 2) p_err(const char *fmt, ...); 96 93 void __printf(1, 2) p_info(const char *fmt, ...); ··· 293 284 int read_kernel_config(const struct kernel_config_option *requested_options, 294 285 size_t num_options, char **out_values, 295 286 const char *define_prefix); 287 + int bpftool_prog_sign(struct bpf_load_and_run_opts *opts); 288 + __u32 register_session_key(const char *key_der_path); 296 289 #endif
+28 -1
tools/bpf/bpftool/prog.c
··· 23 23 #include <linux/err.h> 24 24 #include <linux/perf_event.h> 25 25 #include <linux/sizes.h> 26 + #include <linux/keyctl.h> 26 27 27 28 #include <bpf/bpf.h> 28 29 #include <bpf/btf.h> ··· 1931 1930 { 1932 1931 struct bpf_load_and_run_opts opts = {}; 1933 1932 struct bpf_loader_ctx *ctx; 1933 + char sig_buf[MAX_SIG_SIZE]; 1934 + __u8 prog_sha[SHA256_DIGEST_LENGTH]; 1934 1935 int ctx_sz = sizeof(*ctx) + 64 * max(sizeof(struct bpf_map_desc), 1935 1936 sizeof(struct bpf_prog_desc)); 1936 1937 int log_buf_sz = (1u << 24) - 1; ··· 1956 1953 opts.insns = gen->insns; 1957 1954 opts.insns_sz = gen->insns_sz; 1958 1955 fds_before = count_open_fds(); 1956 + 1957 + if (sign_progs) { 1958 + opts.excl_prog_hash = prog_sha; 1959 + opts.excl_prog_hash_sz = sizeof(prog_sha); 1960 + opts.signature = sig_buf; 1961 + opts.signature_sz = MAX_SIG_SIZE; 1962 + opts.keyring_id = KEY_SPEC_SESSION_KEYRING; 1963 + 1964 + err = bpftool_prog_sign(&opts); 1965 + if (err < 0) { 1966 + p_err("failed to sign program"); 1967 + goto out; 1968 + } 1969 + 1970 + err = register_session_key(cert_path); 1971 + if (err < 0) { 1972 + p_err("failed to add session key"); 1973 + goto out; 1974 + } 1975 + } 1959 1976 err = bpf_load_and_run(&opts); 1960 1977 fd_delta = count_open_fds() - fds_before; 1961 1978 if (err < 0 || verifier_logs) { ··· 1984 1961 fprintf(stderr, "loader prog leaked %d FDs\n", 1985 1962 fd_delta); 1986 1963 } 1964 + out: 1987 1965 free(log_buf); 1988 1966 return err; 1989 1967 } ··· 2011 1987 p_err("failed to open object file"); 2012 1988 goto err_close_obj; 2013 1989 } 1990 + 1991 + if (sign_progs) 1992 + gen.gen_hash = true; 2014 1993 2015 1994 err = bpf_object__gen_loader(obj, &gen); 2016 1995 if (err) ··· 2589 2562 " METRIC := { cycles | instructions | l1d_loads | llc_misses | itlb_misses | dtlb_misses }\n" 2590 2563 " " HELP_SPEC_OPTIONS " |\n" 2591 2564 " {-f|--bpffs} | {-m|--mapcompat} | {-n|--nomount} |\n" 2592 - " {-L|--use-loader} }\n" 2565 + " {-L|--use-loader} | [ {-S|--sign } {-k} <private_key.pem> {-i} <certificate.x509> ] \n" 2593 2566 "", 2594 2567 bin_name, argv[-2]); 2595 2568
+212
tools/bpf/bpftool/sign.c
··· 1 + // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 2 + /* 3 + * Copyright (C) 2025 Google LLC. 4 + */ 5 + 6 + #ifndef _GNU_SOURCE 7 + #define _GNU_SOURCE 8 + #endif 9 + #include <stdio.h> 10 + #include <stdlib.h> 11 + #include <stdint.h> 12 + #include <stdbool.h> 13 + #include <string.h> 14 + #include <string.h> 15 + #include <getopt.h> 16 + #include <err.h> 17 + #include <openssl/opensslv.h> 18 + #include <openssl/bio.h> 19 + #include <openssl/evp.h> 20 + #include <openssl/pem.h> 21 + #include <openssl/err.h> 22 + #include <openssl/cms.h> 23 + #include <linux/keyctl.h> 24 + #include <errno.h> 25 + 26 + #include <bpf/skel_internal.h> 27 + 28 + #include "main.h" 29 + 30 + #define OPEN_SSL_ERR_BUF_LEN 256 31 + 32 + static void display_openssl_errors(int l) 33 + { 34 + char buf[OPEN_SSL_ERR_BUF_LEN]; 35 + const char *file; 36 + const char *data; 37 + unsigned long e; 38 + int flags; 39 + int line; 40 + 41 + while ((e = ERR_get_error_all(&file, &line, NULL, &data, &flags))) { 42 + ERR_error_string_n(e, buf, sizeof(buf)); 43 + if (data && (flags & ERR_TXT_STRING)) { 44 + p_err("OpenSSL %s: %s:%d: %s", buf, file, line, data); 45 + } else { 46 + p_err("OpenSSL %s: %s:%d", buf, file, line); 47 + } 48 + } 49 + } 50 + 51 + #define DISPLAY_OSSL_ERR(cond) \ 52 + do { \ 53 + bool __cond = (cond); \ 54 + if (__cond && ERR_peek_error()) \ 55 + display_openssl_errors(__LINE__);\ 56 + } while (0) 57 + 58 + static EVP_PKEY *read_private_key(const char *pkey_path) 59 + { 60 + EVP_PKEY *private_key = NULL; 61 + BIO *b; 62 + 63 + b = BIO_new_file(pkey_path, "rb"); 64 + private_key = PEM_read_bio_PrivateKey(b, NULL, NULL, NULL); 65 + BIO_free(b); 66 + DISPLAY_OSSL_ERR(!private_key); 67 + return private_key; 68 + } 69 + 70 + static X509 *read_x509(const char *x509_name) 71 + { 72 + unsigned char buf[2]; 73 + X509 *x509 = NULL; 74 + BIO *b; 75 + int n; 76 + 77 + b = BIO_new_file(x509_name, "rb"); 78 + if (!b) 79 + goto cleanup; 80 + 81 + /* Look at the first two bytes of the file to determine the encoding */ 82 + n = BIO_read(b, buf, 2); 83 + if (n != 2) 84 + goto cleanup; 85 + 86 + if (BIO_reset(b) != 0) 87 + goto cleanup; 88 + 89 + if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84) 90 + /* Assume raw DER encoded X.509 */ 91 + x509 = d2i_X509_bio(b, NULL); 92 + else 93 + /* Assume PEM encoded X.509 */ 94 + x509 = PEM_read_bio_X509(b, NULL, NULL, NULL); 95 + 96 + cleanup: 97 + BIO_free(b); 98 + DISPLAY_OSSL_ERR(!x509); 99 + return x509; 100 + } 101 + 102 + __u32 register_session_key(const char *key_der_path) 103 + { 104 + unsigned char *der_buf = NULL; 105 + X509 *x509 = NULL; 106 + int key_id = -1; 107 + int der_len; 108 + 109 + if (!key_der_path) 110 + return key_id; 111 + x509 = read_x509(key_der_path); 112 + if (!x509) 113 + goto cleanup; 114 + der_len = i2d_X509(x509, &der_buf); 115 + if (der_len < 0) 116 + goto cleanup; 117 + key_id = syscall(__NR_add_key, "asymmetric", key_der_path, der_buf, 118 + (size_t)der_len, KEY_SPEC_SESSION_KEYRING); 119 + cleanup: 120 + X509_free(x509); 121 + OPENSSL_free(der_buf); 122 + DISPLAY_OSSL_ERR(key_id == -1); 123 + return key_id; 124 + } 125 + 126 + int bpftool_prog_sign(struct bpf_load_and_run_opts *opts) 127 + { 128 + BIO *bd_in = NULL, *bd_out = NULL; 129 + EVP_PKEY *private_key = NULL; 130 + CMS_ContentInfo *cms = NULL; 131 + long actual_sig_len = 0; 132 + X509 *x509 = NULL; 133 + int err = 0; 134 + 135 + bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz); 136 + if (!bd_in) { 137 + err = -ENOMEM; 138 + goto cleanup; 139 + } 140 + 141 + private_key = read_private_key(private_key_path); 142 + if (!private_key) { 143 + err = -EINVAL; 144 + goto cleanup; 145 + } 146 + 147 + x509 = read_x509(cert_path); 148 + if (!x509) { 149 + err = -EINVAL; 150 + goto cleanup; 151 + } 152 + 153 + cms = CMS_sign(NULL, NULL, NULL, NULL, 154 + CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED | 155 + CMS_STREAM); 156 + if (!cms) { 157 + err = -EINVAL; 158 + goto cleanup; 159 + } 160 + 161 + if (!CMS_add1_signer(cms, x509, private_key, EVP_sha256(), 162 + CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP | 163 + CMS_USE_KEYID | CMS_NOATTR)) { 164 + err = -EINVAL; 165 + goto cleanup; 166 + } 167 + 168 + if (CMS_final(cms, bd_in, NULL, CMS_NOCERTS | CMS_BINARY) != 1) { 169 + err = -EIO; 170 + goto cleanup; 171 + } 172 + 173 + EVP_Digest(opts->insns, opts->insns_sz, opts->excl_prog_hash, 174 + &opts->excl_prog_hash_sz, EVP_sha256(), NULL); 175 + 176 + bd_out = BIO_new(BIO_s_mem()); 177 + if (!bd_out) { 178 + err = -ENOMEM; 179 + goto cleanup; 180 + } 181 + 182 + if (!i2d_CMS_bio_stream(bd_out, cms, NULL, 0)) { 183 + err = -EIO; 184 + goto cleanup; 185 + } 186 + 187 + actual_sig_len = BIO_get_mem_data(bd_out, NULL); 188 + if (actual_sig_len <= 0) { 189 + err = -EIO; 190 + goto cleanup; 191 + } 192 + 193 + if ((size_t)actual_sig_len > opts->signature_sz) { 194 + err = -ENOSPC; 195 + goto cleanup; 196 + } 197 + 198 + if (BIO_read(bd_out, opts->signature, actual_sig_len) != actual_sig_len) { 199 + err = -EIO; 200 + goto cleanup; 201 + } 202 + 203 + opts->signature_sz = actual_sig_len; 204 + cleanup: 205 + BIO_free(bd_out); 206 + CMS_ContentInfo_free(cms); 207 + X509_free(x509); 208 + EVP_PKEY_free(private_key); 209 + BIO_free(bd_in); 210 + DISPLAY_OSSL_ERR(err < 0); 211 + return err; 212 + }