Skip to contentSkip to conversation

bpf: Fix NULL-ptr-derefs when showing a void BTF type

PATCH Superseded Jiayuan Chen 3 participants 9 messages Aug 30

Subsystems (3)

v2 of 4 Superseded Latest is v4 →

lore
Share & tools
lore
#0 lore raw

v2 cover bpf: Fix NULL-ptr-derefs when showing a void BTF type

This series fixes two NULL-ptr-derefs in BTF handling.

Patch 1 handles the syzbot report. A key-less BTF (btf_key_type_id == 0) used to be rejected for hash maps, until htab and rhtab gained a ->map_check_btf (to register a dtor) that does not look at the key, so a key-less hash map is now accepted. Dumping it through bpffs feeds the key type_id 0 into btf_type_seq_show() and NULL-derefs in btf_type_show(). Reject it again.

Patch 2 fixes a related, pre-existing crash reachable via bpf_snprintf_btf(). A "const void" type_id (a modifier that resolves to void, present in the vmlinux BTF) NULL-derefs in btf_modifier_show() - void has no ->show op. void has no size and nothing to render, so route the show call sites through a helper that falls back to btf_df_show() - the "<unsupported kind:N>" placeholder already used for FWD/FUNC/FLOAT/DECL_TAG.

Patches 3 and 4 add selftests for the two cases. They are meant to reproduce the crashes: each deliberately walks the faulting path, so on an unfixed kernel it oopses the task (and panics it under panic_on_oops). That is intentional - the tests verify the fix and reproduce the bug - so a static review flagging them for crashing an unfixed kernel can be ignored.

v1 -> v2: AI reported a pre-exist issue. Let's fold it in this series.

v1: https://lore.kernel.org/bpf/[email protected]/

Jiayuan Chen (4):
bpf: Reject key-less BTF for hash maps
bpf: Fix NULL-ptr-deref when showing a void BTF type selftests/bpf: Add test for key-less BTF hash map selftests/bpf: Add test for showing a void BTF type

 kernel/bpf/btf.c                              | 10 ++-
 kernel/bpf/hashtab.c                          |  8 ++
 .../bpf/prog_tests/btf_map_keyless.c          | 83 +++++++++++++++++++
 .../selftests/bpf/prog_tests/btf_show_void.c  | 57 +++++++++++++
 .../selftests/bpf/progs/btf_show_void.c       | 22 +++++
 5 files changed, 179 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c
 create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_show_void.c
 create mode 100644 tools/testing/selftests/bpf/progs/btf_show_void.c
Signature · 1 line
2.43.0
#1 lore raw

PATCH v2 1/4 bpf: Reject key-less BTF for hash maps

map_check_btf() allows a key-less BTF (btf_key_type_id == 0) only for maps that have a ->map_check_btf callback, and leaves the actual decision to that callback. Hash maps used to have no ->map_check_btf, so a key-less BTF was rejected outright.

Since commit 1df97a7453ee ("bpf: Register dtor for freeing special fields") htab and rhtab got a ->map_check_btf to register a dtor, but it does not look at the key, so a key-less hash map now passes map_check_btf() and gets created. Reading it back through bpffs then feeds the key type_id 0 into btf_type_seq_show(); btf_type_by_id() returns the void type, kind_ops[BTF_KIND_UNKN] is NULL, and btf_type_show() dereferences it:

KASAN: null-ptr-deref in range [0x0000000000000028-0x000000000000002f]
RIP: 0010:btf_type_show (kernel/bpf/btf.c:8251)
Call Trace:
 <TASK>
 btf_type_seq_show_flags (kernel/bpf/btf.c:8269)
 btf_type_seq_show (kernel/bpf/btf.c:8277)
 htab_map_seq_show_elem (kernel/bpf/hashtab.c:1669)
 map_seq_show (kernel/bpf/inode.c:293)
 seq_read_iter (fs/seq_file.c:273)
 seq_read (fs/seq_file.c:163)
 vfs_read (fs/read_write.c:572)
 ksys_read (fs/read_write.c:716)
 __x64_sys_read (fs/read_write.c:725)
 do_syscall_64 (arch/x86/entry/syscall_64.c:61)
 entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
 </TASK>

