public inbox for linux-crypto@vger.kernel.org
 help / color / mirror / Atom feed
From: XIAO WU <shawdoxwu@gmail.com>
To: bot+bpf-ci@kernel.org
Cc: andrii@kernel.org, ast@kernel.org, bpf@vger.kernel.org,
	clm@meta.com, daniel@iogearbox.net, davem@davemloft.net,
	eddyz87@gmail.com, git@danielhodges.dev, haoluo@google.com,
	herbert@gondor.apana.org.au, ihor.solodrai@linux.dev,
	john.fastabend@gmail.com, jolsa@kernel.org, kpsingh@kernel.org,
	linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-kselftest@vger.kernel.org, martin.lau@kernel.org,
	martin.lau@linux.dev, sdf@fomichev.me, song@kernel.org,
	vadim.fedorenko@linux.dev, yatsenko@meta.com,
	yonghong.song@linux.dev
Subject: Re: [PATCH bpf-next v5 2/7] crypto: Add BPF hash algorithm type registration module
Date: Thu, 23 Apr 2026 15:44:55 +0800	[thread overview]
Message-ID: <20260423154455.000021e2@gmail.com> (raw)
In-Reply-To: <e3277b8c7dfe09fae49d0ea7caacc2327f37a18261176964b0be6a67da50c6be@mail.kernel.org>

Hi,

> Can this cause a NULL pointer dereference when a BPF program calls
> bpf_crypto_ctx_create() with type="hash"?
>
> The bpf_crypto_ctx_create() function in kernel/bpf/crypto.c
> unconditionally calls type->setkey(), type->ivsize(), and
> type->statesize():
>
>     *err = type->setkey(ctx->tfm, params->key, params->key_len);
>     ...
>     ctx->siv_len = type->ivsize(ctx->tfm) + type->statesize(ctx->tfm);
>
> But bpf_crypto_shash_type does not implement these callbacks, leaving
> them as NULL.
>
> Note: This appears to be fixed later in the series by commit
> 76d771a64b50 ("bpf: Add hash kfunc for cryptographic hashing") which
> adds NULL checks before calling these function pointers. Should this
> commit be squashed with 76d771a64b50 to ensure each patch in the
> series is bisectable without introducing crashes?

Yes, confirmed.

I reproduced this on x86_64 with a sleepable BPF syscall program that
calls bpf_crypto_ctx_create() with:
- type = "hash"
- algo = "sha256"
- key_len = 1

That reaches the path where type->setkey/type->ivsize/type->statesize
are used without NULL checks for the hash type, and triggers the NULL
dereference as pointed out.

Below is the reproducer (inlined, no attachment):

--8<--
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/bpf.h>
#include <linux/btf.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>

#ifndef __NR_bpf
#define __NR_bpf 321
#endif

#define LOG_BUF_SIZE (1 << 20)

static int bpf_sys(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int
size) {
	return syscall(__NR_bpf, cmd, attr, size);
}

static size_t btf_type_size(const struct btf_type *t)
{
	__u16 vlen = BTF_INFO_VLEN(t->info);
	__u32 kind = BTF_INFO_KIND(t->info);
	size_t sz = sizeof(*t);

	switch (kind) {
	case BTF_KIND_INT: sz += sizeof(__u32); break;
	case BTF_KIND_ARRAY: sz += sizeof(struct btf_array); break;
	case BTF_KIND_STRUCT:
	case BTF_KIND_UNION: sz += (size_t)vlen * sizeof(struct
	btf_member); break; case BTF_KIND_ENUM: sz += (size_t)vlen *
	sizeof(struct btf_enum); break; case BTF_KIND_FUNC_PROTO: sz +=
	(size_t)vlen * sizeof(struct btf_param); break; case
	BTF_KIND_VAR: sz += sizeof(struct btf_var); break; case
	BTF_KIND_DATASEC: sz += (size_t)vlen * sizeof(struct
	btf_var_secinfo); break; case BTF_KIND_DECL_TAG: sz +=
	sizeof(struct btf_decl_tag); break; case BTF_KIND_ENUM64: sz +=
	(size_t)vlen * sizeof(struct btf_enum64); break; default:
	break; } return sz;
}

