Re: [PATCH bpf v3 2/2] selftests/bpf: Cover strparser self-pass forward allocation
From: bot+bpf-ci@kernel.org
Date: 2026-08-17 16:52:13
Also in:
bpf, lkml
quoted hunk ↗ jump to hunk
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c b/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c index 1d7231728eaf..c7ad21d0bbf4 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c@@ -1,5 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 #include <error.h> +#include <linux/inet_diag.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/sock_diag.h> #include <netinet/tcp.h> #include <test_progs.h> #include "sockmap_helpers.h"@@ -460,6 +464,171 @@ static void test_sockmap_strp_parser_reject(void) test_sockmap_strp__destroy(strp); } +/* Read sk_forward_alloc through inet_diag meminfo. */ +static int sockmap_strp_get_fwd_alloc(int sock, int *fwd_alloc) +{ + struct sockaddr_storage local = {}, peer = {}; + struct sockaddr_in *local_in, *peer_in; + socklen_addr_len = sizeof(local); + char buf[1024]; + struct { + struct nlmsghdr nlh; + struct inet_diag_req_v2 req; + } req = { + .nlh = { + .nlmsg_len = sizeof(req), + .nlmsg_type = SOCK_DIAG_BY_FAMILY, + .nlmsg_flags = NLM_F_REQUEST, + .nlmsg_seq = 1, + }, + .req = { + .sdiag_family = AF_INET,
sockmap_strp_get_fwd_alloc() takes an arbitrary socket fd but hardcodes .sdiag_family = AF_INET and casts the sockaddr_storage results of getsockname()/getpeername() to struct sockaddr_in * without checking local.ss_family. For an AF_INET6 socket it would build a request from IPv4-shaped fields of an IPv6 address and then fail the exact lookup, returning -ENOENT and causing a confusing test failure rather than a wrong-family diagnostic. This matters because every other subtest in this file is parameterised over AF_INET and AF_INET6 (test_sockmap_strp_pass(AF_INET6, ...) etc.), so this helper is a candidate for reuse in the v6 direction. Could the helper derive sdiag_family/idiag_src/idiag_dst from ss_family, or reject a non-AF_INET socket explicitly?
+ .sdiag_protocol = IPPROTO_TCP,
+ .idiag_ext = 1 << (INET_DIAG_MEMINFO - 1),
+ .idiag_states = ~0U,
+ .id.idiag_cookie = {
+ INET_DIAG_NOCOOKIE,
+ INET_DIAG_NOCOOKIE,
+ },
+ },
+ };
+ int diag_fd, ret, err = -ENOENT;
+
+ if (getsockname(sock, (struct sockaddr *)&local, &addr_len))
+ return -errno;
+ addr_len = sizeof(peer);
+ if (getpeername(sock, (struct sockaddr *)&peer, &addr_len))
+ return -errno;
+
+ local_in = (struct sockaddr_in *)&local;
+ peer_in = (struct sockaddr_in *)&peer;
+ req.req.id.idiag_sport = local_in->sin_port;
+ req.req.id.idiag_dport = peer_in->sin_port;
+ req.req.id.idiag_src[0] = local_in->sin_addr.s_addr;
+ req.req.id.idiag_dst[0] = peer_in->sin_addr.s_addr;
+
+ diag_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC,
+ NETLINK_SOCK_DIAG);
+ if (diag_fd < 0)
+ return -errno;A subsystem pattern flags this as potentially concerning: the new subtest adds an undeclared kernel-config dependency on the INET socket-monitoring interface and turns its absence into a test failure rather than a skip. socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG) fails with EPROTONOSUPPORT when sock_diag is unavailable, and when inet_diag/tcp_diag is not built in (or is a module that cannot be auto-loaded) inet_diag_lock_handler() / __sock_diag_cmd() reply with NLMSG_ERROR, which this helper maps to -EINVAL. Either way, the ASSERT_OK(err, "sockmap_strp_get_fwd_alloc") at test_sockmap_strp_self_pass_fwd_alloc() reports a failure that has nothing to do with the sockmap/strparser behaviour under test. CONFIG_INET_DIAG and CONFIG_INET_TCP_DIAG do not appear in tools/testing/selftests/bpf/config or any of the per-arch config fragments. They only default to y from net/ipv4/Kconfig, and are tristate, so a base config with =m and no installed modules, or =n, breaks the subtest. Should the config symbols be added to tools/testing/selftests/bpf/config, or should the code detect the missing capability and call test__skip() with a reason? [ ... ]
+/* Test strparser SK_PASS delivery to the same socket. */
+static void test_sockmap_strp_self_pass_fwd_alloc(void)
+{
+ struct test_sockmap_strp *strp = NULL;
+ char snd[4 * 1024];
+ int c = -1, p = -1;
+ int fwd_alloc;
+ int sndbuf = sizeof(snd);
+ int zero = 0;
+ char rcv;
+ int sent, recvd;
+ int map;
+ int err;
+
+ memset(snd, 0xa5, sizeof(snd));
+
+ strp = test_sockmap_strp__open_and_load();
+ if (!ASSERT_OK_PTR(strp, "test_sockmap_strp__open_and_load"))
+ return;
+
+ map = bpf_map__fd(strp->maps.sock_map);
+ err = xbpf_prog_attach(bpf_program__fd(strp->progs.prog_skb_parser_one),
+ map, BPF_SK_SKB_STREAM_PARSER, 0);
+ if (err)
+ goto out_destroy;
+
+ err = xbpf_prog_attach(bpf_program__fd(strp->progs.prog_skb_verdict_pass),
+ map, BPF_SK_SKB_STREAM_VERDICT, 0);
+ if (err)
+ goto out_destroy;
+
+ err = create_pair(AF_INET, SOCK_STREAM, &c, &p);
+ if (!ASSERT_OK(err, "create_pair"))
+ goto out_destroy;
+
+ err = xsetsockopt(c, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
+ if (err)
+ goto out_destroy;
+
+ err = xsetsockopt(p, SOL_SOCKET, SO_RCVBUF, &sndbuf, sizeof(sndbuf));
+ if (err)
+ goto out_destroy;
+
+ err = xbpf_map_update_elem(map, &zero, &p, BPF_NOEXIST);
+ if (err)
+ goto out_destroy;
+
+ sent = send(c, snd, sizeof(snd), MSG_DONTWAIT);
+ if (!ASSERT_EQ(sent, sizeof(snd), "send"))
+ goto out_destroy;
+
+ recvd = recv_timeout(p, &rcv, sizeof(rcv), MSG_DONTWAIT,
+ IO_TIMEOUT_SEC);
+ if (!ASSERT_EQ(recvd, sizeof(rcv), "recv_timeout") ||
+ !ASSERT_EQ(rcv, snd[0], "data mismatch"))
+ goto out_destroy;Does anything here confirm that the one-byte parser actually split the 4 KB write into many strparser messages? Reading a single byte and comparing it to snd[0] succeeds identically whether the stream parser/verdict programs took effect or not (a plain TCP socket returns the same byte). The only other assertion is sk_forward_alloc
= 0, which is trivially true when no self-pass clone was ever charged.
If the setup silently stops exercising the path -- for example, a future change rejects a parse verdict of 1, the parser attach becomes a no-op for some reason, or the messages end up on the psock backlog and are dropped -- the subtest degrades into a tautological pass and stops guarding the fix.
+ + err = sockmap_strp_get_fwd_alloc(p, &fwd_alloc); + if (!ASSERT_OK(err, "sockmap_strp_get_fwd_alloc") || + !ASSERT_GE(fwd_alloc, 0, "sk_forward_alloc")) + goto out_destroy;
Can the asserted condition (sk_forward_alloc >= 0 while self-pass strparser
clones are queued) fail on a fixed kernel?
With the fix, sk_psock_skb_ingress_self_assign() does:
if (settle_fwd_alloc) {
sock_owned_by_me(sk);
if (!sk_rmem_schedule(sk, skb, 0))
return -EAGAIN;
}
skb_set_owner_r(skb, sk);
sk_rmem_schedule(sk, skb, 0) reserves only ceil(deficit / PAGE_SIZE) pages,
leaving sk_forward_alloc in [0, PAGE_SIZE). skb_set_owner_r() then calls
sk_mem_charge(sk, skb->truesize) for the full clone truesize.
net/strparser/strparser.c gives every one of the ~4096 one-byte messages a
skb_clone() of the original 4 KB skb, so truesize is ~4.8 KB per clone.
Therefore, after every single clone charge, sk_forward_alloc is negative
(roughly -(truesize - PAGE_SIZE) .. -truesize) on the fixed kernel too.
The assertion only passes because the one recv() of 1 byte fully consumes
the first message, and kfree_sk_msg() -> consume_skb() -> sock_rfree()
uncharges exactly one truesize, cancelling the last charge and leaving
sk_forward_alloc in [0, PAGE_SIZE).
Any clone charged between that recv() and the inet_diag read makes the
sampled value negative and the subtest fails even though the kernel is
correct. Two realistic ways that happens:
1. Deferred strparser work. tcp_bpf_recvmsg_parser() calls tcp_data_ready()
while holding lock_sock() when sk_receive_queue is non-empty
(net/ipv4/tcp_bpf.c) -- exactly the behaviour the comment at the top of
this file already documents for sockmap_strp_consume_pre_data().
strp_data_ready() then queue_work()s the parse, which runs after recv()
returns and charges more clones.
2. psock backlog. If any one ingress attempt returns -EAGAIN
(sk_rmem_schedule() failing under memcg or global tcp_mem pressure -- the
test intentionally piles ~20 MB of clone truesize onto one socket), the skb
is queued on psock->ingress_skb and from then on every message is charged
asynchronously by sk_psock_backlog(), potentially after the recv().
Could the test sample something that is monotone with respect to the bug
instead: drain all 4 KB (so every clone is uncharged), or re-read/poll
until the value settles, or assert on a value that the fixed kernel
guarantees at all times?
quoted hunk ↗ jump to hunk
+out_destroy: + test_sockmap_strp__destroy(strp); + if (c >= 0) + close(c); + if (p >= 0) + close(p); +} + void test_sockmap_strp(void) { if (test__start_subtest("sockmap strp tcp pass"))@@ -482,4 +651,6 @@ void test_sockmap_strp(void) test_sockmap_strp_dispatch_pkt(AF_INET, SOCK_STREAM); if (test__start_subtest("sockmap strp parser reject pkt mod")) test_sockmap_strp_parser_reject(); + if (test__start_subtest("sockmap strp self pass fwd alloc")) + test_sockmap_strp_self_pass_fwd_alloc(); }
[ ... ] --- 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/32045036810