Reject a key-less BTF in htab_map_check_btf() and rhtab_map_check_btf(), restoring the previous behavior.

Fixes 1df97a7453ee ("bpf: Register dtor for freeing special fields")
Fixes 6905f8601298 ("bpf: Allow special fields in resizable hashtab")
Reported-by [email protected]
---
 kernel/bpf/hashtab.c | 8 ++++++++
 1 file changed, 8 insertions(+)
kernel/bpf/hashtab.c +8 -0
index d40cb5dd446c..43123424183c 100644
@@ -530,6 +530,10 @@ static int htab_map_check_btf(struct bpf_map *map, const struct btf *btf,
 {
 	struct bpf_htab *htab = container_of(map, struct bpf_htab, map);
 
+	/* Hash maps have no use for a key-less BTF. */
+	if (btf_type_is_void(key_type))
+		return -EINVAL;
+
 	if (htab_is_prealloc(htab))
 		return 0;
 	/*
@@ -3110,6 +3114,10 @@ static int rhtab_map_check_btf(struct bpf_map *map, const struct btf *btf,
 {
 	struct bpf_rhtab *rhtab = container_of(map, struct bpf_rhtab, map);
 
+	/* Hash maps have no use for a key-less BTF. */
+	if (btf_type_is_void(key_type))
+		return -EINVAL;
+
 	return bpf_ma_set_dtor(map, &rhtab->ma, rhtab_mem_dtor);
 }
 
Signature · 1 line
2.43.0
#2 lore raw

Re: [PATCH bpf v2 1/4] bpf: Reject key-less BTF for hash maps

Is this attribution accurate? Commit 1df97a7453ee did add htab_map_check_btf() to the four htab ops structs (htab_map_ops, htab_lru_map_ops, htab_percpu_map_ops, htab_lru_percpu_map_ops), but…
Quoted 9 lines

map_check_btf() allows a key-less BTF (btf_key_type_id == 0) only for maps that have a ->map_check_btf callback, and leaves the actual decision to that callback. Hash maps used to have no ->map_check_btf, so a key-less BTF was rejected outright.

Since commit 1df97a7453ee ("bpf: Register dtor for freeing special fields") htab and rhtab got a ->map_check_btf to register a dtor, but it does not look at the key, so a key-less hash map now passes map_check_btf() and gets created.

Is this attribution accurate? Commit 1df97a7453ee did add htab_map_check_btf() to the four htab ops structs (htab_map_ops, htab_lru_map_ops, htab_percpu_map_ops, htab_lru_percpu_map_ops), but BPF_MAP_TYPE_RHASH did not exist at that commit.

The rhtab regression was introduced three months later by the second

Fixes commit 6905f8601298 ("bpf: Allow special fields in resizable

hashtab"), which added rhtab_map_check_btf() and rhtab_map_ops for the first time.

Could the commit message distinguish which regression each Fixes: tag addresses? As written, the sentence attributes both regressions to 1df97a7453ee.

Quoted 8 lines
KASAN: null-ptr-deref in range [0x0000000000000028-0x000000000000002f]
RIP: 0010:btf_type_show (kernel/bpf/btf.c:8251)
Call Trace:
 <TASK>
 btf_type_seq_show_flags (kernel/bpf/btf.c:8269)
 btf_type_seq_show (kernel/bpf/btf.c:8277)
 htab_map_seq_show_elem (kernel/bpf/hashtab.c:1669)
 map_seq_show (kernel/bpf/inode.c:293)
Quoted 8 lines
 seq_read_iter (fs/seq_file.c:273)
 seq_read (fs/seq_file.c:163)
 vfs_read (fs/read_write.c:572)
 ksys_read (fs/read_write.c:716)
 __x64_sys_read (fs/read_write.c:725)
 do_syscall_64 (arch/x86/entry/syscall_64.c:61)
 entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
 </TASK>

Does this match the syzbot report? Looking at the actual report at the
Closes: link, several details differ from what was quoted:

The syscall in the report was pread64:
ksys_pread64 [inline] __x64_sys_pread64+0x1eb/0x250 fs/read_write.c:769

But the quoted trace shows read (ksys_read, __x64_sys_read).

The report shows traverse() frames that are missing here:

  traverse.part.0 fs/seq_file.c:112
  traverse fs/seq_file.c:99 [inline]
  seq_read_iter+0x93f/0x1270 fs/seq_file.c:196

And seq_read_iter's line number is 196 in the report, not 273.

There is an extra btf_type_seq_show frame inserted here that is not in the report.

The btf.c line numbers are shifted by +19: the report shows btf_type_show at 8232, btf_type_seq_show_flags at 8250, and btf_type_seq_show at 8258, but the quoted trace adds 19 to each.

Should the crash trace be quoted verbatim from the syzbot report rather than edited?

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33300136294

#3 lore raw

PATCH v2 2/4 bpf: Fix NULL-ptr-deref when showing a void BTF type

In reply to Jiayuan Chen

btf_modifier_show() resolves the modifier and then calls btf_type_ops(t)->show() unconditionally. For the void type (type_id 0, BTF_KIND_UNKN) kind_ops[] has no entry, so ->show is NULL.

The map dump path cannot reach a void type (a map key/value must have a size and void has none), but bpf_snprintf_btf() takes a type_id straight from the BPF program, and a "const void" (a modifier that resolves to void, present in the vmlinux BTF) NULL-derefs there:

KASAN: null-ptr-deref in range [0x0000000000000028-0x000000000000002f]
RIP: 0010:btf_modifier_show (kernel/bpf/btf.c:2914)
Call Trace:
 <TASK>
 btf_type_show (kernel/bpf/btf.c:8251)
 btf_type_snprintf_show (kernel/bpf/btf.c:8321)
 bpf_snprintf_btf (kernel/trace/bpf_trace.c:1047)
 bpf_prog_test_run_raw_tp (net/bpf/test_run.c:829)
 __sys_bpf (kernel/bpf/syscall.c:4804)
 do_syscall_64 (arch/x86/entry/syscall_64.c:94)
 entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
 </TASK>

void has no size, so there is nothing to render - not even a byte length to fall back to dumping as raw hex. btf_df_show() is already the ->show for the other kinds that carry no value to print - FWD, FUNC, FUNC_PROTO, FLOAT and DECL_TAG - and emits an "<unsupported kind:N>" placeholder; void belongs to the same group and only lacks a show op because it has no kind_ops[] entry at all. Fall back to btf_df_show() in btf_modifier_show() when the resolved type has no show op, so a void type prints that placeholder instead of crashing; bpf_snprintf_btf() then returns the length as usual.

Fixes c4d0bfb45068 ("bpf: Add bpf_snprintf_btf helper")
---
 kernel/bpf/btf.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)
kernel/bpf/btf.c +9 -1
index 91b8ce77f699..6e267c4c4e1f 100644
@@ -2911,7 +2911,15 @@ static void btf_modifier_show(const struct btf *btf,
 	else
 		t = btf_type_skip_modifiers(btf, type_id, NULL);
 
-	btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show);
+	/*
+	 * A modifier can resolve to the void type (e.g. "const void"), which
+	 * has no show op (kind_ops[BTF_KIND_UNKN] is NULL). Print a placeholder
+	 * instead of dereferencing NULL.
+	 */
+	if (!btf_type_ops(t))
+		btf_df_show(btf, t, type_id, data, bits_offset, show);
+	else
+		btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show);
 }
 
 static void btf_var_show(const struct btf *btf, const struct btf_type *t,
Signature · 1 line
2.43.0
#4 lore raw

Re: [PATCH bpf v2 2/4] bpf: Fix NULL-ptr-deref when showing a void BTF type

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] NULL pointer dereference in btf_var_show() when rendering vmlinux BTF types via bpf_snprintf_btf(). --

