From: Matteo Croce <mcroce@xxxxxxxxxxxxx> When generating the skeleton, allow to add a signature. The signature will be passed to the kernel in the newly added field. As in sign-file, allow specifying "pkcs11:..." as key file, to use the openssl engine. Still as in sign-file, read the environment variable KBUILD_SIGN_PIN. Signed-off-by: Matteo Croce <mcroce@xxxxxxxxxxxxx> --- tools/bpf/bpftool/Makefile | 14 ++- tools/bpf/bpftool/gen.c | 33 +++++ tools/bpf/bpftool/main.c | 28 +++++ tools/bpf/bpftool/main.h | 7 ++ tools/bpf/bpftool/sign.c | 218 +++++++++++++++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 2 + tools/lib/bpf/skel_internal.h | 4 + 7 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 tools/bpf/bpftool/sign.c diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile index 42eb8eee3d89..d2645c2f4bc9 100644 --- a/tools/bpf/bpftool/Makefile +++ b/tools/bpf/bpftool/Makefile @@ -96,9 +96,9 @@ RM ?= rm -f FEATURE_USER = .bpftool FEATURE_TESTS = libbfd disassembler-four-args reallocarray zlib libcap \ - clang-bpf-co-re + clang-bpf-co-re libcrypto FEATURE_DISPLAY = libbfd disassembler-four-args zlib libcap \ - clang-bpf-co-re + clang-bpf-co-re libcrypto check_feat := 1 NON_CHECK_FEAT_TARGETS := clean uninstall doc doc-clean doc-install doc-uninstall @@ -131,6 +131,11 @@ CFLAGS += -DUSE_LIBCAP LIBS += -lcap endif +ifeq ($(feature-libcrypto), 1) +CFLAGS_CRYPTO := -DUSE_SIGN +LIBS += -lcrypto +endif + include $(wildcard $(OUTPUT)*.d) all: $(OUTPUT)bpftool @@ -138,6 +143,9 @@ all: $(OUTPUT)bpftool BFD_SRCS = jit_disasm.c SRCS = $(filter-out $(BFD_SRCS),$(wildcard *.c)) +ifneq ($(feature-libcrypto), 1) +SRCS := $(filter-out sign.c,$(SRCS)) +endif ifeq ($(feature-libbfd),1) LIBS += -lbfd -ldl -lopcodes @@ -224,7 +232,7 @@ $(BOOTSTRAP_OUTPUT)%.o: %.c $(LIBBPF_BOOTSTRAP_INTERNAL_HDRS) | $(BOOTSTRAP_OUTP -c -MMD $< -o $@ $(OUTPUT)%.o: %.c - $(QUIET_CC)$(CC) $(CFLAGS) -c -MMD $< -o $@ + $(QUIET_CC)$(CC) $(CFLAGS) $(CFLAGS_CRYPTO) -c -MMD $< -o $@ feature-detect-clean: $(call QUIET_CLEAN, feature-detect) diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c index 997a2865e04a..c9f09b222986 100644 --- a/tools/bpf/bpftool/gen.c +++ b/tools/bpf/bpftool/gen.c @@ -491,6 +491,10 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h struct bpf_map *map; char ident[256]; int err = 0; +#ifdef USE_SIGN + char *signature = NULL; + int sig_len = 0; +#endif err = bpf_object__gen_loader(obj, &opts); if (err) @@ -510,6 +514,19 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h * are populated with the loader program. */ +#ifdef USE_SIGN + if (sign_bpf) { + sig_len = sign(sign_hash, sign_key, sign_cert, + opts.insns, opts.insns_sz, + (unsigned char **)&signature); + if (sig_len <= 0) { + p_err("failed to sign instructions"); + err = -EINVAL; + goto out; + } + } +#endif + /* finish generating 'struct skel' */ codegen("\ \n\ @@ -592,6 +609,18 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h ", opts.insns_sz); print_hex(opts.insns, opts.insns_sz); +#ifdef USE_SIGN + if (sign_bpf) { + codegen("\ + \n\ + \"; \n\ + opts.sig_sz = %d; \n\ + opts.signature = (void *)\"\\ \n\ + ", + sig_len); + print_hex(signature, sig_len); + } +#endif codegen("\ \n\ \"; \n\ @@ -1090,6 +1119,10 @@ static int do_help(int argc, char **argv) " %1$s %2$s help\n" "\n" " " HELP_SPEC_OPTIONS " |\n" +#ifdef USE_SIGN + " {-s|--sign} | {-H|--hash} |\n" + " {-c|--cert} | {-k|--key} |\n" +#endif " {-L|--use-loader} }\n" "", bin_name, "gen"); diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c index 8b71500e7cb2..cea3d07e98e0 100644 --- a/tools/bpf/bpftool/main.c +++ b/tools/bpf/bpftool/main.c @@ -31,6 +31,10 @@ bool block_mount; bool verifier_logs; bool relaxed_maps; bool use_loader; +bool sign_bpf; +const char *sign_hash; +const char *sign_cert; +const char *sign_key; bool legacy_libbpf; struct btf *base_btf; struct hashmap *refs_table; @@ -403,6 +407,12 @@ int main(int argc, char **argv) { "use-loader", no_argument, NULL, 'L' }, { "base-btf", required_argument, NULL, 'B' }, { "legacy", no_argument, NULL, 'l' }, +#ifdef USE_SIGN + { "sign", no_argument, NULL, 's' }, + { "hash", required_argument, NULL, 'H' }, + { "cert", required_argument, NULL, 'c' }, + { "key", required_argument, NULL, 'k' }, +#endif { 0 } }; bool version_requested = false; @@ -416,7 +426,11 @@ int main(int argc, char **argv) bin_name = argv[0]; opterr = 0; +#ifdef USE_SIGN + while ((opt = getopt_long(argc, argv, "VhpjfLmndB:lsH:c:k:", +#else while ((opt = getopt_long(argc, argv, "VhpjfLmndB:l", +#endif options, NULL)) >= 0) { switch (opt) { case 'V': @@ -466,6 +480,20 @@ int main(int argc, char **argv) case 'l': legacy_libbpf = true; break; +#ifdef USE_SIGN + case 's': + sign_bpf = true; + break; + case 'H': + sign_hash = optarg; + break; + case 'c': + sign_cert = optarg; + break; + case 'k': + sign_key = optarg; + break; +#endif 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 8d76d937a62b..ef82219d3f52 100644 --- a/tools/bpf/bpftool/main.h +++ b/tools/bpf/bpftool/main.h @@ -91,6 +91,10 @@ extern bool verifier_logs; extern bool relaxed_maps; extern bool use_loader; extern bool legacy_libbpf; +extern bool sign_bpf; +extern const char *sign_hash; +extern const char *sign_cert; +extern const char *sign_key; extern struct btf *base_btf; extern struct hashmap *refs_table; @@ -260,4 +264,7 @@ static inline bool hashmap__empty(struct hashmap *map) return map ? hashmap__size(map) == 0 : true; } +int sign(const char *hash_algo, const char *key_path, const char *x509_path, + const char *indata, int indatalen, unsigned char **outdata); + #endif diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c new file mode 100644 index 000000000000..ca09dc5f93aa --- /dev/null +++ b/tools/bpf/bpftool/sign.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +/* Sign a module file using the given key and certificate. + * + * Inspired by Linux scripts/sign-file.c + * Copyright (C) 2021 Matteo Croce <mcroce@xxxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the licence, or (at your option) any later version. + */ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <err.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <openssl/opensslv.h> +#include <openssl/bio.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/err.h> +#include <openssl/engine.h> +#include <openssl/cms.h> + +#include "main.h" + +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 void display_openssl_errors(void) +{ + const char *file; + char buf[120]; + int e, line; + + if (!ERR_peek_error()) + return; + + while ((e = ERR_get_error_line(&file, &line))) { + ERR_error_string(e, buf); + fprintf(stderr, "- SSL %s: %s:%d\n", buf, file, line); + } +} + +static EVP_PKEY *read_private_key(const char *key_path) +{ + EVP_PKEY *private_key; + + if (!strncmp(key_path, "pkcs11:", 7)) { + ENGINE *e; + + ENGINE_load_builtin_engines(); + display_openssl_errors(); + e = ENGINE_by_id("pkcs11"); + if (!e) + return NULL; + + if (!ENGINE_init(e)) { + display_openssl_errors(); + return NULL; + } + display_openssl_errors(); + + if (key_pass) + if (!ENGINE_ctrl_cmd_string(e, "PIN", key_pass, 0)) + return NULL; + private_key = ENGINE_load_private_key(e, key_path, NULL, NULL); + } else { + BIO *b; + + b = BIO_new_file(key_path, "rb"); + if (!b) + return NULL; + private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, NULL); + BIO_free(b); + } + + return private_key; +} + +static X509 *read_x509(const char *x509_path) +{ + unsigned char buf[2]; + X509 *x509 = NULL; + BIO *b; + int n; + + b = BIO_new_file(x509_path, "rb"); + if (!b) { + display_openssl_errors(); + return NULL; + } + + /* 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_path); + if (n >= 0) + fprintf(stderr, "%s: Short read\n", x509_path); + display_openssl_errors(); + goto out_free; + } + + if (BIO_reset(b)) { + display_openssl_errors(); + goto out_free; + } + + if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84) + /* Assume raw DER encoded X.509 */ + x509 = d2i_X509_bio(b, NULL); + else + /* Assume PEM encoded X.509 */ + x509 = PEM_read_bio_X509(b, NULL, NULL, NULL); + + if (!x509) + display_openssl_errors(); + +out_free: + BIO_free(b); + + return x509; +} + +int sign(const char *hash_algo, const char *key_path, const char *x509_path, + const char *indata, int indatalen, unsigned char **outdata) +{ + CMS_ContentInfo *cms = NULL; + const EVP_MD *digest_algo; + EVP_PKEY *private_key; + X509 *x509; + BIO *bm; + + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + ERR_clear_error(); + + key_pass = getenv("KBUILD_SIGN_PIN"); + + /* Open the module file */ + bm = BIO_new_mem_buf(indata, indatalen); + if (!bm) { + display_openssl_errors(); + return -1; + } + + /* Read the private key and the X.509 cert the PKCS#7 message + * will point to. + */ + private_key = read_private_key(key_path); + if (!private_key) + goto out_free; + + x509 = read_x509(x509_path); + if (!x509) + goto out_free; + + /* Digest the module data. */ + OpenSSL_add_all_digests(); + display_openssl_errors(); + + digest_algo = EVP_get_digestbyname(hash_algo); + if (!digest_algo) { + display_openssl_errors(); + goto out_free; + } + + /* 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); + if (!cms) { + display_openssl_errors(); + goto out_free; + } + + if (!CMS_add1_signer(cms, x509, private_key, digest_algo, + CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP | + CMS_NOATTR)) { + display_openssl_errors(); + goto out_free; + } + + if (CMS_final(cms, bm, NULL, CMS_NOCERTS | CMS_BINARY) < 0) + display_openssl_errors(); + +out_free: + BIO_free(bm); + + if (!cms) + return -1; + + return i2d_CMS_ContentInfo(cms, outdata); +} diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index c26871263f1f..bbb4435c7586 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -1346,6 +1346,8 @@ union bpf_attr { __aligned_u64 fd_array; /* array of FDs */ __aligned_u64 core_relos; __u32 core_relo_rec_size; /* sizeof(struct bpf_core_relo) */ + __aligned_u64 signature; /* instruction's signature */ + __u32 sig_len; /* signature size */ }; struct { /* anonymous struct used by BPF_OBJ_* commands */ diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h index 0b84d8e6b72a..7c987b79568b 100644 --- a/tools/lib/bpf/skel_internal.h +++ b/tools/lib/bpf/skel_internal.h @@ -52,8 +52,10 @@ struct bpf_load_and_run_opts { struct bpf_loader_ctx *ctx; const void *data; const void *insns; + const void *signature; __u32 data_sz; __u32 insns_sz; + __u32 sig_sz; const char *errstr; }; @@ -93,6 +95,8 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts) attr.prog_type = BPF_PROG_TYPE_SYSCALL; attr.insns = (long) opts->insns; attr.insn_cnt = opts->insns_sz / sizeof(struct bpf_insn); + attr.signature = (long) opts->signature; + attr.sig_len = opts->sig_sz; attr.license = (long) "Dual BSD/GPL"; memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog")); attr.fd_array = (long) &map_fd; -- 2.33.1