All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Blaise Boscaccy" <bboscaccy@linux.microsoft.com>
Cc: bpf@vger.kernel.org
Subject: Re: [PATCH v4 06/10] security: Hornet LSM
Date: Thu, 16 Apr 2026 21:24:53 +0000	[thread overview]
Message-ID: <20260416212454.850C2C2BCAF@smtp.kernel.org> (raw)
In-Reply-To: <20260416173500.176716-7-bboscaccy@linux.microsoft.com>

Sashiko AI review found 11 potential issue(s):
- [Critical] Improper error handling in userspace copies leads to security bypass and uninitialized variable use.
- [High] Unchecked `lookup_user_key` return value causes kernel panic.
- [High] Memory and resource leak of key references.
- [High] NULL pointer dereference when invoking `map_get_hash`.
- [High] State corruption via shared BPF map security blob causes denial of service.
- [High] Runtime map verification loop logic allows integrity check bypass.
- [High] File descriptor TOCTOU race condition in `hornet_bpf` hook.
- [Medium] Subsystem guideline violation: Mixing scope-based cleanup with goto.
- [Medium] Out-of-bounds read in `hornet_map_index()` when `vlen == 0`.
- [Medium] Off-by-one error in `hornet_next_map()` rejects valid signatures.
- [Low] Unchecked allocation size causes memory allocation warnings.
--

commit 6fbe5a3d726790559c6e3d3f0367735ef6beae27
Author: Blaise Boscaccy <bboscaccy@linux.microsoft.com>

security: Hornet LSM

This commit adds the Hornet Linux Security Module to provide enhanced
signature verification and data validation for eBPF programs. It verifies
PKCS#7 signatures on eBPF bytecode and associated map hashes at load time,
and re-verifies map hashes before execution.

> diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
> new file mode 100644
> --- /dev/null
> +++ b/security/hornet/hornet_lsm.c
[ ... ]
> +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;

Because copy_from_bpfptr_offset() returns the number of uncopied bytes
(a positive integer) upon failure, does this if statement fail to catch
a failed copy? If the copy fails, it seems map_fd could remain
uninitialized when passed to CLASS(fd, f)(map_fd) below, resulting in
an arbitrary file descriptor lookup.

> +
> +		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);

Does this code safely handle map types that do not implement the
map_get_hash callback? Many standard BPF map types (like
BPF_MAP_TYPE_PROG_ARRAY) do not implement this. Providing a signature
for such a map type might result in a NULL pointer dereference.

> +
> +		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;

Is it possible for state corruption to occur here since BPF maps can be
shared across multiple eBPF programs?

If a second program loads and verifies the same map, it appears it would
overwrite map_security->index with its own signature index. When the
first program is later executed, hornet_verify_map() would see a mismatch,
potentially causing a denial of service regression for the first program.

> +	}
> +	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;

Could this unintentionally reject valid signatures that use exactly 64
maps? Because MAX_USED_MAPS is 64, incrementing the count for the 64th
map makes the condition true, returning -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;

Are we missing a check for vlen == 0 here? If a malformed signature
specifies an integer length of 0, it looks like this could read one
byte out of bounds from the value pointer.

> +	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);

Since attr->signature_size is a user-controlled value, could a very
large size intentionally trigger kernel memory allocation warnings
(OOM splats)? Should this use __GFP_NOWARN or enforce an upper limit?

> +	if (!sig) {
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +	err = copy_from_bpfptr(sig, usig, attr->signature_size);
> +	if (err != 0)
> +		goto cleanup_sig;

Similar to the map copy check earlier, copy_from_bpfptr() returns a positive
integer on failure. This causes hornet_check_program() to return a positive
value when the copy fails.

Looking at hornet_bpf_prog_load_integrity(), it checks if the result
is less than zero:

static int hornet_bpf_prog_load_integrity(...)
{
        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);
}

Could this result in proceeding to security_bpf_prog_load_post_integrity()
with an uninitialized verdict variable? If the stack memory happens to
hold LSM_INT_VERDICT_OK, it seems signature verification would be fully
bypassed.