commit f9ef7fc1b664c580ca28b8d18cae855dd7b0e940
Author: Jiayuan Chen <[email protected]>

bpf: Fix NULL-ptr-deref when showing a void BTF type

This patch fixes a NULL pointer dereference when btf_modifier_show() attempts to render a void type. Because void lacks a show op, it falls back to btf_df_show() instead of dereferencing a NULL pointer.

Quoted 21 lines
kernel/bpf/btf.c +9 -1
index da36d4b9d31ab..81506cbb9f9ae 100644
@@ -2911,7 +2911,15 @@ static void btf_modifier_show(const struct btf *btf,
 	else
 		t = btf_type_skip_modifiers(btf, type_id, NULL);
 
-	btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show);
+	/*
+	 * A modifier can resolve to the void type (e.g. "const void"), which
+	 * has no show op (kind_ops[BTF_KIND_UNKN] is NULL). Print a placeholder
+	 * instead of dereferencing NULL.
+	 */
+	if (!btf_type_ops(t))
+		btf_df_show(btf, t, type_id, data, bits_offset, show);
+	else
+		btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show);
 }
 
 static void btf_var_show(const struct btf *btf, const struct btf_type *t,

kernel/bpf/btf.c

Quoted 4 lines
kernel/bpf/btf.c +0 -0
 			 u32 type_id, void *data, u8 bits_offset,
 			 struct btf_show *show)
 {
 	t = btf_type_id_resolve(btf, &type_id);

[Severity: High]
This isn't a bug introduced by this patch, but while looking at this fix, I noticed a potential issue in btf_var_show() just below it.

Can btf_var_show() cause a NULL pointer dereference when rendering vmlinux BTF types via bpf_snprintf_btf()?

If a BPF program calls the bpf_snprintf_btf() helper and passes the type ID of a BTF_KIND_VAR from the vmlinux BTF, formatting will dispatch to btf_var_show().

This function unconditionally calls btf_type_id_resolve(), which attempts to dereference btf->resolved_ids.

However, since type resolution is skipped for base BTF during parsing, btf->resolved_ids is NULL for vmlinux BTF. Does this unconditionally result in a NULL pointer dereference inside btf_type_id_resolve()?

Quoted 3 lines

btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show);
}

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
#5 lore raw

