From patchwork Fri Mar 21 16:45:03 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Blaise Boscaccy X-Patchwork-Id: 14025780 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id BDCA122424E; Fri, 21 Mar 2025 16:46:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=13.77.154.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742575570; cv=none; b=qN7xsEeqqqN6UDi2h1AZuGIeZlKGvODifu0Q5yt6Gbl6eLFrbuY7s0ac3Fy7D6slbBZ9rEfYkGTcIs65BzubMaASSJRwOO8rVPvRivWMiuBwQhCnl1ch3yU13GWP13WRLg5xTYb9x9vDEx9QpBzlqgS9YSBAF5MXpVULMe/+ou0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742575570; c=relaxed/simple; bh=ZESNNja70L75Cau/PudYljc+EnLPPoMtznsWEH9sfqU=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=r+acoNYDrFkjAorJpF+Qrd27JbRpVkO9GtyuZhnKGNvV18YNDR0wd6qwHUjy8uFtpsm9xFSWBVvbkjtcn60U6uGT7OotjMOPZWx94BThJsbpm76Gkjg5Zs1/l3zg0LCuvk251AwmdaBLZJGfvWoORj4F072FlwLikfYqfXwQGrE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com; spf=pass smtp.mailfrom=linux.microsoft.com; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b=q8i/5mIy; arc=none smtp.client-ip=13.77.154.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b="q8i/5mIy" Received: from narnia.corp.microsoft.com (unknown [167.220.2.28]) by linux.microsoft.com (Postfix) with ESMTPSA id 08B2E2025382; Fri, 21 Mar 2025 09:45:58 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 08B2E2025382 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1742575568; bh=dcfxCyUSp8As23OnuSV0pnBa5kNEygBXd5toCUWgXz0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=q8i/5mIynk6qSyLhjM59JUs7C/SMkKm3H4qs3tj6Jai3QE0yrNNaeavUuNiA68Uwv CClSA76kJDUdjVuLKj/SxyBg/ymA81N3ThwzG3lMQW+BCft3sLoqVWPVx6bURmzwO6 oljwawRgzAOXM3JQ8LIt+eQYvEbVIFOVQAX/4Ekg= From: Blaise Boscaccy To: Jonathan Corbet , David Howells , Herbert Xu , "David S. Miller" , Paul Moore , James Morris , "Serge E. Hallyn" , Masahiro Yamada , Nathan Chancellor , Nicolas Schier , Shuah Khan , =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= , Nick Desaulniers , Bill Wendling , Justin Stitt , Blaise Boscaccy , Jarkko Sakkinen , Jan Stancek , Neal Gompa , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, keyrings@vger.kernel.org, linux-crypto@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kbuild@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, llvm@lists.linux.dev, nkapron@google.com, teknoraver@meta.com, roberto.sassu@huawei.com, xiyou.wangcong@gmail.com Subject: [RFC PATCH security-next 1/4] security: Hornet LSM Date: Fri, 21 Mar 2025 09:45:03 -0700 Message-ID: <20250321164537.16719-2-bboscaccy@linux.microsoft.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250321164537.16719-1-bboscaccy@linux.microsoft.com> References: <20250321164537.16719-1-bboscaccy@linux.microsoft.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Patchwork-State: RFC This adds the Hornet Linux Security Module which provides signature verification of eBPF programs. Hornet uses a similar signature verification scheme similar to that of kernel modules. A pkcs#7 signature is appended to the end of an executable file. During an invocation of bpf_prog_load, the signature is fetched from the current task's executable file. That signature is used to verify the integrity of the bpf instructions and maps which where passed into the kernel. Additionally, Hornet implicitly trusts any programs which where loaded from inside kernel rather than userspace, which allows BPF_PRELOAD programs along with outputs for BPF_SYSCALL programs to run. Hornet allows users to continue to maintain an invariant that all code running inside of the kernel has been signed and works well with light-skeleton based loaders, or any statically generated program that doesn't require userspace instruction rewriting. Signed-off-by: Blaise Boscaccy --- Documentation/admin-guide/LSM/Hornet.rst | 51 +++++ crypto/asymmetric_keys/pkcs7_verify.c | 10 + include/linux/kernel_read_file.h | 1 + include/linux/verification.h | 1 + include/uapi/linux/lsm.h | 1 + security/Kconfig | 3 +- security/Makefile | 1 + security/hornet/Kconfig | 11 ++ security/hornet/Makefile | 4 + security/hornet/hornet_lsm.c | 239 +++++++++++++++++++++++ 10 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 Documentation/admin-guide/LSM/Hornet.rst create mode 100644 security/hornet/Kconfig create mode 100644 security/hornet/Makefile create mode 100644 security/hornet/hornet_lsm.c diff --git a/Documentation/admin-guide/LSM/Hornet.rst b/Documentation/admin-guide/LSM/Hornet.rst new file mode 100644 index 0000000000000..fa112412638f1 --- /dev/null +++ b/Documentation/admin-guide/LSM/Hornet.rst @@ -0,0 +1,51 @@ +====== +Hornet +====== + +Hornet is a Linux Security Module that provides signature verification +for eBPF programs. This is selectable at build-time with +``CONFIG_SECURITY_HORNET``. + +Overview +======== + +Hornet provides signature verification for eBPF programs by utilizing +the existing PKCS#7 infrastructure that's used for module signature +verification. Hornet works by creating a buffer containing the eBPF +program instructions along with its associated maps and checking a +signature against that buffer. The signature is appended to the end of +the lskel executable file and is extracted at runtime via +get_task_exe_file. Hornet works by hooking into the +security_bpf_prog_load hook. Load invocations that originate from the +kernel (bpf preload, results of bpf_syscall programs, etc.) are +allowed to run unconditionally. Calls that originate from userspace +require signature verification. If signature verification fails, the +program will fail to load. + +Instruction/Map Ordering +======================== + +Hornet supports both sparse-array based maps via map discovery along +with the newly added fd_array_cnt API for continuous map arrays. The +buffer used for signature verification is assumed to be the +instructions followed by all maps used, ordered by their index in +fd_array. + +Tooling +======= + +Some tooling is provided to aid with the development of signed eBPF lskels. + +extract-skel.sh +--------------- + +This simple shell script extracts the instructions and map data used +by the light skeleton from the autogenerated header file created by +bpftool. + +sign-ebpf +--------- + +sign-ebpf works similarly to the sign-file script with one key +difference: it takes a separate input binary used for signature +verification and will append the signature to a different output file. diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c index f0d4ff3c20a83..1a5fbb3612188 100644 --- a/crypto/asymmetric_keys/pkcs7_verify.c +++ b/crypto/asymmetric_keys/pkcs7_verify.c @@ -428,6 +428,16 @@ int pkcs7_verify(struct pkcs7_message *pkcs7, } /* Authattr presence checked in parser */ break; + case VERIFYING_EBPF_SIGNATURE: + if (pkcs7->data_type != OID_data) { + pr_warn("Invalid ebpf sig (not pkcs7-data)\n"); + return -EKEYREJECTED; + } + if (pkcs7->have_authattrs) { + pr_warn("Invalid ebpf sig (has authattrs)\n"); + return -EKEYREJECTED; + } + break; case VERIFYING_UNSPECIFIED_SIGNATURE: if (pkcs7->data_type != OID_data) { pr_warn("Invalid unspecified sig (not pkcs7-data)\n"); diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h index 90451e2e12bd1..7ed9337be5423 100644 --- a/include/linux/kernel_read_file.h +++ b/include/linux/kernel_read_file.h @@ -14,6 +14,7 @@ id(KEXEC_INITRAMFS, kexec-initramfs) \ id(POLICY, security-policy) \ id(X509_CERTIFICATE, x509-certificate) \ + id(EBPF, ebpf) \ id(MAX_ID, ) #define __fid_enumify(ENUM, dummy) READING_ ## ENUM, diff --git a/include/linux/verification.h b/include/linux/verification.h index 4f3022d081c31..812be8ad5f744 100644 --- a/include/linux/verification.h +++ b/include/linux/verification.h @@ -35,6 +35,7 @@ enum key_being_used_for { VERIFYING_KEXEC_PE_SIGNATURE, VERIFYING_KEY_SIGNATURE, VERIFYING_KEY_SELF_SIGNATURE, + VERIFYING_EBPF_SIGNATURE, VERIFYING_UNSPECIFIED_SIGNATURE, NR__KEY_BEING_USED_FOR }; diff --git a/include/uapi/linux/lsm.h b/include/uapi/linux/lsm.h index 938593dfd5daf..2ff9bcdd551e2 100644 --- a/include/uapi/linux/lsm.h +++ b/include/uapi/linux/lsm.h @@ -65,6 +65,7 @@ struct lsm_ctx { #define LSM_ID_IMA 111 #define LSM_ID_EVM 112 #define LSM_ID_IPE 113 +#define LSM_ID_HORNET 114 /* * LSM_ATTR_XXX definitions identify different LSM attributes diff --git a/security/Kconfig b/security/Kconfig index f10dbf15c2947..0030f0224c7ab 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -230,6 +230,7 @@ source "security/safesetid/Kconfig" source "security/lockdown/Kconfig" source "security/landlock/Kconfig" source "security/ipe/Kconfig" +source "security/hornet/Kconfig" source "security/integrity/Kconfig" @@ -273,7 +274,7 @@ config LSM default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,ipe,bpf" if DEFAULT_SECURITY_APPARMOR default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,ipe,bpf" if DEFAULT_SECURITY_TOMOYO default "landlock,lockdown,yama,loadpin,safesetid,ipe,bpf" if DEFAULT_SECURITY_DAC - default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,bpf" + default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,hornet,bpf" help A comma-separated list of LSMs, in initialization order. Any LSMs left off this list, except for those with order diff --git a/security/Makefile b/security/Makefile index 22ff4c8bd8cec..e24bccd951f88 100644 --- a/security/Makefile +++ b/security/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_CGROUPS) += device_cgroup.o obj-$(CONFIG_BPF_LSM) += bpf/ obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/ obj-$(CONFIG_SECURITY_IPE) += ipe/ +obj-$(CONFIG_SECURITY_HORNET) += hornet/ # Object integrity file lists obj-$(CONFIG_INTEGRITY) += integrity/ diff --git a/security/hornet/Kconfig b/security/hornet/Kconfig new file mode 100644 index 0000000000000..19406aa237ac6 --- /dev/null +++ b/security/hornet/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SECURITY_HORNET + bool "Hornet support" + depends on SECURITY + default n + help + This selects Hornet. + Further information can be found in + Documentation/admin-guide/LSM/Hornet.rst. + + If you are unsure how to answer this question, answer N. diff --git a/security/hornet/Makefile b/security/hornet/Makefile new file mode 100644 index 0000000000000..79f4657b215fa --- /dev/null +++ b/security/hornet/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_SECURITY_HORNET) := hornet.o + +hornet-y := hornet_lsm.o diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c new file mode 100644 index 0000000000000..3616c68b76fbc --- /dev/null +++ b/security/hornet/hornet_lsm.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hornet Linux Security Module + * + * Author: Blaise Boscaccy + * + * Copyright (C) 2025 Microsoft Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EBPF_SIG_STRING "~eBPF signature appended~\n" + +struct hornet_maps { + u32 used_idx[MAX_USED_MAPS]; + u32 used_map_cnt; + bpfptr_t fd_array; +}; + +static int cmp_idx(const void *a, const void *b) +{ + return *(const u32 *)a - *(const u32 *)b; +} + +static int add_used_map(struct hornet_maps *maps, int idx) +{ + int i; + + for (i = 0; i < maps->used_map_cnt; i++) + if (maps->used_idx[i] == idx) + return i; + + if (maps->used_map_cnt >= MAX_USED_MAPS) + return -E2BIG; + + maps->used_idx[maps->used_map_cnt] = idx; + return maps->used_map_cnt++; +} + +static int hornet_find_maps(struct bpf_prog *prog, struct hornet_maps *maps) +{ + struct bpf_insn *insn = prog->insnsi; + int insn_cnt = prog->len; + int i; + int err; + + for (i = 0; i < insn_cnt; i++, insn++) { + if (insn[0].code == (BPF_LD | BPF_IMM | BPF_DW)) { + switch (insn[0].src_reg) { + case BPF_PSEUDO_MAP_IDX_VALUE: + case BPF_PSEUDO_MAP_IDX: + err = add_used_map(maps, insn[0].imm); + if (err < 0) + return err; + break; + default: + break; + } + } + } + /* Sort the spare-array indices. This should match the map ordering used during + * signature generation + */ + sort(maps->used_idx, maps->used_map_cnt, sizeof(*maps->used_idx), + cmp_idx, NULL); + + return 0; +} + +static int hornet_populate_fd_array(struct hornet_maps *maps, u32 fd_array_cnt) +{ + int i; + + if (fd_array_cnt > MAX_USED_MAPS) + return -E2BIG; + + for (i = 0; i < fd_array_cnt; i++) + maps->used_idx[i] = i; + + maps->used_map_cnt = fd_array_cnt; + return 0; +} + +/* kern_sys_bpf is declared as an EXPORT_SYMBOL in kernel/bpf/syscall.c, however no definition is + * provided in any bpf header files. If/when this function has a proper definition provided + * somewhere this declaration should be removed + */ +int kern_sys_bpf(int cmd, union bpf_attr *attr, unsigned int size); + +static int hornet_verify_lskel(struct bpf_prog *prog, struct hornet_maps *maps, + void *sig, size_t sig_len) +{ + int fd; + u32 i; + void *buf; + void *new; + size_t buf_sz; + struct bpf_map *map; + int err = 0; + int key = 0; + union bpf_attr attr = {0}; + + buf = kmalloc_array(prog->len, sizeof(struct bpf_insn), GFP_KERNEL); + if (!buf) + return -ENOMEM; + buf_sz = prog->len * sizeof(struct bpf_insn); + memcpy(buf, prog->insnsi, buf_sz); + + for (i = 0; i < maps->used_map_cnt; i++) { + err = copy_from_bpfptr_offset(&fd, maps->fd_array, + maps->used_idx[i] * sizeof(fd), + sizeof(fd)); + if (err < 0) + continue; + if (fd < 1) + continue; + + map = bpf_map_get(fd); + if (IS_ERR(map)) + continue; + + /* don't allow userspace to change map data used for signature verification */ + if (!map->frozen) { + attr.map_fd = fd; + err = kern_sys_bpf(BPF_MAP_FREEZE, &attr, sizeof(attr)); + if (err < 0) + goto out; + } + + new = krealloc(buf, buf_sz + map->value_size, GFP_KERNEL); + if (!new) { + err = -ENOMEM; + goto out; + } + buf = new; + new = map->ops->map_lookup_elem(map, &key); + if (!new) { + err = -ENOENT; + goto out; + } + memcpy(buf + buf_sz, new, map->value_size); + buf_sz += map->value_size; + } + + err = verify_pkcs7_signature(buf, buf_sz, sig, sig_len, + VERIFY_USE_SECONDARY_KEYRING, + VERIFYING_EBPF_SIGNATURE, + NULL, NULL); +out: + kfree(buf); + return err; +} + +static int hornet_check_binary(struct bpf_prog *prog, union bpf_attr *attr, + struct hornet_maps *maps) +{ + struct file *file = get_task_exe_file(current); + const unsigned long markerlen = sizeof(EBPF_SIG_STRING) - 1; + void *buf = NULL; + size_t sz = 0, sig_len, prog_len, buf_sz; + int err = 0; + struct module_signature sig; + + buf_sz = kernel_read_file(file, 0, &buf, INT_MAX, &sz, READING_EBPF); + fput(file); + if (!buf_sz) + return -1; + + prog_len = buf_sz; + + if (prog_len > markerlen && + memcmp(buf + prog_len - markerlen, EBPF_SIG_STRING, markerlen) == 0) + prog_len -= markerlen; + + memcpy(&sig, buf + (prog_len - sizeof(sig)), sizeof(sig)); + sig_len = be32_to_cpu(sig.sig_len); + prog_len -= sig_len + sizeof(sig); + + err = mod_check_sig(&sig, prog->len * sizeof(struct bpf_insn), "ebpf"); + if (err) + return err; + return hornet_verify_lskel(prog, maps, buf + prog_len, sig_len); +} + +static int hornet_check_signature(struct bpf_prog *prog, union bpf_attr *attr, + struct bpf_token *token, bool is_kernel) +{ + struct hornet_maps maps = {0}; + int err; + + /* support both sparse arrays and explicit continuous arrays of map fds */ + if (attr->fd_array_cnt) + err = hornet_populate_fd_array(&maps, attr->fd_array_cnt); + else + err = hornet_find_maps(prog, &maps); + + if (err < 0) + return err; + + maps.fd_array = make_bpfptr(attr->fd_array, is_kernel); + return hornet_check_binary(prog, attr, &maps); +} + +static int hornet_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr, + struct bpf_token *token, bool is_kernel) +{ + if (is_kernel) + return 0; + return hornet_check_signature(prog, attr, token, is_kernel); +} + +static struct security_hook_list hornet_hooks[] __ro_after_init = { + LSM_HOOK_INIT(bpf_prog_load, hornet_bpf_prog_load), +}; + +static const struct lsm_id hornet_lsmid = { + .name = "hornet", + .id = LSM_ID_HORNET, +}; + +static int __init hornet_init(void) +{ + pr_info("Hornet: eBPF signature verification enabled\n"); + security_add_hooks(hornet_hooks, ARRAY_SIZE(hornet_hooks), &hornet_lsmid); + return 0; +} + +DEFINE_LSM(hornet) = { + .name = "hornet", + .init = hornet_init, +}; From patchwork Fri Mar 21 16:45:04 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Blaise Boscaccy X-Patchwork-Id: 14025781 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 2F6901E98EB; Fri, 21 Mar 2025 16:46:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=13.77.154.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742575581; cv=none; b=oIm8vQS/p0Rg9A2iZc4lA8+GlbpgsFzhqRXtpiZduf8oljrjq7w9GGUtwtvgDh3Xt5LNRM0lKjx8K7JCPgK97eCCV9QvfHPS8u16KByuoHbaqOkG0TlLHgDbL5nvPGNyHlENQY8ZtaMxJkY8tByhhR8htNUUC8Z4NTgAHma/JBc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742575581; c=relaxed/simple; bh=GWDmgEs4SVF4GEH2MK+44o35QcdVkWwcjYjnj1J0Zo0=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=OMGZjuG5dfInUarvdG49Rxtw4a3Lcwd//ZIFFrquGCeooNCn3diiBhc9aSc7P8jWqhBBF1InzPReHVkhNSXSllHcs/bZqWwL5uDmC0f/KsWkRwYTorwc9/DREVbsX8w3u+qLLtMTTac97rcxaoDkS1mkKyRT6bAR8MjFCJKdUzc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com; spf=pass smtp.mailfrom=linux.microsoft.com; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b=GtOBRgl4; arc=none smtp.client-ip=13.77.154.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b="GtOBRgl4" Received: from narnia.corp.microsoft.com (unknown [167.220.2.28]) by linux.microsoft.com (Postfix) with ESMTPSA id 193102025386; Fri, 21 Mar 2025 09:46:08 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 193102025386 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1742575577; bh=UzEqDSTXBuOj4+4tDZp8lLw090SqC2DfZSOyZh3D3T8=; h=From:To:Subject:Date:In-Reply-To:References:From; b=GtOBRgl4uOFpmkLR6dmrlBFQTb1NCiO32g/XxntTl2iU/HrTPMsxlYHhavHMwFw3V t3NAq8OQAwn1jXZ/iLAWhpCBj9IjqbvqdtIulYaOFhafg4+p3xofTOhjtGdcdfz36V D4TTUR05ns3kBp+nETrTz+2Zo7RgoEuzMppVELC0= From: Blaise Boscaccy To: Jonathan Corbet , David Howells , Herbert Xu , "David S. Miller" , Paul Moore , James Morris , "Serge E. Hallyn" , Masahiro Yamada , Nathan Chancellor , Nicolas Schier , Shuah Khan , =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= , Nick Desaulniers , Bill Wendling , Justin Stitt , Blaise Boscaccy , Jarkko Sakkinen , Jan Stancek , Neal Gompa , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, keyrings@vger.kernel.org, linux-crypto@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kbuild@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, llvm@lists.linux.dev, nkapron@google.com, teknoraver@meta.com, roberto.sassu@huawei.com, xiyou.wangcong@gmail.com Subject: [RFC PATCH security-next 2/4] hornet: Introduce sign-ebpf Date: Fri, 21 Mar 2025 09:45:04 -0700 Message-ID: <20250321164537.16719-3-bboscaccy@linux.microsoft.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250321164537.16719-1-bboscaccy@linux.microsoft.com> References: <20250321164537.16719-1-bboscaccy@linux.microsoft.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Patchwork-State: RFC This introduces the sign-ebpf tool. It is very similar to the existing sign-file script, with one key difference, it will sign a file with with a signature computed off of arbitrary input data. This can used to sign an ebpf light skeleton loader program for verification via hornet. Typical usage is to provide a payload containing the lskel ebpf syscall program binary and it's associated maps, which can be extracted from the auto-generated skel header. Signed-off-by: Blaise Boscaccy --- scripts/Makefile | 1 + scripts/hornet/Makefile | 5 + scripts/hornet/sign-ebpf.c | 420 +++++++++++++++++++++++++++++++++++++ 3 files changed, 426 insertions(+) create mode 100644 scripts/hornet/Makefile create mode 100644 scripts/hornet/sign-ebpf.c diff --git a/scripts/Makefile b/scripts/Makefile index 46f860529df5e..a2cace05d7342 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -57,6 +57,7 @@ subdir-$(CONFIG_GENKSYMS) += genksyms subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms subdir-$(CONFIG_SECURITY_SELINUX) += selinux subdir-$(CONFIG_SECURITY_IPE) += ipe +subdir-$(CONFIG_SECURITY_HORNET) += hornet # Let clean descend into subdirs subdir- += basic dtc gdb kconfig mod diff --git a/scripts/hornet/Makefile b/scripts/hornet/Makefile new file mode 100644 index 0000000000000..ab71dbb8688e4 --- /dev/null +++ b/scripts/hornet/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +hostprogs-always-y := sign-ebpf + +HOSTCFLAGS_sign-ebpf.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null) +HOSTLDLIBS_sign-ebpf = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto) diff --git a/scripts/hornet/sign-ebpf.c b/scripts/hornet/sign-ebpf.c new file mode 100644 index 0000000000000..ff98fddb79a15 --- /dev/null +++ b/scripts/hornet/sign-ebpf.c @@ -0,0 +1,420 @@ +/* Sign ebpf programs and skeletons using the given key. + * + * This program is heavily based on the kernel's sign-file tool + * with some minor additions to support the signing of eBPF lskels. + * + * Copyright © 2014-2016 Red Hat, Inc. All Rights Reserved. + * Copyright © 2015 Intel Corporation. + * Copyright © 2016 Hewlett Packard Enterprise Development LP + * Copyright © 2025 Microsoft Corporation. + * + * Authors: David Howells + * David Woodhouse + * Juerg Haefliger + * Blaise Boscaccy + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if OPENSSL_VERSION_MAJOR >= 3 +# define USE_PKCS11_PROVIDER +# include +# include +#else +# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) +# define USE_PKCS11_ENGINE +# include +# endif +#endif +#include "../ssl-common.h" + +/* + * Use CMS if we have openssl-1.0.0 or newer available - otherwise we have to + * assume that it's not available and its header file is missing and that we + * should use PKCS#7 instead. Switching to the older PKCS#7 format restricts + * the options we have on specifying the X.509 certificate we want. + * + * Further, older versions of OpenSSL don't support manually adding signers to + * the PKCS#7 message so have to accept that we get a certificate included in + * the signature message. Nor do such older versions of OpenSSL support + * signing with anything other than SHA1 - so we're stuck with that if such is + * the case. + */ +#if defined(LIBRESSL_VERSION_NUMBER) || \ + OPENSSL_VERSION_NUMBER < 0x10000000L || \ + defined(OPENSSL_NO_CMS) +#define USE_PKCS7 +#endif +#ifndef USE_PKCS7 +#include +#else +#include +#endif + +struct module_signature { + uint8_t algo; /* Public-key crypto algorithm [0] */ + uint8_t hash; /* Digest algorithm [0] */ + uint8_t id_type; /* Key identifier type [PKEY_ID_PKCS7] */ + uint8_t signer_len; /* Length of signer's name [0] */ + uint8_t key_id_len; /* Length of key identifier [0] */ + uint8_t __pad[3]; + uint32_t sig_len; /* Length of signature data */ +}; + +#define PKEY_ID_PKCS7 2 + +static char magic_number[] = "~eBPF signature appended~\n"; + +static __attribute__((noreturn)) +void format(void) +{ + fprintf(stderr, + "Usage: scripts/sign-ebpf [-dp] []\n"); + exit(2); +} + +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 EVP_PKEY *read_private_key_pkcs11(const char *private_key_name) +{ + EVP_PKEY *private_key = NULL; +#ifdef USE_PKCS11_PROVIDER + OSSL_STORE_CTX *store; + + if (!OSSL_PROVIDER_try_load(NULL, "pkcs11", true)) + ERR(1, "OSSL_PROVIDER_try_load(pkcs11)"); + if (!OSSL_PROVIDER_try_load(NULL, "default", true)) + ERR(1, "OSSL_PROVIDER_try_load(default)"); + + store = OSSL_STORE_open(private_key_name, NULL, NULL, NULL, NULL); + ERR(!store, "OSSL_STORE_open"); + + while (!OSSL_STORE_eof(store)) { + OSSL_STORE_INFO *info = OSSL_STORE_load(store); + + if (!info) { + drain_openssl_errors(__LINE__, 0); + continue; + } + if (OSSL_STORE_INFO_get_type(info) == OSSL_STORE_INFO_PKEY) { + private_key = OSSL_STORE_INFO_get1_PKEY(info); + ERR(!private_key, "OSSL_STORE_INFO_get1_PKEY"); + } + OSSL_STORE_INFO_free(info); + if (private_key) + break; + } + OSSL_STORE_close(store); +#elif defined(USE_PKCS11_ENGINE) + ENGINE *e; + + ENGINE_load_builtin_engines(); + drain_openssl_errors(__LINE__, 1); + e = ENGINE_by_id("pkcs11"); + ERR(!e, "Load PKCS#11 ENGINE"); + if (ENGINE_init(e)) + drain_openssl_errors(__LINE__, 1); + else + ERR(1, "ENGINE_init"); + if (key_pass) + ERR(!ENGINE_ctrl_cmd_string(e, "PIN", key_pass, 0), "Set PKCS#11 PIN"); + private_key = ENGINE_load_private_key(e, private_key_name, NULL, NULL); + ERR(!private_key, "%s", private_key_name); +#else + fprintf(stderr, "no pkcs11 engine/provider available\n"); + exit(1); +#endif + return private_key; +} + +static EVP_PKEY *read_private_key(const char *private_key_name) +{ + if (!strncmp(private_key_name, "pkcs11:", 7)) { + return read_private_key_pkcs11(private_key_name); + } else { + EVP_PKEY *private_key; + BIO *b; + + b = BIO_new_file(private_key_name, "rb"); + ERR(!b, "%s", private_key_name); + private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, + NULL); + ERR(!private_key, "%s", private_key_name); + BIO_free(b); + + return private_key; + } +} + +static X509 *read_x509(const char *x509_name) +{ + unsigned char buf[2]; + X509 *x509; + BIO *b; + int n; + + b = BIO_new_file(x509_name, "rb"); + ERR(!b, "%s", x509_name); + + /* 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_name); + exit(1); + } + if (n >= 0) { + fprintf(stderr, "%s: Short read\n", x509_name); + exit(1); + } + ERR(1, "%s", x509_name); + } + + ERR(BIO_reset(b) != 0, "%s", x509_name); + + 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); + + BIO_free(b); + ERR(!x509, "%s", x509_name); + + return x509; +} + +int main(int argc, char **argv) +{ + struct module_signature sig_info = { .id_type = PKEY_ID_PKCS7 }; + char *hash_algo = NULL; + char *private_key_name = NULL, *raw_sig_name = NULL; + char *x509_name, *bin_name, *loader_name, *dest_name; + bool save_sig = false, replace_orig; + bool sign_only = false; + bool raw_sig = false; + unsigned char buf[4096]; + unsigned long loader_size, sig_size; + unsigned int use_signed_attrs; + const EVP_MD *digest_algo; + EVP_PKEY *private_key; +#ifndef USE_PKCS7 + CMS_ContentInfo *cms = NULL; + unsigned int use_keyid = 0; +#else + PKCS7 *pkcs7 = NULL; +#endif + X509 *x509; + BIO *bd, *bm, *bl; + int opt, n; + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + ERR_clear_error(); + + key_pass = getenv("KBUILD_SIGN_PIN"); + +#ifndef USE_PKCS7 + use_signed_attrs = CMS_NOATTR; +#else + use_signed_attrs = PKCS7_NOATTR; +#endif + + do { + opt = getopt(argc, argv, "sdpk"); + switch (opt) { + case 's': raw_sig = true; break; + case 'p': save_sig = true; break; + case 'd': sign_only = true; save_sig = true; break; +#ifndef USE_PKCS7 + case 'k': use_keyid = CMS_USE_KEYID; break; +#endif + case -1: break; + default: format(); + } + } while (opt != -1); + + argc -= optind; + argv += optind; + if (argc < 5 || argc > 6) + format(); + + if (raw_sig) { + raw_sig_name = argv[0]; + hash_algo = argv[1]; + } else { + hash_algo = argv[0]; + private_key_name = argv[1]; + } + x509_name = argv[2]; + bin_name = argv[3]; + loader_name = argv[4]; + if (argc == 6 && strcmp(argv[4], argv[5]) != 0) { + dest_name = argv[5]; + replace_orig = false; + } else { + ERR(asprintf(&dest_name, "%s.~signed~", loader_name) < 0, + "asprintf"); + replace_orig = true; + } + +#ifdef USE_PKCS7 + if (strcmp(hash_algo, "sha1") != 0) { + fprintf(stderr, "sign-file: %s only supports SHA1 signing\n", + OPENSSL_VERSION_TEXT); + exit(3); + } +#endif + + /* Open the bin file */ + bm = BIO_new_file(bin_name, "rb"); + ERR(!bm, "%s", bin_name); + + if (!raw_sig) { + /* Read the private key and the X.509 cert the PKCS#7 message + * will point to. + */ + private_key = read_private_key(private_key_name); + x509 = read_x509(x509_name); + + /* Digest the module data. */ + OpenSSL_add_all_digests(); + drain_openssl_errors(__LINE__, 0); + digest_algo = EVP_get_digestbyname(hash_algo); + ERR(!digest_algo, "EVP_get_digestbyname"); + +#ifndef USE_PKCS7 + /* 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); + ERR(!cms, "CMS_sign"); + + ERR(!CMS_add1_signer(cms, x509, private_key, digest_algo, + CMS_NOCERTS | CMS_BINARY | + CMS_NOSMIMECAP | use_keyid | + use_signed_attrs), + "CMS_add1_signer"); + ERR(CMS_final(cms, bm, NULL, CMS_NOCERTS | CMS_BINARY) != 1, + "CMS_final"); + +#else + pkcs7 = PKCS7_sign(x509, private_key, NULL, bm, + PKCS7_NOCERTS | PKCS7_BINARY | + PKCS7_DETACHED | use_signed_attrs); + ERR(!pkcs7, "PKCS7_sign"); +#endif + + if (save_sig) { + char *sig_file_name; + BIO *b; + + ERR(asprintf(&sig_file_name, "%s.p7s", bin_name) < 0, + "asprintf"); + b = BIO_new_file(sig_file_name, "wb"); + ERR(!b, "%s", sig_file_name); +#ifndef USE_PKCS7 + ERR(i2d_CMS_bio_stream(b, cms, NULL, 0) != 1, + "%s", sig_file_name); +#else + ERR(i2d_PKCS7_bio(b, pkcs7) != 1, + "%s", sig_file_name); +#endif + BIO_free(b); + } + + if (sign_only) { + BIO_free(bm); + return 0; + } + } + + /* Open the destination file now so that we can shovel the loader data + * across as we read it. + */ + bd = BIO_new_file(dest_name, "wb"); + ERR(!bd, "%s", dest_name); + + bl = BIO_new_file(loader_name, "rb"); + ERR(!bl, "%s", loader_name); + + + /* Append the marker and the PKCS#7 message to the destination file */ + ERR(BIO_reset(bm) < 0, "%s", bin_name); + ERR(BIO_reset(bl) < 0, "%s", loader_name); + while ((n = BIO_read(bl, buf, sizeof(buf))), + n > 0) { + ERR(BIO_write(bd, buf, n) < 0, "%s", dest_name); + } + BIO_free(bl); + BIO_free(bm); + ERR(n < 0, "%s", loader_name); + loader_size = BIO_number_written(bd); + + if (!raw_sig) { +#ifndef USE_PKCS7 + ERR(i2d_CMS_bio_stream(bd, cms, NULL, 0) != 1, "%s", dest_name); +#else + ERR(i2d_PKCS7_bio(bd, pkcs7) != 1, "%s", dest_name); +#endif + } else { + BIO *b; + + /* Read the raw signature file and write the data to the + * destination file + */ + b = BIO_new_file(raw_sig_name, "rb"); + ERR(!b, "%s", raw_sig_name); + while ((n = BIO_read(b, buf, sizeof(buf))), n > 0) + ERR(BIO_write(bd, buf, n) < 0, "%s", dest_name); + BIO_free(b); + } + + sig_size = BIO_number_written(bd) - loader_size; + sig_info.sig_len = htonl(sig_size); + ERR(BIO_write(bd, &sig_info, sizeof(sig_info)) < 0, "%s", dest_name); + ERR(BIO_write(bd, magic_number, sizeof(magic_number) - 1) < 0, "%s", dest_name); + + ERR(BIO_free(bd) != 1, "%s", dest_name); + + /* Finally, if we're signing in place, replace the original. */ + if (replace_orig) + ERR(rename(dest_name, loader_name) < 0, "%s", dest_name); + + return 0; +} From patchwork Fri Mar 21 16:45:05 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Blaise Boscaccy X-Patchwork-Id: 14025782 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 2AB0222DF8A; Fri, 21 Mar 2025 16:46:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=13.77.154.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742575587; cv=none; b=sLsBrBxJlFOXTujRlvfp7Gl9/lowmYQxw5TlfBhwdm6ViYFWdX85sLdTlsLagVhyFyiJtJF3J9kBMdnE4CD4pZiPLegaXR736HLW9T1VFQNsPsYigokVoNYI8q63yBc4O7QWUPQ/6niWx6cN8ch7OjZFIpUcS3nQfCbtr5WxCWA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742575587; c=relaxed/simple; bh=jQEKhYagKZnxJeEZcSf48KDXQoEqpwq7g7rCaaNBsdg=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dsxbdvC4Edw44DaZz4r+iWExQCoai5jWLcJHgphChkVRe80+z5nH4I6F9gh3tEuJTw3kNIHyXfoc15nUxV1gkeBXbh1enfqtFXMepqUP91ryi4gSWElNJVJZTNc0XigjTfJW0rU1BFaWptyZ4PzmbYGMKpKChn6Z3G9lI3gUQic= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com; spf=pass smtp.mailfrom=linux.microsoft.com; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b=qjzGXKnt; arc=none smtp.client-ip=13.77.154.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b="qjzGXKnt" Received: from narnia.corp.microsoft.com (unknown [167.220.2.28]) by linux.microsoft.com (Postfix) with ESMTPSA id 68E0E2025387; Fri, 21 Mar 2025 09:46:18 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 68E0E2025387 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1742575585; bh=P9LyR8pi1VXUSmUxejT8lJYIiGK/ypJIKiMUYzd0hOs=; h=From:To:Subject:Date:In-Reply-To:References:From; b=qjzGXKntmEFem+89nS5SJHQzVz0btQGimlBRvbeOrkQtp1a0eVN7Jh1Ura/vZbgn3 0+NSKw4GO06BFIV27GATLyPxopgY2nIprhyXf3if/BCxyzZzvzXG7U/GZwEAetERhe XqLyYAUjgnYaqX4Fp65lMSnrbXb4ET5G3IBEShE8= From: Blaise Boscaccy To: Jonathan Corbet , David Howells , Herbert Xu , "David S. Miller" , Paul Moore , James Morris , "Serge E. Hallyn" , Masahiro Yamada , Nathan Chancellor , Nicolas Schier , Shuah Khan , =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= , Nick Desaulniers , Bill Wendling , Justin Stitt , Blaise Boscaccy , Jarkko Sakkinen , Jan Stancek , Neal Gompa , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, keyrings@vger.kernel.org, linux-crypto@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kbuild@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, llvm@lists.linux.dev, nkapron@google.com, teknoraver@meta.com, roberto.sassu@huawei.com, xiyou.wangcong@gmail.com Subject: [RFC PATCH security-next 3/4] hornet: Add an example lskel data extactor script Date: Fri, 21 Mar 2025 09:45:05 -0700 Message-ID: <20250321164537.16719-4-bboscaccy@linux.microsoft.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250321164537.16719-1-bboscaccy@linux.microsoft.com> References: <20250321164537.16719-1-bboscaccy@linux.microsoft.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Patchwork-State: RFC This script eases lskel developments against hornet by generating the data payload used for code signing. It extracts the data out of the autogenerated lskel header that gets created via bpftool. Signed-off-by: Blaise Boscaccy --- scripts/hornet/extract-skel.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 scripts/hornet/extract-skel.sh diff --git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh new file mode 100755 index 0000000000000..9ace78794b85e --- /dev/null +++ b/scripts/hornet/extract-skel.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Microsoft Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. + +function usage() { + echo "Sample script for extracting instructions and map data out of" + echo "autogenerated eBPF lskel headers" + echo "" + echo "USAGE: header_file output_file" + exit +} + +ARGC=$# + +EXPECTED_ARGS=2 + +if [ $ARGC -ne $EXPECTED_ARGS ] ; then + usage +else + printf $(gcc -E $1 | grep "static const char opts_insn" | \ + awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g') > $2 + printf $(gcc -E $1 | grep "static const char opts_data" | \ + awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g') >> $2 +fi From patchwork Fri Mar 21 16:45:06 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Blaise Boscaccy X-Patchwork-Id: 14025783 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 4FC7522D4F1; Fri, 21 Mar 2025 16:46:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=13.77.154.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742575597; cv=none; b=D/w1VOVVET8NSIfUsJ052taIY2xov3U52caVgBNNwNlr+ZrBx1/L4MqUNX0/FreuDZFguOBEpKvSjor3yQjucUUgmDGBPiHwmsvp/wOa2nUt3dCcikHNpz53bS1HP95pl6Q619LMknSd1APIWKMxDsc4Ds3ggXMioYTzLe6hcZE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742575597; c=relaxed/simple; bh=xPu6bWbiDSkAMwrWg2n9WeSm35U8Pgyygg+KOPt0bbU=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Cxuv8xlwZGxbFcjVtCQfh+PAZsFVVPZ5oYca8nPGrRqseM/jKaH5w1rlE+gOtG+VSNtyK1IW/42yfeGC4ip6w4M68j/xTWOKHjGKqa1HAYYTQwMnUVe3FeMaynvx3L5b/vIPKw9J7Ne2u4d43W77TJzersoxHgCBpBU/Itk7eL4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com; spf=pass smtp.mailfrom=linux.microsoft.com; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b=f7FyeNFg; arc=none smtp.client-ip=13.77.154.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b="f7FyeNFg" Received: from narnia.corp.microsoft.com (unknown [167.220.2.28]) by linux.microsoft.com (Postfix) with ESMTPSA id 82BC72025389; Fri, 21 Mar 2025 09:46:26 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 82BC72025389 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1742575594; bh=jCQG/f9mso2uZ0UQYO3u+rgplW0McG0EQhJ+HbLrs+k=; h=From:To:Subject:Date:In-Reply-To:References:From; b=f7FyeNFg+XQdXM8uWdkpzsBDBJHkhAWjDy9MIEDbBem62if8Qj94fO1tOF/hXDYNx fW2+7L/T22bWmKF1eKkUWiBQcj0/hYQB6CVsq0Rnu1oCrW6xpQSc9PNo5YJ59BGYmQ bmRYnpS1aXA3AunaJAsUYoGkXPnJajvRNlThDLqY= From: Blaise Boscaccy To: Jonathan Corbet , David Howells , Herbert Xu , "David S. Miller" , Paul Moore , James Morris , "Serge E. Hallyn" , Masahiro Yamada , Nathan Chancellor , Nicolas Schier , Shuah Khan , =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= , Nick Desaulniers , Bill Wendling , Justin Stitt , Blaise Boscaccy , Jarkko Sakkinen , Jan Stancek , Neal Gompa , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, keyrings@vger.kernel.org, linux-crypto@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kbuild@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, llvm@lists.linux.dev, nkapron@google.com, teknoraver@meta.com, roberto.sassu@huawei.com, xiyou.wangcong@gmail.com Subject: [RFC PATCH security-next 4/4] selftests/hornet: Add a selftest for the hornet LSM Date: Fri, 21 Mar 2025 09:45:06 -0700 Message-ID: <20250321164537.16719-5-bboscaccy@linux.microsoft.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250321164537.16719-1-bboscaccy@linux.microsoft.com> References: <20250321164537.16719-1-bboscaccy@linux.microsoft.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Patchwork-State: RFC This selftest tests contains a simple testcase that utilizes an lskel loader. One version of the lskel is signed with the autogenerated module signing key, another is not. A test driver attempts to load the lskels. With hornet enabled, the signed version should successfully be loaded, and the unsigned version should fail. Signed-off-by: Blaise Boscaccy --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/hornet/Makefile | 51 ++++++++++++++++++++ tools/testing/selftests/hornet/loader.c | 21 ++++++++ tools/testing/selftests/hornet/trivial.bpf.c | 33 +++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 tools/testing/selftests/hornet/Makefile create mode 100644 tools/testing/selftests/hornet/loader.c create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 8daac70c2f9d2..fce32ee4de328 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -41,6 +41,7 @@ TARGETS += ftrace TARGETS += futex TARGETS += gpio TARGETS += hid +TARGETS += hornet TARGETS += intel_pstate TARGETS += iommu TARGETS += ipc diff --git a/tools/testing/selftests/hornet/Makefile b/tools/testing/selftests/hornet/Makefile new file mode 100644 index 0000000000000..93da70f41d40c --- /dev/null +++ b/tools/testing/selftests/hornet/Makefile @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../../../build/Build.include +include ../../../scripts/Makefile.arch +include ../../../scripts/Makefile.include + +CLANG ?= clang +CFLAGS := -g -O2 -Wall +BPFTOOL ?= bpftool +SCRIPTSDIR := $(abspath ../../../../scripts/hornet) +TOOLSDIR := $(abspath ../../..) +LIBDIR := $(TOOLSDIR)/lib +BPFDIR := $(LIBDIR)/bpf +TOOLSINCDIR := $(TOOLSDIR)/include +APIDIR := $(TOOLSINCDIR)/uapi +CERTDIR := $(abspath ../../../../certs) + +TEST_GEN_PROGS_EXTENDED := loader +TEST_GEN_PROGS := signed_loader +TEST_PROGS := fail_loader +TEST_GEN_FILES := vmlinux.h loader.h trivial.bin trivial.bpf.o +$(TEST_GEN_PROGS): LDLIBS += -lbpf +$(TEST_GEN_PROGS): $(TEST_GEN_FILES) + +include ../lib.mk + +BPF_CFLAGS := -target bpf \ + -D__TARGET_ARCH_$(ARCH) \ + -I/usr/include/$(shell uname -m)-linux-gnu \ + $(KHDR_INCLUDES) +vmlinux.h: + $(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h + +trivial.bpf.o: trivial.bpf.c vmlinux.h + $(CLANG) $(CFLAGS) $(BPF_CFLAGS) -c $< -o $@ + +loader.h: trivial.bpf.o + $(BPFTOOL) gen skeleton -L $< name trivial > $@ + +trivial.bin: loader.h + $(SCRIPTSDIR)/extract-skel.sh $< $@ + +loader: loader.c loader.h + $(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) $< -o $@ -lbpf + +fail_loader: fail_loader.c loader.h + $(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) $< -o $@ -lbpf + +signed_loader: trivial.bin loader fail_loader + $(SCRIPTSDIR)/sign-ebpf sha256 $(CERTDIR)/signing_key.pem $(CERTDIR)/signing_key.x509 \ + trivial.bin loader signed_loader + chmod u+x $@ diff --git a/tools/testing/selftests/hornet/loader.c b/tools/testing/selftests/hornet/loader.c new file mode 100644 index 0000000000000..9a43bb012d1b2 --- /dev/null +++ b/tools/testing/selftests/hornet/loader.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +#include +#include +#include +#include +#include +#include +#include "loader.h" + +int main(int argc, char **argv) +{ + struct trivial *skel; + + skel = trivial__open_and_load(); + if (!skel) + return -1; + + trivial__destroy(skel); + return 0; +} diff --git a/tools/testing/selftests/hornet/trivial.bpf.c b/tools/testing/selftests/hornet/trivial.bpf.c new file mode 100644 index 0000000000000..d38c5b53ff932 --- /dev/null +++ b/tools/testing/selftests/hornet/trivial.bpf.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +#include "vmlinux.h" + +#include +#include +#include + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +int monitored_pid = 0; + +SEC("tracepoint/syscalls/sys_enter_unlinkat") +int handle_enter_unlink(struct trace_event_raw_sys_enter *ctx) +{ + char filename[128] = { 0 }; + struct task_struct *task; + unsigned long start_time = 0; + int pid = bpf_get_current_pid_tgid() >> 32; + char *pathname_ptr = (char *) BPF_CORE_READ(ctx, args[1]); + + bpf_probe_read_str(filename, sizeof(filename), pathname_ptr); + task = (struct task_struct *)bpf_get_current_task(); + start_time = BPF_CORE_READ(task, start_time); + + bpf_printk("BPF triggered unlinkat by PID: %d, start_time %ld. pathname = %s", + pid, start_time, filename); + + if (monitored_pid == pid) + bpf_printk("target pid found"); + + return 0; +}