> +
> +	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));
> +
> +	if (verify_pkcs7_message_sig(prog->insnsi, prog->len * sizeof(struct bpf_insn), msg,
> +				     key,

If lookup_user_key() fails (for example, due to an invalid ID or
permission denied), it returns an error-encoded key_ref_t.

Since IS_ERR() is not used to check the return value, could this pass an
invalid pointer through key_ref_to_ptr() and subsequently to
verify_pkcs7_message_sig(), potentially causing a panic?

Also, when lookup_user_key() successfully finds a key, it increments its
usage count. Is there a missing key_put(key) to drop this reference before
the function returns? Without it, repeated calls could leak struct key
objects.

[ ... ]
> +static int hornet_check_prog_maps(u32 ufd)
> +{
> +	CLASS(fd, f)(ufd);
> +	struct bpf_prog *prog;
> +	int i, result = 0;

This isn't a bug, but using CLASS(fd, f) alongside a goto-based error
handling pattern appears to violate the cleanup guidelines, which
suggest converting all resources to scope-based cleanup or none of them
to prevent inconsistent resource management.

> +
> +	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);

Does using the loop counter i as the signature index for hornet_verify_map()
allow bypassing the integrity check for some maps?

The number of used maps doesn't seem to have a fixed relation to the
signature's map sequence length. If a map was verified at load time with a
signature index greater than or equal to used_map_cnt, it appears this loop
would never call hornet_verify_map() for that index, allowing the map
contents to be modified after loading.

> +		if (result)
> +			goto out;
> +	}
> +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);

Is there a race condition here between the security check and the actual
execution in the syscall?

The hook resolves attr->test.prog_fd, performs checks, and then drops the
file descriptor reference before returning. The bpf() syscall will then
resolve the file descriptor again.

Could a concurrent thread use dup2() to overwrite attr->test.prog_fd
with an unsigned BPF program in the window between the check and the
execution?

> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260416173500.176716-1-bboscaccy@linux.microsoft.com?part=6

  reply	other threads:[~2026-04-16 21:24 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-16 17:33 [PATCH v4 00/10] Reintroduce Hornet LSM Blaise Boscaccy
2026-04-16 17:33 ` [PATCH v4 01/10] crypto: pkcs7: add flag for validated trust on a signed info block Blaise Boscaccy
2026-04-16 19:26   ` sashiko-bot
2026-04-16 17:33 ` [PATCH v4 02/10] crypto: pkcs7: add ability to extract signed attributes by OID Blaise Boscaccy
2026-04-16 19:56   ` sashiko-bot
2026-04-16 17:33 ` [PATCH v4 03/10] crypto: pkcs7: add tests for pkcs7_get_authattr Blaise Boscaccy
2026-04-16 20:17   ` sashiko-bot
2026-04-16 17:33 ` [PATCH v4 04/10] lsm: framework for BPF integrity verification Blaise Boscaccy
2026-04-16 17:33 ` [PATCH v4 05/10] lsm: security: Add additional enum values for bpf integrity checks Blaise Boscaccy
2026-04-16 17:33 ` [PATCH v4 06/10] security: Hornet LSM Blaise Boscaccy
2026-04-16 21:24   ` sashiko-bot [this message]
2026-04-16 17:33 ` [PATCH v4 07/10] hornet: Introduce gen_sig Blaise Boscaccy
2026-04-16 21:33   ` sashiko-bot
2026-04-16 17:33 ` [PATCH v4 08/10] hornet: Add a light skeleton data extractor scripts Blaise Boscaccy
2026-04-16 21:44   ` sashiko-bot
2026-04-16 17:33 ` [PATCH v4 09/10] selftests/hornet: Add a selftest for the Hornet LSM Blaise Boscaccy
2026-04-16 21:55   ` sashiko-bot
2026-04-16 17:33 ` [PATCH v4 10/10] ipe: Add BPF program load policy enforcement via Hornet integration Blaise Boscaccy
2026-04-16 21:03   ` Fan Wu
2026-04-16 22:17   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260416212454.850C2C2BCAF@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=bboscaccy@linux.microsoft.com \
    --cc=bpf@vger.kernel.org \
    --cc=sashiko@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.