PATCH v2 3/4 selftests/bpf: Add test for key-less BTF hash map

In reply to Jiayuan Chen

Create a hash and an rhash map with btf_key_type_id == 0 and expect bpf_map_create() to fail with -EINVAL; a positive control with a real key type confirms the rejection is about the key-less BTF. On an unfixed kernel the map is created, and the test pins and reads it back to walk the bpffs dump path, which reproduces the btf_type_show() NULL-deref - so running it on an unfixed kernel oopses the reading task (and panics it under panic_on_oops).

---
 .../bpf/prog_tests/btf_map_keyless.c          | 83 +++++++++++++++++++
 1 file changed, 83 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c
tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c new file +83 -0
new file mode 100644
index 000000000000..43f0bb9bd578
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <test_progs.h>
+#include <bpf/btf.h>
+
+/*
+ * A hash map with a key-less BTF (btf_key_type_id == 0) used to NULL-deref in
+ * btf_type_show() when dumped via bpffs.
+ * A fixed kernel rejects such a map at creation; on an unfixed kernel the
+ * pin-and-read below deliberately walks that bpffs dump path, so it doubles
+ * as a reproducer: it oopses an unfixed kernel (and panics it under
+ * panic_on_oops).
+ */
+static void check_keyless(int map_type, __u32 map_flags, int btf_fd, int val_id)
+{
+	LIBBPF_OPTS(bpf_map_create_opts, opts);
+	const char *path = "/sys/fs/bpf/keyless_map";
+	__u32 key = 1, val = 0x41424344;
+	char buf[256];
+	int map_fd;
+	FILE *f;
+
+	opts.map_flags = map_flags;
+	opts.btf_fd = btf_fd;
+	opts.btf_value_type_id = val_id;
+
+	/*
+	 * Positive control: the same map with a real key type must be accepted,
+	 * so the -EINVAL below is about the key-less BTF and not some unrelated
+	 * rejection (e.g. an unknown map type).
+	 */
+	opts.btf_key_type_id = val_id;
+	map_fd = bpf_map_create(map_type, "keyed_map", 4, 4, 8, &opts);
+	if (!ASSERT_GE(map_fd, 0, "keyed create is accepted"))
+		return;
+	close(map_fd);
+
+	/* A key-less BTF must be rejected. */
+	opts.btf_key_type_id = 0;
+	map_fd = bpf_map_create(map_type, "keyless_map", 4, 4, 8, &opts);
+
+	if (map_fd >= 0) {
+		/* Unfixed kernel: reproduce the oops via the bpffs dump path. */
+		(void)bpf_map_update_elem(map_fd, &key, &val, 0);
+		if (bpf_obj_pin(map_fd, path) == 0) {
+			f = fopen(path, "r");
+			if (f) {
+				while (fgets(buf, sizeof(buf), f))
+					;
+				fclose(f);
+			}
+			unlink(path);
+		}
+		close(map_fd);
+	}
+
+	ASSERT_EQ(map_fd, -EINVAL, "key-less create is rejected");
+}
+
+void test_btf_map_keyless(void)
+{
+	int btf_fd, val_id;
+	struct btf *btf;
+
+	btf = btf__new_empty();
+	if (!ASSERT_OK_PTR(btf, "btf__new_empty"))
+		return;
+
+	val_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+	if (!ASSERT_GT(val_id, 0, "btf__add_int"))
+		goto out;
+
+	if (!ASSERT_OK(btf__load_into_kernel(btf), "btf__load_into_kernel"))
+		goto out;
+	btf_fd = btf__fd(btf);
+
+	if (test__start_subtest("hash"))
+		check_keyless(BPF_MAP_TYPE_HASH, 0, btf_fd, val_id);
+	if (test__start_subtest("rhash"))
+		check_keyless(BPF_MAP_TYPE_RHASH, BPF_F_NO_PREALLOC, btf_fd, val_id);
+out:
+	btf__free(btf);
+}
Signature · 1 line
2.43.0
#6 lore raw