static int find_vmlinux_func_btf_id(const char *func_name)
{
	int fd = -1, ret = -1;
	struct stat st;
	unsigned char *blob = NULL;
	struct btf_header *hdr;
	char *types, *strs;
	__u32 off, id;
	ssize_t n;
	size_t got = 0;

	fd = open("/sys/kernel/btf/vmlinux", O_RDONLY);
	if (fd < 0) goto out;
	if (fstat(fd, &st) < 0 || st.st_size <= 0) goto out;

	blob = malloc(st.st_size);
	if (!blob) goto out;

	while (got < (size_t)st.st_size) {
		n = read(fd, blob + got, (size_t)st.st_size - got);
		if (n < 0) {
			if (errno == EINTR) continue;
			goto out;
		}
		if (n == 0) break;
		got += (size_t)n;
	}
	if (got < sizeof(*hdr)) goto out;

	hdr = (struct btf_header *)blob;
	if (hdr->magic != BTF_MAGIC || hdr->version != BTF_VERSION)
	goto out; if ((size_t)hdr->hdr_len + hdr->type_off +
	hdr->type_len > got) goto out; if ((size_t)hdr->hdr_len +
	hdr->str_off + hdr->str_len > got) goto out;

	types = (char *)blob + hdr->hdr_len + hdr->type_off;
	strs = (char *)blob + hdr->hdr_len + hdr->str_off;

	for (off = 0, id = 1; off < hdr->type_len; id++) {
		struct btf_type *t = (struct btf_type *)(types + off);
		const char *name = t->name_off ? (strs + t->name_off) :
	""; size_t sz = btf_type_size(t);

		if (sz == 0 || off + sz > hdr->type_len) goto out;
		if (BTF_INFO_KIND(t->info) == BTF_KIND_FUNC &&
		strcmp(name, func_name) == 0) { ret = (int)id;
			goto out;
		}
		off += sz;
	}

out:
	free(blob);
	if (fd >= 0) close(fd);
	return ret;
}

