* [PATCH bpf-next v2 0/2] bpftool: Fail map dump for maps that can't be iterated @ 2026-09-25 16:20 Maxim Skokov 2026-09-25 16:20 ` [PATCH bpf-next v2 1/2] " Maxim Skokov 2026-09-25 16:20 ` [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage Maxim Skokov 0 siblings, 2 replies; 7+ messages in thread From: Maxim Skokov @ 2026-09-25 16:20 UTC (permalink / raw) To: bpf Cc: qmo, ast, daniel, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai, kuba, shuah, linux-kselftest, Maxim Skokov "bpftool map dump" treats only ENOENT from bpf_map_get_next_key() as the end of the map. Map types that can't be iterated (local storage, ringbuf, bloom filter, arena, queue, stack) fail on the first key, and bpftool prints an empty dump, "[]" or "Found 0 elements", with only a non-zero exit status. Patch 1 asks for the first key before printing anything and fails with the map type and the error. Patch 2 adds a test_progs case on a socket storage map. The new test fails without patch 1 and passes with it; the other bpftool_* selftests pass. Tested on x86_64 under a bpf-next kernel with KASAN and lockdep, by hand on queue, stack, bloom filter, ringbuf, arena, sk_storage and empty and filled hash maps, and through BPF CI (x86_64, aarch64, s390x) with only comment style differing from this posting. Changes in v2: - probe the first key before any output instead of printing a hint for ENOTSUPP only, so every non-iterable type gets an error and stdout gets no "[]" (Alexei) - drop the Fixes tag and use a neutral example map (Alexei) - selftests: check that JSON output is only the error object and plain stdout has no elements or count; clear the output buffer before each run (Sashiko) - multi-line comment style (bpf-ci) v1: https://lore.kernel.org/bpf/20260924160732.485650-1-skokovmaksimevg@gmail.com/ Maxim Skokov (2): bpftool: Fail map dump for maps that can't be iterated selftests/bpf: Check that bpftool map dump fails for socket storage tools/bpf/bpftool/map.c | 26 +++++- .../bpf/prog_tests/bpftool_map_dump.c | 88 +++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c base-commit: 4f3a5eae895b9995e93425a75235d8f1f3268caa -- 2.47.3 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH bpf-next v2 1/2] bpftool: Fail map dump for maps that can't be iterated 2026-09-25 16:20 [PATCH bpf-next v2 0/2] bpftool: Fail map dump for maps that can't be iterated Maxim Skokov @ 2026-09-25 16:20 ` Maxim Skokov 2026-09-25 19:24 ` Quentin Monnet 2026-09-25 16:20 ` [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage Maxim Skokov 1 sibling, 1 reply; 7+ messages in thread From: Maxim Skokov @ 2026-09-25 16:20 UTC (permalink / raw) To: bpf Cc: qmo, ast, daniel, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai, kuba, shuah, linux-kselftest, Maxim Skokov "bpftool map dump" walks the map with bpf_map_get_next_key() and treats ENOENT as the end of the map. Map types that can't be iterated fail that call on the very first key: local storage and ringbuf with ENOTSUPP, bloom filter and arena with EOPNOTSUPP, queue and stack with EINVAL. bpftool then ends the walk without a word and prints what it prints for an empty map, "[]" with BTF or "Found 0 elements" without, and only the exit status tells that the map was not read. On a socket storage map that holds storage for a socket: # bpftool map dump pinned /sys/fs/bpf/sk_storage [] Ask for the first key before printing anything, and when that fails with anything but ENOENT, fail with the map type and the error instead of a dump: # bpftool map dump pinned /sys/fs/bpf/sk_storage Error: can't dump sk_storage map: Unknown error 524 # bpftool -j map dump pinned /sys/fs/bpf/sk_storage {"error":"can't dump sk_storage map: Unknown error 524"} An error later in the walk is now reported too, as "bpftool map getnext" reports it, and the element count is printed only after a complete walk. Assisted-by: LLM Signed-off-by: Maxim Skokov <skokovmaksimevg@gmail.com> --- tools/bpf/bpftool/map.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c index 20d59eab09..626882f207 100644 --- a/tools/bpf/bpftool/map.c +++ b/tools/bpf/bpftool/map.c @@ -856,6 +856,25 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr, } } + /* + * Map types that can't be iterated fail on the very first key. Say so + * before printing anything, so that the output isn't an empty dump. + */ + if (bpf_map_get_next_key(fd, NULL, key) && errno != ENOENT) { + const char *map_type_str; + int saved_errno = errno; + + map_type_str = libbpf_bpf_map_type_str(info->type); + if (map_type_str) + p_err("can't dump %s map: %s", map_type_str, + strerror(saved_errno)); + else + p_err("can't dump map of type %u: %s", info->type, + strerror(saved_errno)); + err = -1; + goto exit_free; + } + if (wtr) { err = get_map_kv_btf(info, &btf); if (err) { @@ -883,8 +902,11 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr, while (true) { err = bpf_map_get_next_key(fd, prev_key, key); if (err) { - if (errno == ENOENT) + if (errno == ENOENT) { err = 0; + break; + } + p_err("can't get next key: %s", strerror(errno)); break; } if (!dump_map_elem(fd, key, value, info, btf, wtr, @@ -897,7 +919,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr, jsonw_end_array(wtr); /* elements */ if (show_header) jsonw_end_object(wtr); /* map object */ - } else { + } else if (!err) { printf("Found %u element%s\n", num_elems, num_elems != 1 ? "s" : ""); } -- 2.47.3 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next v2 1/2] bpftool: Fail map dump for maps that can't be iterated 2026-09-25 16:20 ` [PATCH bpf-next v2 1/2] " Maxim Skokov @ 2026-09-25 19:24 ` Quentin Monnet 2026-09-26 1:44 ` Tianyi Chen 0 siblings, 1 reply; 7+ messages in thread From: Quentin Monnet @ 2026-09-25 19:24 UTC (permalink / raw) To: Maxim Skokov, bpf, Tianyi Chen Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai, kuba, shuah, linux-kselftest 2026-09-25 19:20 UTC+0300 ~ Maxim Skokov <skokovmaksimevg@gmail.com> > "bpftool map dump" walks the map with bpf_map_get_next_key() and treats > ENOENT as the end of the map. Map types that can't be iterated fail that > call on the very first key: local storage and ringbuf with ENOTSUPP, > bloom filter and arena with EOPNOTSUPP, queue and stack with EINVAL. > bpftool then ends the walk without a word and prints what it prints for > an empty map, "[]" with BTF or "Found 0 elements" without, and only the > exit status tells that the map was not read. On a socket storage map > that holds storage for a socket: > > # bpftool map dump pinned /sys/fs/bpf/sk_storage > [] > > Ask for the first key before printing anything, and when that fails with > anything but ENOENT, fail with the map type and the error instead of a > dump: > > # bpftool map dump pinned /sys/fs/bpf/sk_storage > Error: can't dump sk_storage map: Unknown error 524 > # bpftool -j map dump pinned /sys/fs/bpf/sk_storage > {"error":"can't dump sk_storage map: Unknown error 524"} > > An error later in the walk is now reported too, as "bpftool map getnext" > reports it, and the element count is printed only after a complete walk. > > Assisted-by: LLM > Signed-off-by: Maxim Skokov <skokovmaksimevg@gmail.com> > --- > tools/bpf/bpftool/map.c | 26 ++++++++++++++++++++++++-- > 1 file changed, 24 insertions(+), 2 deletions(-) > > diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c > index 20d59eab09..626882f207 100644 > --- a/tools/bpf/bpftool/map.c > +++ b/tools/bpf/bpftool/map.c > @@ -856,6 +856,25 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr, > } > } > > + /* > + * Map types that can't be iterated fail on the very first key. Say so > + * before printing anything, so that the output isn't an empty dump. > + */ Or simply: /* Fail early to avoid an empty dump if map type cannot be iterated */ But that's a minor nit, the patch looks good to me, thank you! Acked-by: Quentin Monnet <qmo@kernel.org> Looks like you need to address Sashiko's findings on patch 2, though. > + if (bpf_map_get_next_key(fd, NULL, key) && errno != ENOENT) { > + const char *map_type_str; > + int saved_errno = errno; > + > + map_type_str = libbpf_bpf_map_type_str(info->type); > + if (map_type_str) > + p_err("can't dump %s map: %s", map_type_str, > + strerror(saved_errno)); > + else > + p_err("can't dump map of type %u: %s", info->type, > + strerror(saved_errno)); > + err = -1; > + goto exit_free; > + } Tianyi, note that this conflicts with your series ("bpftool: Add recursive map dumping"), one of you will have to rebase. > + > if (wtr) { > err = get_map_kv_btf(info, &btf); > if (err) { > @@ -883,8 +902,11 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr, > while (true) { > err = bpf_map_get_next_key(fd, prev_key, key); > if (err) { > - if (errno == ENOENT) > + if (errno == ENOENT) { > err = 0; > + break; > + } > + p_err("can't get next key: %s", strerror(errno)); > break; > } > if (!dump_map_elem(fd, key, value, info, btf, wtr, > @@ -897,7 +919,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr, > jsonw_end_array(wtr); /* elements */ > if (show_header) > jsonw_end_object(wtr); /* map object */ > - } else { > + } else if (!err) { > printf("Found %u element%s\n", num_elems, > num_elems != 1 ? "s" : ""); > } ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next v2 1/2] bpftool: Fail map dump for maps that can't be iterated 2026-09-25 19:24 ` Quentin Monnet @ 2026-09-26 1:44 ` Tianyi Chen 0 siblings, 0 replies; 7+ messages in thread From: Tianyi Chen @ 2026-09-26 1:44 UTC (permalink / raw) To: qmo, skokovmaksimevg Cc: bpf, ast, daniel, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai, kuba, shuah, linux-kselftest Hi Quentin, Maxim, > Tianyi, note that this conflicts with your series ("bpftool: Add > recursive map dumping"), one of you will have to rebase. I'll take care of rebasing the recursive dump series. I have posted v9 on current bpf-next plus Maxim's v2 patch 1/2, explicitly listed as a prerequisite in the cover letter: https://lore.kernel.org/r/20260926014208.24773-1-hi@tychen.cc The conflict resolution keeps the early iteration check before recursive plain/BTF output and retains Maxim's iteration-error reporting. All 15 recursive dump subtests pass on the combined tree. I also checked that non-iterable ringbuf dumps fail with valid JSON, with and without recursion. Maxim, please go ahead with your revisions; no need to rebase around my series. I'll adjust mine if your implementation changes. Thanks, Tianyi ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage 2026-09-25 16:20 [PATCH bpf-next v2 0/2] bpftool: Fail map dump for maps that can't be iterated Maxim Skokov 2026-09-25 16:20 ` [PATCH bpf-next v2 1/2] " Maxim Skokov @ 2026-09-25 16:20 ` Maxim Skokov 2026-09-25 16:29 ` sashiko-bot 1 sibling, 1 reply; 7+ messages in thread From: Maxim Skokov @ 2026-09-25 16:20 UTC (permalink / raw) To: bpf Cc: qmo, ast, daniel, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai, kuba, shuah, linux-kselftest, Maxim Skokov Socket storage can't be iterated from user space. Create a BPF_MAP_TYPE_SK_STORAGE map, attach storage to a socket, pin it, and check that "bpftool map dump" fails with nothing but an error: a JSON error object with -j, no elements and no count on stdout in plain mode, and the reason on stderr. Assisted-by: LLM Signed-off-by: Maxim Skokov <skokovmaksimevg@gmail.com> --- .../bpf/prog_tests/bpftool_map_dump.c | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c b/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c new file mode 100644 index 0000000000..0a0fa8926b --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <sys/socket.h> +#include <unistd.h> +#include <bpf/btf.h> +#include <test_progs.h> +#include "bpftool_helpers.h" + +#define PIN_PATH "/sys/fs/bpf/test_bpftool_map_dump_sk_storage" +#define JSON_ERROR "{\"error\":\"can't dump sk_storage map" + +/* A socket storage map with a single element, keyed on a socket we own. */ +static int create_sk_storage_map(void) +{ + LIBBPF_OPTS(bpf_map_create_opts, opts, + .map_flags = BPF_F_NO_PREALLOC); + struct btf *btf; + int int_id, fd = -1; + + btf = btf__new_empty(); + if (!ASSERT_OK_PTR(btf, "btf__new_empty")) + return -1; + + int_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED); + if (!ASSERT_GT(int_id, 0, "btf__add_int")) + goto out; + if (!ASSERT_OK(btf__load_into_kernel(btf), "btf__load_into_kernel")) + goto out; + + opts.btf_fd = btf__fd(btf); + opts.btf_key_type_id = int_id; + opts.btf_value_type_id = int_id; + fd = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage", sizeof(int), + sizeof(int), 0, &opts); + ASSERT_OK_FD(fd, "bpf_map_create"); +out: + btf__free(btf); + return fd; +} + +void test_bpftool_map_dump(void) +{ + int map_fd, sk_fd = -1, value = 42, ret; + char output[1024] = {}; + + map_fd = create_sk_storage_map(); + if (map_fd < 0) + return; + + sk_fd = socket(AF_INET, SOCK_STREAM, 0); + if (!ASSERT_OK_FD(sk_fd, "socket")) + goto out; + if (!ASSERT_OK(bpf_map_update_elem(map_fd, &sk_fd, &value, BPF_NOEXIST), + "add socket storage")) + goto out; + if (!ASSERT_OK(bpf_obj_pin(map_fd, PIN_PATH), "pin map")) + goto out; + + /* + * Socket storage can't be iterated, so the dump must fail with an + * error and nothing else, rather than print an empty map that holds + * an element. The buffer is cleared before each run so that a run + * that fails to start can't match stale or uninitialised output. + */ + ret = get_bpftool_command_output("-j map dump pinned " PIN_PATH, + output, sizeof(output)); + ASSERT_NEQ(ret, 0, "json dump fails"); + ASSERT_STRNEQ(output, JSON_ERROR, strlen(JSON_ERROR), + "json dump is only the error"); + + memset(output, 0, sizeof(output)); + ret = get_bpftool_command_output("map dump pinned " PIN_PATH, + output, sizeof(output)); + ASSERT_NEQ(ret, 0, "plain dump fails"); + ASSERT_NULL(strchr(output, '['), "plain dump prints no elements"); + ASSERT_NULL(strstr(output, "Found"), "plain dump prints no count"); + + memset(output, 0, sizeof(output)); + get_bpftool_command_output("map dump pinned " PIN_PATH " 2>&1 >/dev/null", + output, sizeof(output)); + ASSERT_HAS_SUBSTR(output, "can't dump sk_storage map", + "plain dump explains why on stderr"); + + unlink(PIN_PATH); +out: + if (sk_fd >= 0) + close(sk_fd); + close(map_fd); +} -- 2.47.3 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage 2026-09-25 16:20 ` [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage Maxim Skokov @ 2026-09-25 16:29 ` sashiko-bot 2026-09-25 19:32 ` Maxim Skokov 0 siblings, 1 reply; 7+ messages in thread From: sashiko-bot @ 2026-09-25 16:29 UTC (permalink / raw) To: Maxim Skokov; +Cc: bpf Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] bpf: missing unlink of pinned map causes permanent test failure -- commit 602b2c7f146f62e2714d566e5a1fb9912bee7425 Author: Maxim Skokov <skokovmaksimevg@gmail.com> selftests/bpf: Check that bpftool map dump fails for socket storage This commit adds a test to verify that bpftool fails to dump a socket storage map. It creates a map, attaches storage to a socket, pins it, and checks various output formats. > diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c b/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c > new file mode 100644 > index 0000000000000..0a0fa8926b239 > --- /dev/null > +++ b/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c > @@ -0,0 +1,88 @@ [ ... ] > +void test_bpftool_map_dump(void) > +{ > + int map_fd, sk_fd = -1, value = 42, ret; > + char output[1024] = {}; > + > + map_fd = create_sk_storage_map(); > + if (map_fd < 0) > + return; > + > + sk_fd = socket(AF_INET, SOCK_STREAM, 0); > + if (!ASSERT_OK_FD(sk_fd, "socket")) > + goto out; > + if (!ASSERT_OK(bpf_map_update_elem(map_fd, &sk_fd, &value, BPF_NOEXIST), > + "add socket storage")) > + goto out; > + if (!ASSERT_OK(bpf_obj_pin(map_fd, PIN_PATH), "pin map")) > + goto out; [Severity: Medium] Looking at test_bpftool_map_dump() in tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c, will this cause a permanent test failure on subsequent runs if the pin operation fails, or if the test is interrupted early? If bpf_obj_pin fails and we jump to the out label, the test skips over the unlink cleanup. The map then remains pinned, which means the next run will encounter EEXIST when it tries to pin the map again. [ ... ] > + ASSERT_HAS_SUBSTR(output, "can't dump sk_storage map", > + "plain dump explains why on stderr"); > + > + unlink(PIN_PATH); > +out: > + if (sk_fd >= 0) > + close(sk_fd); > + close(map_fd); > +} [Severity: Medium] Since the out label is positioned after unlink(PIN_PATH), the cleanup is bypassed on failure paths. Should the unlink be moved after the out label so the cleanup is always executed? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260925162031.27259-1-skokovmaksimevg@gmail.com?part=2 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage 2026-09-25 16:29 ` sashiko-bot @ 2026-09-25 19:32 ` Maxim Skokov 0 siblings, 0 replies; 7+ messages in thread From: Maxim Skokov @ 2026-09-25 19:32 UTC (permalink / raw) To: sashiko-reviews; +Cc: bpf, Maxim Skokov > Since the out label is positioned after unlink(PIN_PATH), the cleanup is > bypassed on failure paths. > > Should the unlink be moved after the out label so the cleanup is always > executed? A failed pin creates nothing, and every check after a successful pin falls through to the unlink, so no path leaves the map pinned. What is left is a run killed after the pin: the stale pin then makes every later run fail with EEXIST. Moving the unlink under the out label lets the next failed run clear it, so I'll do that in the next version. ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-26 1:44 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-25 16:20 [PATCH bpf-next v2 0/2] bpftool: Fail map dump for maps that can't be iterated Maxim Skokov 2026-09-25 16:20 ` [PATCH bpf-next v2 1/2] " Maxim Skokov 2026-09-25 19:24 ` Quentin Monnet 2026-09-26 1:44 ` Tianyi Chen 2026-09-25 16:20 ` [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage Maxim Skokov 2026-09-25 16:29 ` sashiko-bot 2026-09-25 19:32 ` Maxim Skokov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).