Skip to contentSkip to conversation

bpftool: Reset map info between queries

PATCH In review Tianyi Chen 2 participants 6 messages Sep 28 – Oct 16

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

cover bpftool: Reset map info between queries

Reset map info before each query in the existing show and dump loops to prevent BTF metadata from a preceding map leaking into a map without BTF.

Patch 1 fixes the four query loops. Patch 2 adds regression tests for full and named listings, JSON dumps, and plain dumps.

All four regression subtests fail on the unmodified bpf-next base and pass with the fix in an x86-64 VM. This series does not depend on recursive dumping or Maxim's series.

Tianyi Chen (2):
bpftool: Reset map info before each query in show and dump loops selftests/bpf: Check bpftool map info across mixed BTF maps

 tools/bpf/bpftool/map.c                       |   8 ++
 .../bpf/prog_tests/bpftool_map_info.c         | 126 ++++++++++++++++++
 2 files changed, 134 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_map_info.c

base-commit: ea9358e1270ab2c3ba6f36bd9bdda68617665516

Signature · 1 line
2.55.0
#1 lore raw

PATCH 1/2 bpftool: Reset map info before each query in show and dump loops

The kernel copies bpf_map_info from userspace and only fills in the BTF fields when a map has BTF. Reusing the structure for a later map without BTF therefore retains the preceding map's BTF IDs.

Clear the structure and restore its length before each query in map listing, named-map listing, BTF detection and dumping. This prevents reporting stale BTF IDs, choosing BTF output for mixed map selections, and formatting a map with another map's key and value types.

---
 tools/bpf/bpftool/map.c | 8 ++++++++
 1 file changed, 8 insertions(+)