int main(void)
{
	int create_id =
find_vmlinux_func_btf_id("bpf_crypto_ctx_create"); int release_id =
find_vmlinux_func_btf_id("bpf_crypto_ctx_release"); if (create_id <= 0
|| release_id <= 0) { fprintf(stderr, "failed resolving BTF IDs:
create=%d release=%d\n", create_id, release_id); return 1;
	}

	enum { PARAMS_BASE = 424, ERR_BASE = 16, PARAMS_SIZE = 408 };

	struct bpf_insn insn[128];
	int pc = 0, off;

	for (off = -8; off >= -424; off -= 8)
		insn[pc++] = (struct bpf_insn){
			.code = BPF_ST | BPF_MEM | BPF_DW,
			.dst_reg = BPF_REG_10,
			.off = off,
			.imm = 0,
		};

	insn[pc++] = (struct bpf_insn){ .code = BPF_ST | BPF_MEM |
	BPF_W, .dst_reg = BPF_REG_10, .off = -PARAMS_BASE + 0, .imm =
	0x68736168 }; insn[pc++] = (struct bpf_insn){ .code = BPF_ST |
	BPF_MEM | BPF_W, .dst_reg = BPF_REG_10, .off = -PARAMS_BASE +
	16, .imm = 0x32616873 }; insn[pc++] = (struct bpf_insn){ .code
	= BPF_ST | BPF_MEM | BPF_W, .dst_reg = BPF_REG_10, .off =
	-PARAMS_BASE + 20, .imm = 0x00003635 }; insn[pc++] = (struct
	bpf_insn){ .code = BPF_ST | BPF_MEM | BPF_B, .dst_reg =
	BPF_REG_10, .off = -PARAMS_BASE + 144, .imm = 0x11 };
	insn[pc++] = (struct bpf_insn){ .code = BPF_ST | BPF_MEM |
	BPF_W, .dst_reg = BPF_REG_10, .off = -PARAMS_BASE + 400, .imm =
	1 };

	insn[pc++] = (struct bpf_insn){ .code = BPF_ALU64 | BPF_MOV |
	BPF_X, .dst_reg = BPF_REG_1, .src_reg = BPF_REG_10 };
	insn[pc++] = (struct bpf_insn){ .code = BPF_ALU64 | BPF_ADD |
	BPF_K, .dst_reg = BPF_REG_1, .imm = -PARAMS_BASE }; insn[pc++]
	= (struct bpf_insn){ .code = BPF_ALU64 | BPF_MOV | BPF_K,
	.dst_reg = BPF_REG_2, .imm = PARAMS_SIZE }; insn[pc++] =
	(struct bpf_insn){ .code = BPF_ALU64 | BPF_MOV | BPF_X,
	.dst_reg = BPF_REG_3, .src_reg = BPF_REG_10 }; insn[pc++] =
	(struct bpf_insn){ .code = BPF_ALU64 | BPF_ADD | BPF_K,
	.dst_reg = BPF_REG_3, .imm = -ERR_BASE };

	insn[pc++] = (struct bpf_insn){ .code = BPF_JMP | BPF_CALL,
	.src_reg = BPF_PSEUDO_KFUNC_CALL, .imm = create_id };

	insn[pc++] = (struct bpf_insn){ .code = BPF_JMP | BPF_JEQ |
	BPF_K, .dst_reg = BPF_REG_0, .off = 2, .imm = 0 }; insn[pc++] =
	(struct bpf_insn){ .code = BPF_ALU64 | BPF_MOV | BPF_X,
	.dst_reg = BPF_REG_1, .src_reg = BPF_REG_0 }; insn[pc++] =
	(struct bpf_insn){ .code = BPF_JMP | BPF_CALL, .src_reg =
	BPF_PSEUDO_KFUNC_CALL, .imm = release_id };

	insn[pc++] = (struct bpf_insn){ .code = BPF_ALU64 | BPF_MOV |
	BPF_K, .dst_reg = BPF_REG_0, .imm = 0 }; insn[pc++] = (struct
	bpf_insn){ .code = BPF_JMP | BPF_EXIT };

	char logbuf[LOG_BUF_SIZE];
	char lic[] = "GPL";
	union bpf_attr attr;
	memset(logbuf, 0, sizeof(logbuf));
	memset(&attr, 0, sizeof(attr));

	attr.prog_type = BPF_PROG_TYPE_SYSCALL;
	attr.prog_flags = BPF_F_SLEEPABLE;
	attr.insn_cnt = pc;
	attr.insns = (uint64_t)(uintptr_t)insn;
	attr.license = (uint64_t)(uintptr_t)lic;
	attr.log_level = 1;
	attr.log_size = sizeof(logbuf);
	attr.log_buf = (uint64_t)(uintptr_t)logbuf;
	memcpy(attr.prog_name, "poc_hash_bug", 12);

	int prog_fd = bpf_sys(BPF_PROG_LOAD, &attr, sizeof(attr));
	if (prog_fd < 0) {
		fprintf(stderr, "BPF_PROG_LOAD failed: errno=%d
	(%s)\n", errno, strerror(errno)); fprintf(stderr, "Verifier
	log:\n%s\n", logbuf); return 2;
	}

	union bpf_attr run;
	memset(&run, 0, sizeof(run));
	run.test.prog_fd = prog_fd;
	(void)bpf_sys(BPF_PROG_TEST_RUN, &run, sizeof(run));
	close(prog_fd);
	return 0;
}
--8<--

I agree this should be fixed at the patch granularity level. I will
squash the NULL-check fix into patch 2 in v6 so each patch remains
bisectable and does not introduce a crash window.

As this issue is already publicly discussed on bpf-next and raised by
CI review, replying on-list is appropriate.

Signed-off-by: XIAO WU <shawdoxwu@gmail.com>

Thanks

  reply	other threads:[~2026-04-23  9:39 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-20 18:46 [PATCH bpf-next v5 0/7] Add cryptographic hash and signature verification kfuncs to BPF Daniel Hodges
2026-01-20 18:46 ` [PATCH bpf-next v5 1/7] bpf: Extend bpf_crypto_type with hash operations Daniel Hodges
2026-01-20 18:46 ` [PATCH bpf-next v5 2/7] crypto: Add BPF hash algorithm type registration module Daniel Hodges
2026-01-20 19:13   ` bot+bpf-ci
2026-04-23  7:44     ` XIAO WU [this message]
2026-01-20 18:46 ` [PATCH bpf-next v5 3/7] crypto: Add BPF signature " Daniel Hodges
2026-01-20 19:13   ` bot+bpf-ci
2026-01-20 18:46 ` [PATCH bpf-next v5 4/7] bpf: Add hash kfunc for cryptographic hashing Daniel Hodges
2026-01-20 18:46 ` [PATCH bpf-next v5 5/7] selftests/bpf: Add tests for bpf_crypto_hash kfunc Daniel Hodges
2026-01-20 18:47 ` [PATCH bpf-next v5 6/7] bpf: Add signature verification kfuncs Daniel Hodges
2026-01-20 18:47 ` [PATCH bpf-next v5 7/7] selftests/bpf: Add tests for " Daniel Hodges

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=20260423154455.000021e2@gmail.com \
    --to=shawdoxwu@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bot+bpf-ci@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=clm@meta.com \
    --cc=daniel@iogearbox.net \
    --cc=davem@davemloft.net \
    --cc=eddyz87@gmail.com \
    --cc=git@danielhodges.dev \
    --cc=haoluo@google.com \
    --cc=herbert@gondor.apana.org.au \
    --cc=ihor.solodrai@linux.dev \
    --cc=john.fastabend@gmail.com \
    --cc=jolsa@kernel.org \
    --cc=kpsingh@kernel.org \
    --cc=linux-crypto@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=martin.lau@kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=sdf@fomichev.me \
    --cc=song@kernel.org \
    --cc=vadim.fedorenko@linux.dev \
    --cc=yatsenko@meta.com \
    --cc=yonghong.song@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox