[PATCH v5 06/10] security: Hornet LSM
Fan Wu
wufan at kernel.org
Tue Apr 21 00:08:42 UTC 2026
On Mon, Apr 20, 2026 at 2:27 PM Blaise Boscaccy
<bboscaccy at linux.microsoft.com> wrote:
>
> This adds the Hornet Linux Security Module which provides enhanced
> signature verification and data validation for eBPF programs. This
> allows users to continue to maintain an invariant that all code
> running inside of the kernel has actually been signed and verified, by
> the kernel.
>
> This effort builds upon the currently excepted upstream solution. It
> further hardens it by providing deterministic, in-kernel checking of
> map hashes to solidify auditing along with preventing TOCTOU attacks
> against lskel map hashes.
>
> Target map hashes are passed in via PKCS#7 signed attributes. Hornet
> determines the extent which the eBFP program is signed and defers to
> other LSMs for policy decisions.
>
> Signed-off-by: Blaise Boscaccy <bboscaccy at linux.microsoft.com>
> Nacked-by: Alexei Starovoitov <alexei.starovoitov at gmail.com>
...
> 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
The dependency doesn't seem to be complete, for example,
PKCS7_MESSAGE_PARSER is not selected.
> + 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..26b6f954f762e
> --- /dev/null
> +++ b/security/hornet/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_SECURITY_HORNET) := hornet.o
> +
> +hornet-y := hornet.asn1.o \
> + hornet_lsm.o \
> +
> +$(obj)/hornet.asn1.o: $(obj)/hornet.asn1.c $(obj)/hornet.asn1.h
> diff --git a/security/hornet/hornet.asn1 b/security/hornet/hornet.asn1
> new file mode 100644
> index 0000000000000..c8d47b16b65d7
> --- /dev/null
> +++ b/security/hornet/hornet.asn1
> @@ -0,0 +1,13 @@
> +-- SPDX-License-Identifier: BSD-3-Clause
> +--
> +-- Copyright (C) 2009 IETF Trust and the persons identified as authors
> +-- of the code
I'm not a lawyer, but since this is a new AA which is not in the RFC,
should the copyright belong to IETF?
> +--
> +-- https://www.rfc-editor.org/rfc/rfc5652#section-3
> +
> +HornetData ::= SET OF Map
> +
> +Map ::= SEQUENCE {
> + index INTEGER ({ hornet_map_index }),
> + sha OCTET STRING ({ hornet_map_hash })
> +} ({ hornet_next_map })
> diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
> new file mode 100644
> index 0000000000000..f7d62fe6229c9
> --- /dev/null
> +++ b/security/hornet/hornet_lsm.c
> @@ -0,0 +1,346 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Hornet Linux Security Module
> + *
> + * Author: Blaise Boscaccy <bboscaccy at linux.microsoft.com>
> + *
> + * Copyright (C) 2026 Microsoft Corporation
> + */
> +
> +#include <linux/lsm_hooks.h>
> +#include <uapi/linux/lsm.h>
> +#include <linux/bpf.h>
> +#include <linux/verification.h>
> +#include <crypto/public_key.h>
> +#include <linux/module_signature.h>
> +#include <crypto/pkcs7.h>
> +#include <linux/sort.h>
> +#include <linux/asn1_decoder.h>
> +#include <linux/oid_registry.h>
> +#include "hornet.asn1.h"
> +
> +#define MAX_USED_MAPS 64
> +
> +struct hornet_maps {
> + bpfptr_t fd_array;
> +};
> +
> +/* The only hashing algorithm available is SHA256 due to it be hardcoded
> + in the bpf subsystem. */
> +
> +struct hornet_parse_context {
> + int indexes[MAX_USED_MAPS];
> + bool skips[MAX_USED_MAPS];
> + unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
> + int hash_count;
> +};
> +
> +struct hornet_prog_security_struct {
> + bool checked[MAX_USED_MAPS];
> + unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
> +};
> +
> +struct hornet_map_security_struct {
> + bool checked;
> + int index;
> +};
> +
Can maps be shared between programs? If so, since the LSM blob is
per-map, a second program's load will overwrite map_security->index
set by the first. A later run of the first program would then fail to
find its own map.
> +struct lsm_blob_sizes hornet_blob_sizes __ro_after_init = {
> + .lbs_bpf_map = sizeof(struct hornet_map_security_struct),
> + .lbs_bpf_prog = sizeof(struct hornet_prog_security_struct),
> +};
> +
> +static inline struct hornet_prog_security_struct *
> +hornet_bpf_prog_security(struct bpf_prog *prog)
> +{
> + return prog->aux->security + hornet_blob_sizes.lbs_bpf_prog;
> +}
> +
> +static inline struct hornet_map_security_struct *
> +hornet_bpf_map_security(struct bpf_map *map)
> +{
> + return map->security + hornet_blob_sizes.lbs_bpf_map;
> +}
> +
> +static int hornet_verify_hashes(struct hornet_maps *maps,
> + struct hornet_parse_context *ctx,
> + struct bpf_prog *prog)
> +{
> + int map_fd;
> + u32 i;
> + struct bpf_map *map;
> + int err = 0;
> + unsigned char hash[SHA256_DIGEST_SIZE];
> + struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog);
> + struct hornet_map_security_struct *map_security;
> +
> + for (i = 0; i < ctx->hash_count; i++) {
> + if (ctx->skips[i])
> + continue;
> +
> + err = copy_from_bpfptr_offset(&map_fd, maps->fd_array,
> + ctx->indexes[i] * sizeof(map_fd),
> + sizeof(map_fd));
> + if (err < 0)
> + return LSM_INT_VERDICT_FAULT;
> +
> + CLASS(fd, f)(map_fd);
> + if (fd_empty(f))
> + return LSM_INT_VERDICT_FAULT;
> + if (unlikely(fd_file(f)->f_op != &bpf_map_fops))
> + return LSM_INT_VERDICT_FAULT;
> +
> + map = fd_file(f)->private_data;
> + if (!map->frozen)
> + return LSM_INT_VERDICT_FAULT;
> +
> + map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash);
> +
> + err = memcmp(hash, &ctx->hashes[i * SHA256_DIGEST_SIZE],
> + SHA256_DIGEST_SIZE);
> + if (err)
> + return LSM_INT_VERDICT_UNEXPECTED;
> +
> + security->checked[i] = true;
> + memcpy(&security->hashes[i * SHA256_DIGEST_SIZE], hash, SHA256_DIGEST_SIZE);
> + map_security = hornet_bpf_map_security(map);
> + map_security->checked = true;
> + map_security->index = i;
> + }
> + return LSM_INT_VERDICT_OK;
> +}
> +
> +int hornet_next_map(void *context, size_t hdrlen,
> + unsigned char tag,
> + const void *value, size_t vlen)
> +{
> + struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
> +
> + if (++ctx->hash_count >= MAX_USED_MAPS)
> + return -EINVAL;
> + return 0;
> +}
> +
> +int hornet_map_index(void *context, size_t hdrlen,
> + unsigned char tag,
> + const void *value, size_t vlen)
> +{
> + struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
> +
> + if (vlen > 1)
> + return -EINVAL;
> +
> + ctx->indexes[ctx->hash_count] = *(u8 *)value;
> + return 0;
> +}
> +
> +int hornet_map_hash(void *context, size_t hdrlen,
> + unsigned char tag,
> + const void *value, size_t vlen)
> +
> +{
> + struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
> +
> + if (vlen != SHA256_DIGEST_SIZE && vlen != 0)
> + return -EINVAL;
> +
> + if (vlen) {
> + ctx->skips[ctx->hash_count] = false;
> + memcpy(&ctx->hashes[ctx->hash_count * SHA256_DIGEST_SIZE], value, vlen);
> + } else
> + ctx->skips[ctx->hash_count] = true;
> +
> + return 0;
> +}
> +
> +static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr,
> + struct bpf_token *token, bool is_kernel,
> + enum lsm_integrity_verdict *verdict)
> +{
> + struct hornet_maps maps = {0};
> + bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
> + struct pkcs7_message *msg;
> + struct hornet_parse_context *ctx;
> + void *sig;
> + int err;
> + const void *authattrs;
> + size_t authattrs_len;
> + struct key *key;
> +
> + if (!attr->signature) {
> + *verdict = LSM_INT_VERDICT_UNSIGNED;
> + return 0;
> + }
> +
> + ctx = kzalloc(sizeof(struct hornet_parse_context), GFP_KERNEL);
> + if (!ctx)
> + return -ENOMEM;
> +
> + maps.fd_array = make_bpfptr(attr->fd_array, is_kernel);
> + sig = kzalloc(attr->signature_size, GFP_KERNEL);
> + if (!sig) {
> + err = -ENOMEM;
> + goto out;
> + }
> + err = copy_from_bpfptr(sig, usig, attr->signature_size);
> + if (err != 0)
> + goto cleanup_sig;
> +
> + msg = pkcs7_parse_message(sig, attr->signature_size);
> + if (IS_ERR(msg)) {
> + *verdict = LSM_INT_VERDICT_BADSIG;
> + err = 0;
> + goto cleanup_sig;
> + }
> +
> + if (system_keyring_id_check(attr->keyring_id) == 0)
> + key = (struct key*)(unsigned long)attr->keyring_id;
> + else
> + key = key_ref_to_ptr(lookup_user_key(attr->keyring_id, 0, KEY_DEFER_PERM_CHECK));
You might need to key_put the user key.
> +
> + if (verify_pkcs7_message_sig(prog->insnsi, prog->len * sizeof(struct bpf_insn), msg,
> + key,
> + VERIFYING_BPF_SIGNATURE,
> + NULL, NULL)) {
> + *verdict = LSM_INT_VERDICT_UNKNOWNKEY;
> + err = 0;
> + goto cleanup_msg;
> + }
> +
> + if (pkcs7_get_authattr(msg, OID_hornet_data,
> + &authattrs, &authattrs_len) == -ENODATA) {
> + *verdict = LSM_INT_VERDICT_PARTIALSIG;
> + err = 0;
> + goto cleanup_msg;
> + }
> +
> + err = asn1_ber_decoder(&hornet_decoder, ctx, authattrs, authattrs_len);
> + if (err < 0 || authattrs == NULL) {
> + *verdict = LSM_INT_VERDICT_BADSIG;
> + err = 0;
> + goto cleanup_msg;
> + }
> +
> + err = hornet_verify_hashes(&maps, ctx, prog);
> + if (err == 0)
> + *verdict = LSM_INT_VERDICT_OK;
> + else
> + *verdict = err;
> +
> +cleanup_msg:
> + pkcs7_free_message(msg);
> +cleanup_sig:
> + kfree(sig);
> +out:
> + kfree(ctx);
> + return err;
> +}
> +
> +static const struct lsm_id hornet_lsmid = {
> + .name = "hornet",
> + .id = LSM_ID_HORNET,
> +};
> +
> +static int hornet_bpf_prog_load_integrity(struct bpf_prog *prog, union bpf_attr *attr,
> + struct bpf_token *token, bool is_kernel)
> +{
> + enum lsm_integrity_verdict verdict;
> + int result = hornet_check_program(prog, attr, token, is_kernel, &verdict);
> +
> + if (result < 0)
> + return result;
> +
> + return security_bpf_prog_load_post_integrity(prog, attr, token, is_kernel,
> + &hornet_lsmid, verdict);
> +}
> +
> +static int hornet_verify_map(struct bpf_prog *prog, int index)
> +{
> + unsigned char hash[SHA256_DIGEST_SIZE];
> + int i;
> + struct bpf_map *map;
> + struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog);
> + struct hornet_map_security_struct *map_security;
> +
> + if (!security->checked[index])
> + return 0;
> +
> + for (i = 0; i < prog->aux->used_map_cnt; i++) {
> + map = prog->aux->used_maps[i];
> + map_security = hornet_bpf_map_security(map);
> + if (map_security->index != index)
> + continue;
> +
> + if (!map->frozen)
> + return -EPERM;
> +
> + map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash);
> + if (memcmp(hash, &security->hashes[index * SHA256_DIGEST_SIZE],
> + SHA256_DIGEST_SIZE) != 0)
> + return -EPERM;
> + else
> + return 0;
> + }
> + return -EINVAL;
> +}
> +
> +static int hornet_check_prog_maps(u32 ufd)
> +{
> + CLASS(fd, f)(ufd);
> + struct bpf_prog *prog;
> + int i, result = 0;
> +
> + if (fd_empty(f))
> + return -EBADF;
> + if (fd_file(f)->f_op != &bpf_prog_fops)
> + return -EINVAL;
> +
> + prog = fd_file(f)->private_data;
> +
> + mutex_lock(&prog->aux->used_maps_mutex);
> + if (!prog->aux->used_map_cnt)
> + goto out;
> +
> + for (i = 0; i < prog->aux->used_map_cnt; i++) {
> + result = hornet_verify_map(prog, i);
> + if (result)
> + goto out;
> + }
This loop is kind of confusing for me, I guess it's just iterating
through the maps the program currently in use. I feel the nested loop
is unnecessary and the hash could be saved into the map's blob
instead. Please correct me if I'm wrong here.
-Fan
> +out:
> + mutex_unlock(&prog->aux->used_maps_mutex);
> + return result;
> +}
> +
> +static int hornet_bpf(int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
> +{
> + /* in horent_bpf(), anything that had originated from kernel space we assume
> + has already been checked, in some form or another, so we don't bother
> + checking the intergity of any maps. In hornet_bpf_prog_load_integrity(),
> + hornet doesn't make any opinion on that and delegates that to the downstream
> + policy enforcement. */
> +
> + if (cmd != BPF_PROG_RUN)
> + return 0;
> + if (kernel)
> + return 0;
> +
> + return hornet_check_prog_maps(attr->test.prog_fd);
> +}
> +
> +static struct security_hook_list hornet_hooks[] __ro_after_init = {
> + LSM_HOOK_INIT(bpf_prog_load_integrity, hornet_bpf_prog_load_integrity),
> + LSM_HOOK_INIT(bpf, hornet_bpf),
> +};
> +
> +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) = {
> + .id = &hornet_lsmid,
> + .blobs = &hornet_blob_sizes,
> + .init = hornet_init,
> +};
> --
> 2.53.0
>
More information about the Linux-security-module-archive
mailing list