tools/bpf/bpftool/map.c +8 -0
index 20d59eab09a1..f84311fa640d 100644
@@ -647,6 +647,8 @@ static int do_show_subset(int argc, char **argv)
 	if (json_output && nb_fds > 1)
 		jsonw_start_array(json_wtr);	/* root array */
 	for (i = 0; i < nb_fds; i++) {
+		memset(&info, 0, sizeof(info));
+		len = sizeof(info);
 		err = bpf_map_get_info_by_fd(fds[i], &info, &len);
 		if (err) {
 			p_err("can't get map info: %s",
@@ -718,6 +720,8 @@ static int do_show(int argc, char **argv)
 			break;
 		}
 
+		memset(&info, 0, sizeof(info));
+		len = sizeof(info);
 		err = bpf_map_get_info_by_fd(fd, &info, &len);
 		if (err) {
 			p_err("can't get map info: %s", strerror(errno));
@@ -774,6 +778,8 @@ static int maps_have_btf(int *fds, int nb_fds)
 	int err, i;
 
 	for (i = 0; i < nb_fds; i++) {
+		memset(&info, 0, sizeof(info));
+		len = sizeof(info);
 		err = bpf_map_get_info_by_fd(fds[i], &info, &len);
 		if (err) {
 			p_err("can't get map info: %s", strerror(errno));
@@ -952,6 +958,8 @@ static int do_dump(int argc, char **argv)
 	if (wtr && nb_fds > 1)
 		jsonw_start_array(wtr);	/* root array */
 	for (i = 0; i < nb_fds; i++) {
+		memset(&info, 0, sizeof(info));
+		len = sizeof(info);
 		if (bpf_map_get_info_by_fd(fds[i], &info, &len)) {
 			p_err("can't get map info: %s", strerror(errno));
 			err = -1;
Signature · 1 line
2.55.0
maintainer Acked-by on v1 1/2
#2 lore raw

Re: [PATCH bpf-next 1/2] bpftool: Reset map info before each query in show and dump loops

2026-09-29 02:13 UTC+0900 ~ Tianyi Chen <[email protected]>

Quoted 9 lines

The kernel copies bpf_map_info from userspace and only fills in the BTF fields when a map has BTF. Reusing the structure for a later map without BTF therefore retains the preceding map's BTF IDs.

Clear the structure and restore its length before each query in map listing, named-map listing, BTF detection and dumping. This prevents reporting stale BTF IDs, choosing BTF output for mixed map selections, and formatting a map with another map's key and value types.

Quoted 4 lines
Fixes 71bb428fe2c1 ("tools: bpf: add bpftool")
Fixes 99f9863a0c45 ("bpftool: Match maps by name")

Thanks for this.

maintainer
#3 lore raw

Re: [PATCH bpf-next 1/2] bpftool: Reset map info before each query in show and dump loops

2026-09-29 11:28 UTC+0100 ~ Quentin Monnet <[email protected]>

Quoted 16 lines

2026-09-29 02:13 UTC+0900 ~ Tianyi Chen <[email protected]>

Quoted 9 lines

The kernel copies bpf_map_info from userspace and only fills in the BTF fields when a map has BTF. Reusing the structure for a later map without BTF therefore retains the preceding map's BTF IDs.

Clear the structure and restore its length before each query in map listing, named-map listing, BTF detection and dumping. This prevents reporting stale BTF IDs, choosing BTF output for mixed map selections, and formatting a map with another map's key and value types.

Quoted 4 lines
Quoted 6 lines
Fixes 71bb428fe2c1 ("tools: bpf: add bpftool")
Fixes 99f9863a0c45 ("bpftool: Match maps by name")

Thanks for this.

Hi Tianyi, sorry for the delay, it seems your series somehow fell through the cracks. Daniel sent a fix on kernel side for this [0], but I think it's still worth getting the fix in bpftool as well, to avoid hitting the bug if we run it on older kernels that won't have the fix.

Would you please send a rebased version of your series (and drop the mention of recursing dumps in patch 2's description, keep the Fixes: and
Acked-by: tags)? Similarly, can you repost a rebased version of your patches for the batch lookups for hashmap dumps in bpftool, please?

Thanks,
Quentin

[0]
https://lore.kernel.org/all/[email protected]/

#4 lore raw

PATCH 2/2 selftests/bpf: Check bpftool map info across mixed BTF maps

In reply to Tianyi Chen

Create a typed map followed by a smaller untyped map with the same name. Check that full and named listings do not report BTF for the untyped map, JSON dumps match independently dumped contents, and plain dumps use raw output when the selected maps do not all have BTF.

These cover the separate info-query loops in map show and map dump without requiring recursive dumping.

Assisted-by LLM
---
 .../bpf/prog_tests/bpftool_map_info.c         | 126 ++++++++++++++++++
 1 file changed, 126 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_map_info.c
tools/testing/selftests/bpf/prog_tests/bpftool_map_info.c new file +126 -0
new file mode 100644
index 000000000000..5bcfef952619
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <test_progs.h>
+#include <bpftool_helpers.h>
+#include <bpf/btf.h>
+
+#define OUTPUT_SIZE (1024 * 1024)
+
+static void check_show(char *output, struct bpf_map_info *info)
+{
+	char id[32];
+	char *map, *end, *btf;
+
+	snprintf(id, sizeof(id), "{\"id\":%u,", info->id);
+	map = strstr(output, id);
+	if (!ASSERT_OK_PTR(map, "map_in_output"))
+		return;
+	end = strchr(map, '}');
+	if (!ASSERT_OK_PTR(end, "map_object_end"))
+		return;
+	btf = strstr(map, "\"btf_id\":");
+	if (info->btf_id) {
+		ASSERT_TRUE(btf && btf < end, "typed_map_btf");
+		if (btf && btf < end)
+			ASSERT_EQ(strtoul(btf + strlen("\"btf_id\":"), NULL, 10),
+				  info->btf_id, "btf_id");
+	} else {
+		ASSERT_TRUE(!btf || btf > end, "raw_map_no_btf");
+	}
+}
+
+static void test_map_info(const char *operation)
+{
+	LIBBPF_OPTS(bpf_map_create_opts, opts);
+	struct bpf_map_info info[2] = {};
+	char command[MAX_BPFTOOL_CMD_LEN], name[BPF_OBJ_NAME_LEN];
+	char elements[2][1024] = {}, expected[4096];
+	struct btf *btf = NULL;
+	int fds[] = { -1, -1 };
+	__u64 wide_key = 0, wide_value = 1;
+	__u32 key = 0, value = 2;
+	char *output;
+	int i;
+
+	output = malloc(OUTPUT_SIZE);
+	if (!ASSERT_OK_PTR(output, "output"))
+		return;
+	snprintf(name, sizeof(name), "info_%u", getpid());
+	btf = btf__new_empty();
+	if (!ASSERT_OK_PTR(btf, "create_btf") ||
+	    !ASSERT_EQ(btf__add_int(btf, "unsigned long long", 8, 0), 1, "btf_int") ||
+	    !ASSERT_OK(btf__load_into_kernel(btf), "load_btf"))
+		goto out;
+	opts.btf_fd = btf__fd(btf);
+	opts.btf_key_type_id = 1;
+	opts.btf_value_type_id = 1;
+	/* Query the typed map before a smaller map without BTF. */
+	fds[0] = bpf_map_create(BPF_MAP_TYPE_HASH, name, 8, 8, 1, &opts);
+	fds[1] = bpf_map_create(BPF_MAP_TYPE_HASH, name, 4, 4, 1, NULL);
+	if (!ASSERT_OK_FD(fds[0], "create_typed") ||
+	    !ASSERT_OK_FD(fds[1], "create_raw") ||
+	    !ASSERT_OK(bpf_map_update_elem(fds[0], &wide_key, &wide_value, BPF_ANY),
+		       "populate_typed") ||
+	    !ASSERT_OK(bpf_map_update_elem(fds[1], &key, &value, BPF_ANY), "populate_raw"))
+		goto out;
+	for (i = 0; i < ARRAY_SIZE(fds); i++) {
+		__u32 len = sizeof(info[i]);
+
+		if (!ASSERT_OK(bpf_map_get_info_by_fd(fds[i], &info[i], &len), "map_info"))
+			goto out;
+	}
+	if (!ASSERT_LT(info[0].id, info[1].id, "map_order"))
+		goto out;
+
+	if (!strcmp(operation, "show_all"))
+		snprintf(command, sizeof(command), "-j map show");
+	else if (!strcmp(operation, "show_name"))
+		snprintf(command, sizeof(command), "-j map show name %s", name);
+	else
+		snprintf(command, sizeof(command), "%s map dump name %s",
+			 !strcmp(operation, "dump_json") ? "-j" : "", name);
+	output[0] = '\0';
+	if (!ASSERT_OK(get_bpftool_command_output(command, output, OUTPUT_SIZE), operation))
+		goto out;
+	if (!strncmp(operation, "show_", 5)) {
+		check_show(output, &info[0]);
+		check_show(output, &info[1]);
+	} else if (!strcmp(operation, "dump_json")) {
+		for (i = 0; i < ARRAY_SIZE(fds); i++) {
+			snprintf(command, sizeof(command), "-j map dump id %u", info[i].id);
+			if (!ASSERT_OK(get_bpftool_command_output(command, elements[i],
+							 sizeof(elements[i])), "single_dump"))
+				goto out;
+			elements[i][strcspn(elements[i], "\n")] = '\0';
+		}
+		snprintf(expected, sizeof(expected),
+			 "[{\"id\":%u,\"type\":\"hash\",\"name\":\"%s\",\"flags\":0,"
+			 "\"elements\":%s},{\"id\":%u,\"type\":\"hash\",\"name\":\"%s\","
+			 "\"flags\":0,\"elements\":%s}]\n",
+			 info[0].id, name, elements[0], info[1].id, name, elements[1]);
+		ASSERT_STREQ(output, expected, "independent_map_formatting");
+	} else {
+		char *count = strstr(output, "Found 1 element\n");
+
+		ASSERT_NULL(strstr(output, "\"value\":"), "mixed_maps_use_raw_output");
+		if (ASSERT_OK_PTR(count, "first_map_count"))
+			ASSERT_HAS_SUBSTR(count + 1, "Found 1 element\n", "second_map_count");
+	}
+out:
+	for (i = 0; i < ARRAY_SIZE(fds); i++)
+		if (fds[i] >= 0)
+			close(fds[i]);
+	btf__free(btf);
+	free(output);
+}
+
+void serial_test_bpftool_map_info(void)
+{
+	if (test__start_subtest("show_all"))
+		test_map_info("show_all");
+	if (test__start_subtest("show_name"))
+		test_map_info("show_name");
+	if (test__start_subtest("dump_json"))
+		test_map_info("dump_json");
+	if (test__start_subtest("dump_plain"))
+		test_map_info("dump_plain");
+}
Signature · 1 line
2.55.0
maintainer Acked-by on v1 2/2
#5 lore raw

Re: [PATCH bpf-next 2/2] selftests/bpf: Check bpftool map info across mixed BTF maps

2026-09-29 02:13 UTC+0900 ~ Tianyi Chen <[email protected]>

Quoted 7 lines

Create a typed map followed by a smaller untyped map with the same name. Check that full and named listings do not report BTF for the untyped map, JSON dumps match independently dumped contents, and plain dumps use raw output when the selected maps do not all have BTF.

These cover the separate info-query loops in map show and map dump without requiring recursive dumping.

Not sure why we'd mention recursive dumping at all in the commit logs for this patch (can we please drop this sentence when applying?).

Looks good otherwise, thanks.

Quoted 3 lines
Assisted-by LLM