* [PATCH] selftests/bpf: tc_tunnel - validate decap tunnel state without false-pass gaps
@ 2026-08-18 12:03 Nick Hudson
2026-08-18 12:11 ` sashiko-bot
2026-08-18 12:59 ` bot+bpf-ci
0 siblings, 2 replies; 3+ messages in thread
From: Nick Hudson @ 2026-08-18 12:03 UTC (permalink / raw)
To: bpf, netdev
Cc: Nick Hudson, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Shuah Khan,
linux-kselftest, linux-kernel
This follow-up tightens the selftest to guard against false passes in
GSO decap validation.
It validates the expected post-decap state for both GSO and non-GSO
packets: expected tunnel gso_type bits must be cleared and
skb->encapsulation must match the remaining tunnel state.
It also adds explicit assertions that the decap validation path was
executed, including the large-send phase for GSO-marked subtests, and
asserts that the large-send marker is reset after the send.
This keeps the tc_tunnel decap checks meaningful and avoids false-pass
regressions without over-constraining behavior across tunnel modes.
Signed-off-by: Nick Hudson <nhudson@akamai.com>
---
.../selftests/bpf/prog_tests/test_tc_tunnel.c | 33 +++++++++++++++----
.../selftests/bpf/progs/test_tc_tunnel.c | 19 +++++++++++
2 files changed, 46 insertions(+), 6 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c b/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c
index 67ba27d69347..08d7d90f7772 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c
@@ -206,7 +206,7 @@ static void disconnect_client_from_server(struct subtest_cfg *cfg,
free(conn);
}
-static int send_and_test_data(struct subtest_cfg *cfg)
+static int send_and_test_data(struct subtest_cfg *cfg, struct test_tc_tunnel *skel)
{
struct connection *conn;
int err, res = -1;
@@ -215,6 +215,7 @@ static int send_and_test_data(struct subtest_cfg *cfg)
if (!ASSERT_OK_PTR(conn, "connect to server"))
return -1;
+ skel->bss->decap_expect_large_send = 0;
err = send(conn->client_fd, tx_buffer, DEFAULT_TEST_DATA_SIZE, 0);
if (!ASSERT_EQ(err, DEFAULT_TEST_DATA_SIZE, "send data from client"))
goto end;
@@ -226,14 +227,17 @@ static int send_and_test_data(struct subtest_cfg *cfg)
goto end;
}
+ skel->bss->decap_expect_large_send = 1;
err = send(conn->client_fd, tx_buffer, GSO_TEST_DATA_SIZE, 0);
if (!ASSERT_EQ(err, GSO_TEST_DATA_SIZE, "send (large) data from client"))
goto end;
if (check_server_rx_data(cfg, conn, DEFAULT_TEST_DATA_SIZE))
goto end;
+ skel->bss->decap_expect_large_send = 0;
res = 0;
end:
+ skel->bss->decap_expect_large_send = 0;
disconnect_client_from_server(cfg, conn);
return res;
}
@@ -374,10 +378,15 @@ static int configure_ebpf_decapsulation(struct subtest_cfg *cfg)
return ret;
}
-static void run_test(struct subtest_cfg *cfg)
+static void run_test(struct subtest_cfg *cfg, struct test_tc_tunnel *skel)
{
struct nstoken *nstoken;
+ skel->bss->decap_validation_seen = 0;
+ skel->bss->decap_gso_validation_seen = 0;
+ skel->bss->decap_large_send_validation_seen = 0;
+ skel->bss->decap_expect_large_send = 0;
+
if (!ASSERT_OK(run_server(cfg), "run server"))
return;
@@ -386,7 +395,7 @@ static void run_test(struct subtest_cfg *cfg)
goto fail;
/* Basic communication must work */
- if (!ASSERT_OK(send_and_test_data(cfg), "connect without any encap"))
+ if (!ASSERT_OK(send_and_test_data(cfg, skel), "connect without any encap"))
goto fail;
/* Attach encapsulation program to client */
@@ -398,7 +407,7 @@ static void run_test(struct subtest_cfg *cfg)
if (!ASSERT_OK(configure_kernel_decapsulation(cfg),
"configure kernel decapsulation"))
goto fail;
- if (!ASSERT_OK(send_and_test_data(cfg),
+ if (!ASSERT_OK(send_and_test_data(cfg, skel),
"connect with encap prog and kern decap"))
goto fail;
}
@@ -406,7 +415,18 @@ static void run_test(struct subtest_cfg *cfg)
/* Replace kernel decapsulation with BPF decapsulation, test must pass */
if (!ASSERT_OK(configure_ebpf_decapsulation(cfg), "configure ebpf decapsulation"))
goto fail;
- ASSERT_OK(send_and_test_data(cfg), "connect with encap and decap progs");
+ if (!ASSERT_OK(send_and_test_data(cfg, skel), "connect with encap and decap progs"))
+ goto fail;
+ if (!ASSERT_NEQ(skel->bss->decap_validation_seen, 0,
+ "decap validation executed"))
+ goto fail;
+ if (!ASSERT_EQ(skel->bss->decap_expect_large_send, 0,
+ "decap large-send marker reset"))
+ goto fail;
+ if (cfg->test_gso) {
+ ASSERT_NEQ(skel->bss->decap_large_send_validation_seen, 0,
+ "decap validation executed for large send");
+ }
fail:
close_netns(nstoken);
@@ -438,6 +458,7 @@ static int setup(void)
SYS(fail_close_ns_client, "ip link add %s type veth peer name %s",
"veth1 mtu 1500 netns " CLIENT_NS " address " MAC_ADDR_VETH1,
"veth2 mtu 1500 netns " SERVER_NS " address " MAC_ADDR_VETH2);
+ SYS(fail_close_ns_client, "ethtool -K veth1 tso off");
SYS(fail_close_ns_client, "ip link set veth1 up");
nstoken_server = open_netns(SERVER_NS);
if (!ASSERT_OK_PTR(nstoken_server, "open server ns"))
@@ -701,7 +722,7 @@ void test_tc_tunnel(void)
if (ret < 0 || !test__start_subtest(cfg->name))
continue;
if (subtest_setup(skel, cfg) == 0)
- run_test(cfg);
+ run_test(cfg, skel);
subtest_cleanup(cfg);
}
cleanup();
diff --git a/tools/testing/selftests/bpf/progs/test_tc_tunnel.c b/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
index 853bca962910..e9bd1c9781f7 100644
--- a/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
+++ b/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
@@ -16,6 +16,11 @@ static const int cfg_port = 8000;
static const int cfg_udp_src = 20000;
+__u64 decap_validation_seen;
+__u64 decap_gso_validation_seen;
+__u64 decap_large_send_validation_seen;
+__u32 decap_expect_large_send;
+
#define ETH_P_MPLS_UC 0x8847
#define ETH_P_TEB 0x6558
@@ -690,7 +695,21 @@ static int decap_internal(struct __sk_buff *skb, int off, int len, char proto,
kskb = bpf_cast_to_kern_ctx(skb);
shinfo = bpf_core_cast(kskb->head + kskb->end, struct skb_shared_info);
+
+ if (flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
+ BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK))
+ decap_validation_seen++;
+
+ if ((flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
+ BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) &&
+ decap_expect_large_send)
+ decap_large_send_validation_seen++;
+
if (shinfo->gso_size) {
+ if (flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
+ BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK))
+ decap_gso_validation_seen++;
+
if ((flags & BPF_F_ADJ_ROOM_DECAP_L4_UDP) &&
(shinfo->gso_type & SKB_GSO_UDP_TUNNEL_MASK))
return TC_ACT_SHOT;
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH] selftests/bpf: tc_tunnel - validate decap tunnel state without false-pass gaps
2026-08-18 12:03 [PATCH] selftests/bpf: tc_tunnel - validate decap tunnel state without false-pass gaps Nick Hudson
@ 2026-08-18 12:11 ` sashiko-bot
2026-08-18 12:59 ` bot+bpf-ci
1 sibling, 0 replies; 3+ messages in thread
From: sashiko-bot @ 2026-08-18 12:11 UTC (permalink / raw)
To: Nick Hudson; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The variable `decap_gso_validation_seen` is introduced to track whether GSO metadata was present during decap validation, but it is never asserted in the test suite.
--
--- Patch [1]: [PATCH] selftests/bpf: tc_tunnel - validate decap tunnel state without false-pass gaps ---
commit f4a6b83a5918e8a74560dcb08d2827677bbdfe70
Author: Nick Hudson <nhudson@akamai.com>
selftests/bpf: tc_tunnel - validate decap tunnel state without false-pass gaps
This commit tightens the tc_tunnel selftest to guard against false passes
in GSO decap validation. It asserts that decap validation paths are executed
during large-send phases and verifies post-decap states.
> diff --git a/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c b/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c
> --- a/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c
> +++ b/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c
[ ... ]
> @@ -406,7 +415,18 @@ static void run_test(struct subtest_cfg *cfg)
> /* Replace kernel decapsulation with BPF decapsulation, test must pass */
> if (!ASSERT_OK(configure_ebpf_decapsulation(cfg), "configure ebpf decapsulation"))
> goto fail;
> - ASSERT_OK(send_and_test_data(cfg), "connect with encap and decap progs");
> + if (!ASSERT_OK(send_and_test_data(cfg, skel), "connect with encap and decap progs"))
> + goto fail;
> + if (!ASSERT_NEQ(skel->bss->decap_validation_seen, 0,
> + "decap validation executed"))
> + goto fail;
> + if (!ASSERT_EQ(skel->bss->decap_expect_large_send, 0,
> + "decap large-send marker reset"))
> + goto fail;
> + if (cfg->test_gso) {
> + ASSERT_NEQ(skel->bss->decap_large_send_validation_seen, 0,
> + "decap validation executed for large send");
> + }
[Severity: Medium]
Is decap_gso_validation_seen missing an assertion here?
The BPF program tracks this variable to verify the presence of GSO metadata,
but it appears the userspace test runner never actually checks it.
If a kernel bug strips the GSO metadata before reaching the BPF program,
shinfo->gso_size would be 0, bypassing the inner GSO validations. The test
might still pass because it only asserts decap_large_send_validation_seen.
> diff --git a/tools/testing/selftests/bpf/progs/test_tc_tunnel.c b/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
> --- a/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
> +++ b/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
[ ... ]
> @@ -690,7 +695,21 @@ static int decap_internal(struct __sk_buff *skb, int off, int len, char proto,
>
> kskb = bpf_cast_to_kern_ctx(skb);
> shinfo = bpf_core_cast(kskb->head + kskb->end, struct skb_shared_info);
> +
> + if (flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
> + BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK))
> + decap_validation_seen++;
> +
> + if ((flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
> + BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) &&
> + decap_expect_large_send)
> + decap_large_send_validation_seen++;
> +
> if (shinfo->gso_size) {
> + if (flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
> + BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK))
> + decap_gso_validation_seen++;
> +
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260818120308.623905-1-nhudson@akamai.com?part=1
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] selftests/bpf: tc_tunnel - validate decap tunnel state without false-pass gaps
2026-08-18 12:03 [PATCH] selftests/bpf: tc_tunnel - validate decap tunnel state without false-pass gaps Nick Hudson
2026-08-18 12:11 ` sashiko-bot
@ 2026-08-18 12:59 ` bot+bpf-ci
1 sibling, 0 replies; 3+ messages in thread
From: bot+bpf-ci @ 2026-08-18 12:59 UTC (permalink / raw)
To: nhudson, bpf, netdev
Cc: nhudson, ast, daniel, andrii, eddyz87, memxor, shuah,
linux-kselftest, linux-kernel, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4753 bytes --]
> This follow-up tightens the selftest to guard against false passes in
> GSO decap validation.
>
> It validates the expected post-decap state for both GSO and non-GSO
> packets: expected tunnel gso_type bits must be cleared and
> skb->encapsulation must match the remaining tunnel state.
Does this paragraph accurately describe what the patch adds?
The gso_type and skb->encapsulation validation it mentions already exists
in the base revision. Comparing HEAD^ with HEAD for
progs/test_tc_tunnel.c shows the added lines are only the four .bss
globals and three counter increments. The checks described appear only
as context lines:
if ((flags & BPF_F_ADJ_ROOM_DECAP_L4_UDP) &&
(shinfo->gso_type & SKB_GSO_UDP_TUNNEL_MASK))
return TC_ACT_SHOT;
...
if (flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) {
if ((shinfo->gso_type & SKB_GSO_TUNNEL_MASK) &&
!kskb->encapsulation)
return TC_ACT_SHOT;
All of this validation is pre-existing. The series is a single commit
(530113d..1dc5f339), so this code is already upstream before this patch.
> It also adds explicit assertions that the decap validation path was
> executed, including the large-send phase for GSO-marked subtests, and
> asserts that the large-send marker is reset after the send.
The large-send marker reset assertion mentioned here checks a variable
that only userspace writes. decap_expect_large_send is read-only in the
BPF program, and send_and_test_data() sets it to 0 on both the success
path and the `end:` path before returning. Can that assertion ever fail?
Also, the commit message doesn't mention the 'ethtool -K veth1 tso off'
change in setup() even though it alters what traffic the test generates.
> diff --git a/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c b/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c
> index 67ba27d69347..08d7d90f7772 100644
> --- a/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c
> +++ b/tools/testing/selftests/bpf/prog_tests/test_tc_tunnel.c
[ ... ]
> @@ -374,10 +378,15 @@ static int configure_ebpf_decapsulation(struct subtest_cfg *cfg)
> return ret;
> }
>
> -static void run_test(struct subtest_cfg *cfg)
> +static void run_test(struct subtest_cfg *cfg, struct test_tc_tunnel *skel)
> {
> struct nstoken *nstoken;
>
> + skel->bss->decap_validation_seen = 0;
> + skel->bss->decap_gso_validation_seen = 0;
> + skel->bss->decap_large_send_validation_seen = 0;
> + skel->bss->decap_expect_large_send = 0;
> +
> if (!ASSERT_OK(run_server(cfg), "run server"))
> return;
>
> @@ -406,7 +415,18 @@ static void run_test(struct subtest_cfg *cfg)
> /* Replace kernel decapsulation with BPF decapsulation, test must pass */
> if (!ASSERT_OK(configure_ebpf_decapsulation(cfg), "configure ebpf decapsulation"))
> goto fail;
> - ASSERT_OK(send_and_test_data(cfg), "connect with encap and decap progs");
> + if (!ASSERT_OK(send_and_test_data(cfg, skel), "connect with encap and decap progs"))
> + goto fail;
> + if (!ASSERT_NEQ(skel->bss->decap_validation_seen, 0,
> + "decap validation executed"))
> + goto fail;
> + if (!ASSERT_EQ(skel->bss->decap_expect_large_send, 0,
> + "decap large-send marker reset"))
> + goto fail;
> + if (cfg->test_gso) {
> + ASSERT_NEQ(skel->bss->decap_large_send_validation_seen, 0,
> + "decap validation executed for large send");
> + }
Is the decap_expect_large_send assertion meaningful here?
decap_expect_large_send is written only by user space; the BPF program
only reads it. send_and_test_data() clears it on every exit path:
skel->bss->decap_expect_large_send = 0; /* line 236, success */
res = 0;
end:
skel->bss->decap_expect_large_send = 0; /* line 240, all paths */
Every return from send_and_test_data() passes through the `end:` label,
so by the time run_test() reads the value it is unconditionally 0.
A broken kernel, a broken BPF program and a correct one all produce the
same result. Can this check ever fail?
> diff --git a/tools/testing/selftests/bpf/progs/test_tc_tunnel.c b/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
> index 853bca962910..e9bd1c9781f7 100644
> --- a/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
> +++ b/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
[ ... ]
This looks like a bug fix for false-pass gaps in the selftest. Should
this include:
Fixes: adb771973026 ("selftests/bpf: tc_tunnel - validate decap GSO and encapsulation state")
---
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/32136539422
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-18 12:59 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 12:03 [PATCH] selftests/bpf: tc_tunnel - validate decap tunnel state without false-pass gaps Nick Hudson
2026-08-18 12:11 ` sashiko-bot
2026-08-18 12:59 ` bot+bpf-ci
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.