Re: [PATCH bpf v2 3/4] selftests/bpf: Add test for key-less BTF hash map

This isn't a bug, but is the #define _GNU_SOURCE needed here? Everything the file uses looks like plain POSIX (fopen/fgets/fclose from stdio.h, unlink/close from unistd.h, plus libbpf/BTF calls)…

tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c line 2

Quoted 8 lines
tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c new file +2 -0
new file mode 100644
index 0000000000000..43f0bb9bd5786
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE

This isn't a bug, but is the #define _GNU_SOURCE needed here? Everything the file uses looks like plain POSIX (fopen/fgets/fclose from stdio.h, unlink/close from unistd.h, plus libbpf/BTF calls), and lib.mk already passes -D_GNU_SOURCE= to selftest builds.

tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c line 4

Quoted 2 lines
tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c new file +2 -0
+#include <test_progs.h>
+#include <bpf/btf.h>

[ ... ]

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33300136294

#7 lore raw

PATCH v2 4/4 selftests/bpf: Add test for showing a void BTF type

In reply to Jiayuan Chen

Call bpf_snprintf_btf() with the type_id of a "const void" from the vmlinux BTF and check the rendered output is the "<unsupported kind:0>" placeholder. On an unfixed kernel this used to NULL-deref in btf_modifier_show(), so the test doubles as a reproducer: it oopses the task (and panics it under panic_on_oops).

---
 .../selftests/bpf/prog_tests/btf_show_void.c  | 57 +++++++++++++++++++
 .../selftests/bpf/progs/btf_show_void.c       | 22 +++++++
 2 files changed, 79 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_show_void.c
 create mode 100644 tools/testing/selftests/bpf/progs/btf_show_void.c
