diff mbox series

[RFC,bpf-next,2/2] bpftool: add signature in skeleton

Message ID 20211012190028.54828-3-mcroce@linux.microsoft.com (mailing list archive)
State RFC
Delegated to: BPF
Headers show
Series bpf: sign bpf programs | expand

Checks

Context Check Description
bpf/vmtest-bpf-next fail VM_Test
bpf/vmtest-bpf-next-PR fail PR summary
netdev/cover_letter success Series has a cover letter
netdev/fixes_present success Fixes tag not required for -next series
netdev/patch_count success Link
netdev/tree_selection success Clearly marked for bpf-next
netdev/subject_prefix success Link
netdev/cc_maintainers warning 20 maintainers not CCed: joe@cilium.io brouer@redhat.com llvm@lists.linux.dev songliubraving@fb.com revest@chromium.org netdev@vger.kernel.org ndesaulniers@google.com chengzhihao1@huawei.com yhs@fb.com jackmanb@google.com kafai@fb.com sedat.dilek@gmail.com mcroce@microsoft.com nathan@kernel.org jean-philippe@linaro.org toke@redhat.com john.fastabend@gmail.com alastorze@fb.com kpsingh@kernel.org quentin@isovalent.com
netdev/source_inline success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/module_param success Was 0 now: 0
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/verify_fixes success No Fixes tag
netdev/checkpatch warning CHECK: Alignment should match open parenthesis CHECK: No space is necessary after a cast WARNING: 'specifing' may be misspelled - perhaps 'specifying'? WARNING: Avoid line continuations in quoted strings WARNING: Avoid unnecessary line continuations WARNING: Missing or malformed SPDX-License-Identifier tag in line 1 WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: suspect code indent for conditional statements (8, 11) WARNING: unnecessary whitespace before a quoted newline
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/header_inline success No static functions without inline keyword in header files

Commit Message

Matteo Croce Oct. 12, 2021, 7 p.m. UTC
From: Matteo Croce <mcroce@microsoft.com>

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 specifing "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@microsoft.com>
---
 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       | 217 +++++++++++++++++++++++++++++++++
 tools/include/uapi/linux/bpf.h |   2 +
 tools/lib/bpf/skel_internal.h  |   4 +
 7 files changed, 302 insertions(+), 3 deletions(-)
 create mode 100644 tools/bpf/bpftool/sign.c
diff mbox series

Patch

diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile
index 1fcf5b01a193..b67d6e0b9067 100644
--- a/tools/bpf/bpftool/Makefile
+++ b/tools/bpf/bpftool/Makefile
@@ -78,9 +78,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
@@ -113,6 +113,11 @@  CFLAGS += -DUSE_LIBCAP
 LIBS += -lcap
 endif
 
+ifeq ($(feature-libcrypto), 1)
+CFLAGS_SSL := -DUSE_SIGN
+LIBS += -lssl -lcrypto
+endif
+
 include $(wildcard $(OUTPUT)*.d)
 
 all: $(OUTPUT)bpftool
@@ -120,6 +125,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
@@ -202,7 +210,7 @@  $(BOOTSTRAP_OUTPUT)%.o: %.c | $(BOOTSTRAP_OUTPUT)
 	$(QUIET_CC)$(HOSTCC) $(CFLAGS) -c -MMD -o $@ $<
 
 $(OUTPUT)%.o: %.c
-	$(QUIET_CC)$(CC) $(CFLAGS) -c -MMD -o $@ $<
+	$(QUIET_CC)$(CC) $(CFLAGS) $(CFLAGS_SSL) -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 cc835859465b..2551fe90dc89 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -434,6 +434,10 @@  static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
 	DECLARE_LIBBPF_OPTS(gen_loader_opts, opts);
 	struct bpf_map *map;
 	int err = 0;
+#ifdef USE_SIGN
+	char *signature = NULL;
+	int sig_len = 0;
+#endif
 
 	err = bpf_object__gen_loader(obj, &opts);
 	if (err)
@@ -453,6 +457,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\
@@ -537,6 +554,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\
@@ -1037,6 +1066,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 02eaaf065f65..4e70b89c5b22 100644
--- a/tools/bpf/bpftool/main.c
+++ b/tools/bpf/bpftool/main.c
@@ -30,6 +30,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;
 struct btf *base_btf;
 struct pinned_obj_table prog_table;
 struct pinned_obj_table map_table;
@@ -398,6 +402,12 @@  int main(int argc, char **argv)
 		{ "debug",	no_argument,	NULL,	'd' },
 		{ "use-loader",	no_argument,	NULL,	'L' },
 		{ "base-btf",	required_argument, NULL, 'B' },
+#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 }
 	};
 	int opt, ret;
@@ -414,7 +424,11 @@  int main(int argc, char **argv)
 	hash_init(link_table.table);
 
 	opterr = 0;
+#ifdef USE_SIGN
+	while ((opt = getopt_long(argc, argv, "VhpjfLmndB:sH:c:k:",
+#else
 	while ((opt = getopt_long(argc, argv, "VhpjfLmndB:",
+#endif
 				  options, NULL)) >= 0) {
 		switch (opt) {
 		case 'V':
@@ -460,6 +474,20 @@  int main(int argc, char **argv)
 		case 'L':
 			use_loader = 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 90caa42aac4c..78742720447f 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -90,6 +90,10 @@  extern bool block_mount;
 extern bool verifier_logs;
 extern bool relaxed_maps;
 extern bool use_loader;
+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 pinned_obj_table prog_table;
 extern struct pinned_obj_table map_table;
@@ -259,4 +263,7 @@  int do_filter_dump(struct tcmsg *ifinfo, struct nlattr **tb, const char *kind,
 
 int print_all_levels(__maybe_unused enum libbpf_print_level level,
 		     const char *format, va_list args);
+
+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..50b257a7177c
--- /dev/null
+++ b/tools/bpf/bpftool/sign.c
@@ -0,0 +1,217 @@ 
+/* Sign a module file using the given key and certificate.
+ *
+ * Inspired by Linux scripts/sign-file.c
+ * Copyright (C) 2021 Matteo Croce <mcroce@microsoft.com>
+ *
+ * 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 c2b8857b8a1c..b9d259f26e92 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1336,6 +1336,8 @@  union bpf_attr {
 		};
 		__u32		:32;		/* pad */
 		__aligned_u64	fd_array;	/* array of FDs */
+		__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 9cf66702fa8d..1ef2df16f90c 100644
--- a/tools/lib/bpf/skel_internal.h
+++ b/tools/lib/bpf/skel_internal.h
@@ -42,8 +42,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;
 };
 
@@ -84,6 +86,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;