tools/testing/selftests/bpf/prog_tests/btf_show_void.c new file +57 -0
new file mode 100644
index 000000000000..546833199195
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <bpf/btf.h>
+#include "btf_show_void.skel.h"
+
+/*
+ * bpf_snprintf_btf() with the type_id of a "const void" (a modifier that
+ * resolves to void, present in the vmlinux BTF) used to NULL-deref in
+ * btf_modifier_show(). A fixed kernel prints the "<unsupported kind:0>"
+ * placeholder; on an unfixed kernel this oopses the task (and panics it under
+ * panic_on_oops), so it doubles as a reproducer.
+ */
+void test_btf_show_void(void)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	const struct btf_type *t;
+	struct btf_show_void *skel;
+	int i, n, cv = 0, err;
+	char ctx[16] = {};
+	struct btf *btf;
+
+	btf = btf__parse("/sys/kernel/btf/vmlinux", NULL);
+	if (!ASSERT_OK_PTR(btf, "btf__parse vmlinux"))
+		return;
+
+	n = btf__type_cnt(btf);
+	for (i = 1; i < n; i++) {
+		t = btf__type_by_id(btf, i);
+		if (btf_kind(t) == BTF_KIND_CONST && t->type == 0) {
+			cv = i;
+			break;
+		}
+	}
+	if (!ASSERT_GT(cv, 0, "find const void in vmlinux BTF"))
+		goto out_btf;
+
+	skel = btf_show_void__open();
+	if (!ASSERT_OK_PTR(skel, "skel_open"))
+		goto out_btf;
+	skel->rodata->const_void_id = cv;
+	if (!ASSERT_OK(btf_show_void__load(skel), "skel_load"))
+		goto out_skel;
+
+	topts.ctx_in = ctx;
+	topts.ctx_size_in = sizeof(ctx);
+	err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.dump_const_void),
+				     &topts);
+	if (!ASSERT_OK(err, "test_run"))
+		goto out_skel;
+
+	ASSERT_EQ(skel->bss->ret, sizeof("<unsupported kind:0>") - 1, "ret");
+	ASSERT_STREQ(skel->bss->out, "<unsupported kind:0>", "placeholder");
+out_skel:
+	btf_show_void__destroy(skel);
+out_btf:
+	btf__free(btf);
+}
tools/testing/selftests/bpf/progs/btf_show_void.c new file +22 -0
new file mode 100644
index 000000000000..9fabd7372f89
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "btf_ptr.h"
+#include <bpf/bpf_helpers.h>
+
+const volatile __u32 const_void_id;
+char out[64];
+long ret;
+
+SEC("raw_tp/sys_enter")
+int dump_const_void(void *ctx)
+{
+	struct btf_ptr ptr = {
+		.ptr = ctx,
+		.type_id = const_void_id,
+		.flags = 0,
+	};
+
+	ret = bpf_snprintf_btf(out, sizeof(out), &ptr, sizeof(ptr), 0);
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
Signature · 1 line
2.43.0
#8 lore raw

Re: [PATCH bpf v2 4/4] selftests/bpf: Add test for showing a void BTF type

A subsystem pattern flags this as potentially concerning: this adds a new standalone test file pair for one narrow case (a modifier resolving to void in bpf_snprintf_btf(), exercising the…
Quoted 9 lines
tools/testing/selftests/bpf/prog_tests/btf_show_void.c new file +3 -0
new file mode 100644
index 0000000000000..5468331991958
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <bpf/btf.h>

tools/testing/selftests/bpf/prog_tests/btf_show_void.c line 13

Quoted 10 lines
tools/testing/selftests/bpf/prog_tests/btf_show_void.c new file +10 -0
+#include "btf_show_void.skel.h"
+
+/*
+ * bpf_snprintf_btf() with the type_id of a "const void" (a modifier that
+ * resolves to void, present in the vmlinux BTF) used to NULL-deref in
+ * btf_modifier_show(). A fixed kernel prints the "<unsupported kind:0>"
+ * placeholder; on an unfixed kernel this oopses the task (and panics it under
+ * panic_on_oops), so it doubles as a reproducer.
+ */
+void test_btf_show_void(void)

A subsystem pattern flags this as potentially concerning: this adds a new standalone test file pair for one narrow case (a modifier resolving to void in bpf_snprintf_btf(), exercising the btf_modifier_show() -> btf_df_show() fallback). The directory already has prog_tests/snprintf_btf.c (serial_test_snprintf_btf, driving progs/netif_receive_skb.c which includes progs/btf_ptr.h) that exercises bpf_snprintf_btf() output formatting for a range of types.

Would this case fit better as an additional case in that existing test rather than as a new binary/skeleton pair?

The counter-argument: snprintf_btf.c is structured very differently - it attaches a netif_receive_skb raw tracepoint and triggers it with system("ping -c 1 127.0.0.1"), and still uses the deprecated CHECK() macros. The new test instead needs a type_id discovered from the vmlinux BTF at runtime injected through rodata, and drives the program with bpf_prog_test_run_opts() rather than a real event. Folding it into the existing test would mean adding a second program and a second run mechanism to that file. That is a plausible engineering reason for a separate file, and the new file is the more modern idiom.

tools/testing/selftests/bpf/prog_tests/btf_show_void.c line 24

Quoted 11 lines
tools/testing/selftests/bpf/prog_tests/btf_show_void.c new file +11 -0
+{
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	const struct btf_type *t;
+	struct btf_show_void *skel;
+	int i, n, cv = 0, err;
+	char ctx[16] = {};
+	struct btf *btf;
+
+	btf = btf__parse("/sys/kernel/btf/vmlinux", NULL);
+	if (!ASSERT_OK_PTR(btf, "btf__parse vmlinux"))
+		return;

Both environment prerequisites of this test are reported as hard failures rather than skips.

On a kernel built without CONFIG_DEBUG_INFO_BTF the /sys/kernel/btf/vmlinux file does not exist, btf__parse() fails with ENOENT, and the test reports a failure instead of a skip. The path is also hardcoded; btf__load_vmlinux_btf() (used by 14 other prog_tests, e.g. fill_link_info.c, tracing_multi.c, btf_module.c) additionally searches /boot/vmlinux-$(uname -r) and the module build dirs.

tools/testing/selftests/bpf/prog_tests/btf_show_void.c line 35

Quoted 11 lines
tools/testing/selftests/bpf/prog_tests/btf_show_void.c new file +11 -0
+
+	n = btf__type_cnt(btf);
+	for (i = 1; i < n; i++) {
+		t = btf__type_by_id(btf, i);
+		if (btf_kind(t) == BTF_KIND_CONST && t->type == 0) {
+			cv = i;
+			break;
+		}
+	}
+	if (!ASSERT_GT(cv, 0, "find const void in vmlinux BTF"))
+		goto out_btf;

The test scans the vmlinux BTF for the first BTF_KIND_CONST whose ->type == 0 and hard-fails via ASSERT_GT when none exists. Whether a standalone const void node survives in a given kernel's BTF is a property of that build (pahole version, dedup, whether anything in that kernel actually uses const void *), not a property of the kernel logic under test. If it is ever absent the test reports a kernel regression that is not there, with no indication that the input the test needed simply was not present.

Would a test__skip() with a reason string on both arms (the pattern used in prog_tests/snprintf_btf.c) keep an environment difference from being read as a failure?

Mitigating context: all BPF selftests already require CONFIG_DEBUG_INFO_BTF=y, and the exact idiom btf__parse("/sys/kernel/btf/vmlinux", NULL) followed by ASSERT_OK_PTR is used unchanged in prog_tests/libbpf_str.c (4 sites), prog_tests/libbpf_probes.c (2 sites) and prog_tests/btf_sysfs.c, so the first arm matches existing neighbours. const void is also very likely present in any vmlinux BTF, since const void * appears in many exported prototypes. The concern is robustness/diagnosability of the failure mode, not a functional defect.

Quoted 12 lines
tools/testing/selftests/bpf/prog_tests/btf_show_void.c new file +12 -0
+
+	skel = btf_show_void__open();
+	if (!ASSERT_OK_PTR(skel, "skel_open"))
+		goto out_btf;
+	skel->rodata->const_void_id = cv;
+	if (!ASSERT_OK(btf_show_void__load(skel), "skel_load"))
+		goto out_skel;
+
+	topts.ctx_in = ctx;
+	topts.ctx_size_in = sizeof(ctx);
+	err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.dump_const_void),
+				     &topts);

tools/testing/selftests/bpf/prog_tests/btf_show_void.c line 57

Quoted 10 lines
tools/testing/selftests/bpf/prog_tests/btf_show_void.c new file +10 -0
+	if (!ASSERT_OK(err, "test_run"))
+		goto out_skel;
+
+	ASSERT_EQ(skel->bss->ret, sizeof("<unsupported kind:0>") - 1, "ret");
+	ASSERT_STREQ(skel->bss->out, "<unsupported kind:0>", "placeholder");
+out_skel:
+	btf_show_void__destroy(skel);
+out_btf:
+	btf__free(btf);
+}

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33300136294