* [PATCH bpf v2 2/2] selftests/bpf: Add tests for sock_ops ctx access with same src/dst register
2026-04-06 3:12 [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops Jiayuan Chen
@ 2026-04-06 3:12 ` Jiayuan Chen
2026-04-06 16:47 ` sun jian
2026-04-06 23:01 ` Martin KaFai Lau
2026-04-06 3:47 ` [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops bot+bpf-ci
` (2 subsequent siblings)
3 siblings, 2 replies; 9+ messages in thread
From: Jiayuan Chen @ 2026-04-06 3:12 UTC (permalink / raw)
To: bpf
Cc: Jiayuan Chen, Martin KaFai Lau, Daniel Borkmann, John Fastabend,
Stanislav Fomichev, Alexei Starovoitov, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Song Liu,
Yonghong Song, Jiri Olsa, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
linux-kernel, netdev, linux-kselftest
Add selftests to verify SOCK_OPS_GET_SK() and SOCK_OPS_GET_FIELD()
correctly return NULL/zero when dst_reg == src_reg and is_fullsock == 0.
Three subtests are included:
- get_sk: ctx->sk with same src/dst register (SOCK_OPS_GET_SK)
- get_field: ctx->snd_cwnd with same src/dst register (SOCK_OPS_GET_FIELD)
- get_sk_diff_reg: ctx->sk with different src/dst register (baseline)
Each BPF program uses inline asm (__naked) to force specific register
allocation, reads is_fullsock first, then loads the field using the same
(or different) register. The test triggers TCP_NEW_SYN_RECV via a TCP
handshake and checks that the result is NULL/zero when is_fullsock == 0.
Test:
./test_progs -a sock_ops_get_sk -v
test_sock_ops_get_sk:PASS:join_cgroup 0 nsec
test_sock_ops_get_sk:PASS:skel_open_load 0 nsec
run_sock_ops_test:PASS:prog_attach 0 nsec
run_sock_ops_test:PASS:start_server 0 nsec
run_sock_ops_test:PASS:connect_to_fd 0 nsec
test_sock_ops_get_sk:PASS:null_seen 0 nsec
test_sock_ops_get_sk:PASS:bug_not_detected 0 nsec
#419/1 sock_ops_get_sk/get_sk:OK
run_sock_ops_test:PASS:prog_attach 0 nsec
run_sock_ops_test:PASS:start_server 0 nsec
run_sock_ops_test:PASS:connect_to_fd 0 nsec
test_sock_ops_get_sk:PASS:field_null_seen 0 nsec
test_sock_ops_get_sk:PASS:field_bug_not_detected 0 nsec
#419/2 sock_ops_get_sk/get_field:OK
run_sock_ops_test:PASS:prog_attach 0 nsec
run_sock_ops_test:PASS:start_server 0 nsec
run_sock_ops_test:PASS:connect_to_fd 0 nsec
test_sock_ops_get_sk:PASS:diff_reg_null_seen 0 nsec
test_sock_ops_get_sk:PASS:diff_reg_bug_not_detected 0 nsec
#419/3 sock_ops_get_sk/get_sk_diff_reg:OK
#419 sock_ops_get_sk:OK
Summary: 1/3 PASSED, 0 SKIPPED, 0 FAILED
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
.../bpf/prog_tests/sock_ops_get_sk.c | 77 ++++++++++++
.../selftests/bpf/progs/sock_ops_get_sk.c | 117 ++++++++++++++++++
2 files changed, 194 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c
create mode 100644 tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
diff --git a/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c b/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c
new file mode 100644
index 0000000000000..e086f7abb197e
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include "cgroup_helpers.h"
+#include "network_helpers.h"
+#include "sock_ops_get_sk.skel.h"
+
+/* See progs/sock_ops_get_sk.c for the bug description. */
+static void run_sock_ops_test(struct sock_ops_get_sk *skel, int cgroup_fd,
+ int prog_fd)
+{
+ int server_fd, client_fd, err;
+
+ err = bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_SOCK_OPS, 0);
+ if (!ASSERT_OK(err, "prog_attach"))
+ return;
+
+ server_fd = start_server(AF_INET, SOCK_STREAM, NULL, 0, 0);
+ if (!ASSERT_GE(server_fd, 0, "start_server"))
+ goto detach;
+
+ /* Trigger TCP handshake which causes TCP_NEW_SYN_RECV state where
+ * is_fullsock == 0 and is_locked_tcp_sock == 0.
+ */
+ client_fd = connect_to_fd(server_fd, 0);
+ if (!ASSERT_GE(client_fd, 0, "connect_to_fd"))
+ goto close_server;
+
+ close(client_fd);
+
+close_server:
+ close(server_fd);
+detach:
+ bpf_prog_detach(cgroup_fd, BPF_CGROUP_SOCK_OPS);
+}
+
+void test_sock_ops_get_sk(void)
+{
+ struct sock_ops_get_sk *skel;
+ int cgroup_fd;
+
+ cgroup_fd = test__join_cgroup("/sock_ops_get_sk");
+ if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup"))
+ return;
+
+ skel = sock_ops_get_sk__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_load"))
+ goto close_cgroup;
+
+ /* Test SOCK_OPS_GET_SK with same src/dst register */
+ if (test__start_subtest("get_sk")) {
+ run_sock_ops_test(skel, cgroup_fd,
+ bpf_program__fd(skel->progs.sock_ops_get_sk_same_reg));
+ ASSERT_EQ(skel->bss->null_seen, 1, "null_seen");
+ ASSERT_EQ(skel->bss->bug_detected, 0, "bug_not_detected");
+ }
+
+ /* Test SOCK_OPS_GET_FIELD with same src/dst register */
+ if (test__start_subtest("get_field")) {
+ run_sock_ops_test(skel, cgroup_fd,
+ bpf_program__fd(skel->progs.sock_ops_get_field_same_reg));
+ ASSERT_EQ(skel->bss->field_null_seen, 1, "field_null_seen");
+ ASSERT_EQ(skel->bss->field_bug_detected, 0, "field_bug_not_detected");
+ }
+
+ /* Test SOCK_OPS_GET_SK with different src/dst register */
+ if (test__start_subtest("get_sk_diff_reg")) {
+ run_sock_ops_test(skel, cgroup_fd,
+ bpf_program__fd(skel->progs.sock_ops_get_sk_diff_reg));
+ ASSERT_EQ(skel->bss->diff_reg_null_seen, 1, "diff_reg_null_seen");
+ ASSERT_EQ(skel->bss->diff_reg_bug_detected, 0, "diff_reg_bug_not_detected");
+ }
+
+ sock_ops_get_sk__destroy(skel);
+close_cgroup:
+ close(cgroup_fd);
+}
diff --git a/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c b/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
new file mode 100644
index 0000000000000..3a0689f8ce7ca
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+/*
+ * Test the SOCK_OPS_GET_SK() and SOCK_OPS_GET_FIELD() macros in
+ * sock_ops_convert_ctx_access() when dst_reg == src_reg.
+ *
+ * When dst_reg == src_reg, the macros borrow a temporary register to load
+ * is_fullsock / is_locked_tcp_sock, because dst_reg holds the ctx pointer
+ * and cannot be clobbered before ctx->sk / ctx->field is read. If
+ * is_fullsock == 0 (e.g., TCP_NEW_SYN_RECV with a request_sock), the macro
+ * must still zero dst_reg so the verifier's PTR_TO_SOCKET_OR_NULL /
+ * SCALAR_VALUE type is correct at runtime. A missing clear leaves a stale
+ * ctx pointer in dst_reg that passes NULL checks (GET_SK) or leaks a kernel
+ * address as a scalar (GET_FIELD).
+ *
+ * When dst_reg != src_reg, dst_reg itself is used to load is_fullsock, so
+ * the JEQ (dst_reg == 0) naturally leaves it zeroed on the !fullsock path.
+ */
+
+int bug_detected;
+int null_seen;
+
+SEC("sockops")
+__naked void sock_ops_get_sk_same_reg(void)
+{
+ asm volatile (
+ "r7 = *(u32 *)(r1 + %[is_fullsock_off]);"
+ "r1 = *(u64 *)(r1 + %[sk_off]);"
+ "if r7 != 0 goto 2f;"
+ "if r1 == 0 goto 1f;"
+ "r1 = %[bug_detected] ll;"
+ "r2 = 1;"
+ "*(u32 *)(r1 + 0) = r2;"
+ "goto 2f;"
+ "1:"
+ "r1 = %[null_seen] ll;"
+ "r2 = 1;"
+ "*(u32 *)(r1 + 0) = r2;"
+ "2:"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
+ __imm_const(sk_off, offsetof(struct bpf_sock_ops, sk)),
+ __imm_addr(bug_detected),
+ __imm_addr(null_seen)
+ : __clobber_all);
+}
+
+/* SOCK_OPS_GET_FIELD: same-register, is_locked_tcp_sock == 0 path. */
+int field_bug_detected;
+int field_null_seen;
+
+SEC("sockops")
+__naked void sock_ops_get_field_same_reg(void)
+{
+ asm volatile (
+ "r7 = *(u32 *)(r1 + %[is_fullsock_off]);"
+ "r1 = *(u32 *)(r1 + %[snd_cwnd_off]);"
+ "if r7 != 0 goto 2f;"
+ "if r1 == 0 goto 1f;"
+ "r1 = %[field_bug_detected] ll;"
+ "r2 = 1;"
+ "*(u32 *)(r1 + 0) = r2;"
+ "goto 2f;"
+ "1:"
+ "r1 = %[field_null_seen] ll;"
+ "r2 = 1;"
+ "*(u32 *)(r1 + 0) = r2;"
+ "2:"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
+ __imm_const(snd_cwnd_off, offsetof(struct bpf_sock_ops, snd_cwnd)),
+ __imm_addr(field_bug_detected),
+ __imm_addr(field_null_seen)
+ : __clobber_all);
+}
+
+/* SOCK_OPS_GET_SK: different-register, is_fullsock == 0 path. */
+int diff_reg_bug_detected;
+int diff_reg_null_seen;
+
+SEC("sockops")
+__naked void sock_ops_get_sk_diff_reg(void)
+{
+ asm volatile (
+ "r7 = r1;"
+ "r6 = *(u32 *)(r7 + %[is_fullsock_off]);"
+ "r2 = *(u64 *)(r7 + %[sk_off]);"
+ "if r6 != 0 goto 2f;"
+ "if r2 == 0 goto 1f;"
+ "r1 = %[diff_reg_bug_detected] ll;"
+ "r3 = 1;"
+ "*(u32 *)(r1 + 0) = r3;"
+ "goto 2f;"
+ "1:"
+ "r1 = %[diff_reg_null_seen] ll;"
+ "r3 = 1;"
+ "*(u32 *)(r1 + 0) = r3;"
+ "2:"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
+ __imm_const(sk_off, offsetof(struct bpf_sock_ops, sk)),
+ __imm_addr(diff_reg_bug_detected),
+ __imm_addr(diff_reg_null_seen)
+ : __clobber_all);
+}
+
+char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH bpf v2 2/2] selftests/bpf: Add tests for sock_ops ctx access with same src/dst register
2026-04-06 3:12 ` [PATCH bpf v2 2/2] selftests/bpf: Add tests for sock_ops ctx access with same src/dst register Jiayuan Chen
@ 2026-04-06 16:47 ` sun jian
2026-04-06 23:01 ` Martin KaFai Lau
1 sibling, 0 replies; 9+ messages in thread
From: sun jian @ 2026-04-06 16:47 UTC (permalink / raw)
To: Jiayuan Chen
Cc: bpf, Martin KaFai Lau, Daniel Borkmann, John Fastabend,
Stanislav Fomichev, Alexei Starovoitov, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Song Liu,
Yonghong Song, Jiri Olsa, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
linux-kernel, netdev, linux-kselftest
On Mon, Apr 6, 2026 at 11:15 AM Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
>
> Add selftests to verify SOCK_OPS_GET_SK() and SOCK_OPS_GET_FIELD()
> correctly return NULL/zero when dst_reg == src_reg and is_fullsock == 0.
>
> Three subtests are included:
> - get_sk: ctx->sk with same src/dst register (SOCK_OPS_GET_SK)
> - get_field: ctx->snd_cwnd with same src/dst register (SOCK_OPS_GET_FIELD)
> - get_sk_diff_reg: ctx->sk with different src/dst register (baseline)
>
> Each BPF program uses inline asm (__naked) to force specific register
> allocation, reads is_fullsock first, then loads the field using the same
> (or different) register. The test triggers TCP_NEW_SYN_RECV via a TCP
> handshake and checks that the result is NULL/zero when is_fullsock == 0.
>
> Test:
> ./test_progs -a sock_ops_get_sk -v
> test_sock_ops_get_sk:PASS:join_cgroup 0 nsec
> test_sock_ops_get_sk:PASS:skel_open_load 0 nsec
> run_sock_ops_test:PASS:prog_attach 0 nsec
> run_sock_ops_test:PASS:start_server 0 nsec
> run_sock_ops_test:PASS:connect_to_fd 0 nsec
> test_sock_ops_get_sk:PASS:null_seen 0 nsec
> test_sock_ops_get_sk:PASS:bug_not_detected 0 nsec
> #419/1 sock_ops_get_sk/get_sk:OK
> run_sock_ops_test:PASS:prog_attach 0 nsec
> run_sock_ops_test:PASS:start_server 0 nsec
> run_sock_ops_test:PASS:connect_to_fd 0 nsec
> test_sock_ops_get_sk:PASS:field_null_seen 0 nsec
> test_sock_ops_get_sk:PASS:field_bug_not_detected 0 nsec
> #419/2 sock_ops_get_sk/get_field:OK
> run_sock_ops_test:PASS:prog_attach 0 nsec
> run_sock_ops_test:PASS:start_server 0 nsec
> run_sock_ops_test:PASS:connect_to_fd 0 nsec
> test_sock_ops_get_sk:PASS:diff_reg_null_seen 0 nsec
> test_sock_ops_get_sk:PASS:diff_reg_bug_not_detected 0 nsec
> #419/3 sock_ops_get_sk/get_sk_diff_reg:OK
> #419 sock_ops_get_sk:OK
> Summary: 1/3 PASSED, 0 SKIPPED, 0 FAILED
>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
> ---
> .../bpf/prog_tests/sock_ops_get_sk.c | 77 ++++++++++++
> .../selftests/bpf/progs/sock_ops_get_sk.c | 117 ++++++++++++++++++
> 2 files changed, 194 insertions(+)
> create mode 100644 tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c
> create mode 100644 tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c b/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c
> new file mode 100644
> index 0000000000000..e086f7abb197e
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c
> @@ -0,0 +1,77 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <test_progs.h>
> +#include "cgroup_helpers.h"
> +#include "network_helpers.h"
> +#include "sock_ops_get_sk.skel.h"
> +
> +/* See progs/sock_ops_get_sk.c for the bug description. */
> +static void run_sock_ops_test(struct sock_ops_get_sk *skel, int cgroup_fd,
> + int prog_fd)
> +{
> + int server_fd, client_fd, err;
> +
> + err = bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_SOCK_OPS, 0);
> + if (!ASSERT_OK(err, "prog_attach"))
> + return;
> +
> + server_fd = start_server(AF_INET, SOCK_STREAM, NULL, 0, 0);
> + if (!ASSERT_GE(server_fd, 0, "start_server"))
> + goto detach;
> +
> + /* Trigger TCP handshake which causes TCP_NEW_SYN_RECV state where
> + * is_fullsock == 0 and is_locked_tcp_sock == 0.
> + */
> + client_fd = connect_to_fd(server_fd, 0);
> + if (!ASSERT_GE(client_fd, 0, "connect_to_fd"))
> + goto close_server;
> +
> + close(client_fd);
> +
> +close_server:
> + close(server_fd);
> +detach:
> + bpf_prog_detach(cgroup_fd, BPF_CGROUP_SOCK_OPS);
> +}
> +
> +void test_sock_ops_get_sk(void)
> +{
> + struct sock_ops_get_sk *skel;
> + int cgroup_fd;
> +
> + cgroup_fd = test__join_cgroup("/sock_ops_get_sk");
> + if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup"))
> + return;
> +
> + skel = sock_ops_get_sk__open_and_load();
> + if (!ASSERT_OK_PTR(skel, "skel_open_load"))
> + goto close_cgroup;
> +
> + /* Test SOCK_OPS_GET_SK with same src/dst register */
> + if (test__start_subtest("get_sk")) {
> + run_sock_ops_test(skel, cgroup_fd,
> + bpf_program__fd(skel->progs.sock_ops_get_sk_same_reg));
> + ASSERT_EQ(skel->bss->null_seen, 1, "null_seen");
> + ASSERT_EQ(skel->bss->bug_detected, 0, "bug_not_detected");
> + }
> +
> + /* Test SOCK_OPS_GET_FIELD with same src/dst register */
> + if (test__start_subtest("get_field")) {
> + run_sock_ops_test(skel, cgroup_fd,
> + bpf_program__fd(skel->progs.sock_ops_get_field_same_reg));
> + ASSERT_EQ(skel->bss->field_null_seen, 1, "field_null_seen");
> + ASSERT_EQ(skel->bss->field_bug_detected, 0, "field_bug_not_detected");
> + }
> +
> + /* Test SOCK_OPS_GET_SK with different src/dst register */
> + if (test__start_subtest("get_sk_diff_reg")) {
> + run_sock_ops_test(skel, cgroup_fd,
> + bpf_program__fd(skel->progs.sock_ops_get_sk_diff_reg));
> + ASSERT_EQ(skel->bss->diff_reg_null_seen, 1, "diff_reg_null_seen");
> + ASSERT_EQ(skel->bss->diff_reg_bug_detected, 0, "diff_reg_bug_not_detected");
> + }
> +
> + sock_ops_get_sk__destroy(skel);
> +close_cgroup:
> + close(cgroup_fd);
> +}
> diff --git a/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c b/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
> new file mode 100644
> index 0000000000000..3a0689f8ce7ca
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
> @@ -0,0 +1,117 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include "vmlinux.h"
> +#include <bpf/bpf_helpers.h>
> +#include "bpf_misc.h"
> +
> +/*
> + * Test the SOCK_OPS_GET_SK() and SOCK_OPS_GET_FIELD() macros in
> + * sock_ops_convert_ctx_access() when dst_reg == src_reg.
> + *
> + * When dst_reg == src_reg, the macros borrow a temporary register to load
> + * is_fullsock / is_locked_tcp_sock, because dst_reg holds the ctx pointer
> + * and cannot be clobbered before ctx->sk / ctx->field is read. If
> + * is_fullsock == 0 (e.g., TCP_NEW_SYN_RECV with a request_sock), the macro
> + * must still zero dst_reg so the verifier's PTR_TO_SOCKET_OR_NULL /
> + * SCALAR_VALUE type is correct at runtime. A missing clear leaves a stale
> + * ctx pointer in dst_reg that passes NULL checks (GET_SK) or leaks a kernel
> + * address as a scalar (GET_FIELD).
> + *
> + * When dst_reg != src_reg, dst_reg itself is used to load is_fullsock, so
> + * the JEQ (dst_reg == 0) naturally leaves it zeroed on the !fullsock path.
> + */
> +
> +int bug_detected;
> +int null_seen;
> +
> +SEC("sockops")
> +__naked void sock_ops_get_sk_same_reg(void)
> +{
> + asm volatile (
> + "r7 = *(u32 *)(r1 + %[is_fullsock_off]);"
> + "r1 = *(u64 *)(r1 + %[sk_off]);"
> + "if r7 != 0 goto 2f;"
> + "if r1 == 0 goto 1f;"
> + "r1 = %[bug_detected] ll;"
> + "r2 = 1;"
> + "*(u32 *)(r1 + 0) = r2;"
> + "goto 2f;"
> + "1:"
> + "r1 = %[null_seen] ll;"
> + "r2 = 1;"
> + "*(u32 *)(r1 + 0) = r2;"
> + "2:"
> + "r0 = 1;"
> + "exit;"
> + :
> + : __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
> + __imm_const(sk_off, offsetof(struct bpf_sock_ops, sk)),
> + __imm_addr(bug_detected),
> + __imm_addr(null_seen)
> + : __clobber_all);
> +}
> +
> +/* SOCK_OPS_GET_FIELD: same-register, is_locked_tcp_sock == 0 path. */
> +int field_bug_detected;
> +int field_null_seen;
> +
> +SEC("sockops")
> +__naked void sock_ops_get_field_same_reg(void)
> +{
> + asm volatile (
> + "r7 = *(u32 *)(r1 + %[is_fullsock_off]);"
> + "r1 = *(u32 *)(r1 + %[snd_cwnd_off]);"
> + "if r7 != 0 goto 2f;"
> + "if r1 == 0 goto 1f;"
> + "r1 = %[field_bug_detected] ll;"
> + "r2 = 1;"
> + "*(u32 *)(r1 + 0) = r2;"
> + "goto 2f;"
> + "1:"
> + "r1 = %[field_null_seen] ll;"
> + "r2 = 1;"
> + "*(u32 *)(r1 + 0) = r2;"
> + "2:"
> + "r0 = 1;"
> + "exit;"
> + :
> + : __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
> + __imm_const(snd_cwnd_off, offsetof(struct bpf_sock_ops, snd_cwnd)),
> + __imm_addr(field_bug_detected),
> + __imm_addr(field_null_seen)
> + : __clobber_all);
> +}
> +
> +/* SOCK_OPS_GET_SK: different-register, is_fullsock == 0 path. */
> +int diff_reg_bug_detected;
> +int diff_reg_null_seen;
> +
> +SEC("sockops")
> +__naked void sock_ops_get_sk_diff_reg(void)
> +{
> + asm volatile (
> + "r7 = r1;"
> + "r6 = *(u32 *)(r7 + %[is_fullsock_off]);"
> + "r2 = *(u64 *)(r7 + %[sk_off]);"
> + "if r6 != 0 goto 2f;"
> + "if r2 == 0 goto 1f;"
> + "r1 = %[diff_reg_bug_detected] ll;"
> + "r3 = 1;"
> + "*(u32 *)(r1 + 0) = r3;"
> + "goto 2f;"
> + "1:"
> + "r1 = %[diff_reg_null_seen] ll;"
> + "r3 = 1;"
> + "*(u32 *)(r1 + 0) = r3;"
> + "2:"
> + "r0 = 1;"
> + "exit;"
> + :
> + : __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
> + __imm_const(sk_off, offsetof(struct bpf_sock_ops, sk)),
> + __imm_addr(diff_reg_bug_detected),
> + __imm_addr(diff_reg_null_seen)
> + : __clobber_all);
> +}
> +
> +char _license[] SEC("license") = "GPL";
> --
> 2.43.0
>
>
Reviewed-by: Sun Jian <sun.jian.kdev@gmail.com>
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH bpf v2 2/2] selftests/bpf: Add tests for sock_ops ctx access with same src/dst register
2026-04-06 3:12 ` [PATCH bpf v2 2/2] selftests/bpf: Add tests for sock_ops ctx access with same src/dst register Jiayuan Chen
2026-04-06 16:47 ` sun jian
@ 2026-04-06 23:01 ` Martin KaFai Lau
1 sibling, 0 replies; 9+ messages in thread
From: Martin KaFai Lau @ 2026-04-06 23:01 UTC (permalink / raw)
To: Jiayuan Chen
Cc: bpf, Daniel Borkmann, John Fastabend, Stanislav Fomichev,
Alexei Starovoitov, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kernel, netdev, linux-kselftest
On Mon, Apr 06, 2026 at 11:12:51AM +0800, Jiayuan Chen wrote:
> diff --git a/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c b/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c
> new file mode 100644
> index 0000000000000..e086f7abb197e
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c
> @@ -0,0 +1,77 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <test_progs.h>
> +#include "cgroup_helpers.h"
> +#include "network_helpers.h"
> +#include "sock_ops_get_sk.skel.h"
> +
> +/* See progs/sock_ops_get_sk.c for the bug description. */
> +static void run_sock_ops_test(struct sock_ops_get_sk *skel, int cgroup_fd,
skel is not used. sashiko has flagged it also.
pw-bot: cr
> + int prog_fd)
> +{
> + int server_fd, client_fd, err;
> +
> + err = bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_SOCK_OPS, 0);
> + if (!ASSERT_OK(err, "prog_attach"))
> + return;
> +
> + server_fd = start_server(AF_INET, SOCK_STREAM, NULL, 0, 0);
> + if (!ASSERT_GE(server_fd, 0, "start_server"))
> + goto detach;
> +
> + /* Trigger TCP handshake which causes TCP_NEW_SYN_RECV state where
> + * is_fullsock == 0 and is_locked_tcp_sock == 0.
> + */
> + client_fd = connect_to_fd(server_fd, 0);
> + if (!ASSERT_GE(client_fd, 0, "connect_to_fd"))
> + goto close_server;
> +
> + close(client_fd);
> +
> +close_server:
> + close(server_fd);
> +detach:
> + bpf_prog_detach(cgroup_fd, BPF_CGROUP_SOCK_OPS);
> +}
> +
> +void test_sock_ops_get_sk(void)
Add a "ns" in the naming so that test_progs will start a new netns.
Like 'void test_ns_sock_ops_get_sk(void)'.
> +{
> + struct sock_ops_get_sk *skel;
> + int cgroup_fd;
> +
> + cgroup_fd = test__join_cgroup("/sock_ops_get_sk");
> + if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup"))
nit. Use ASSERT_OK_FD check. Same for the other fd checks in this file.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops
2026-04-06 3:12 [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops Jiayuan Chen
2026-04-06 3:12 ` [PATCH bpf v2 2/2] selftests/bpf: Add tests for sock_ops ctx access with same src/dst register Jiayuan Chen
@ 2026-04-06 3:47 ` bot+bpf-ci
2026-04-06 4:54 ` Jiayuan Chen
2026-04-06 16:06 ` sun jian
2026-04-06 23:03 ` Martin KaFai Lau
3 siblings, 1 reply; 9+ messages in thread
From: bot+bpf-ci @ 2026-04-06 3:47 UTC (permalink / raw)
To: jiayuan.chen, bpf
Cc: jiayuan.chen, 2022090917019, dddddd, M202472210, dzm91, emil, ast,
daniel, andrii, martin.lau, eddyz87, memxor, song, yonghong.song,
jolsa, john.fastabend, sdf, davem, edumazet, kuba, pabeni, horms,
shuah, linux-kernel, netdev, linux-kselftest, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 982 bytes --]
> Fixes: 84f44df664e9 ("bpf: sock_ops sk access may stomp registers when dst_reg = src_reg")
This Fixes: tag covers the SOCK_OPS_GET_SK() portion of the fix,
since 84f44df664e9 introduced that macro. However, the identical
bug in SOCK_OPS_GET_FIELD() was introduced by an earlier commit:
fd09af010788 ("bpf: sock_ops ctx access may stomp registers in corner case")
That commit added the dst_reg == src_reg handling to
SOCK_OPS_GET_FIELD() with the same missing zeroing in the
!fullsock path. Should this also carry:
Fixes: fd09af010788 ("bpf: sock_ops ctx access may stomp registers in corner case")
Without it, kernels that have fd09af010788 but not 84f44df664e9
would not receive the SOCK_OPS_GET_FIELD() fix via stable backport.
---
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/24017481706
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops
2026-04-06 3:47 ` [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops bot+bpf-ci
@ 2026-04-06 4:54 ` Jiayuan Chen
2026-04-06 23:09 ` Martin KaFai Lau
0 siblings, 1 reply; 9+ messages in thread
From: Jiayuan Chen @ 2026-04-06 4:54 UTC (permalink / raw)
To: bot+bpf-ci, bpf
Cc: 2022090917019, dddddd, M202472210, dzm91, emil, ast, daniel,
andrii, martin.lau, eddyz87, memxor, song, yonghong.song, jolsa,
john.fastabend, sdf, davem, edumazet, kuba, pabeni, horms, shuah,
linux-kernel, netdev, linux-kselftest, martin.lau, clm,
ihor.solodrai
On 4/6/26 11:47 AM, bot+bpf-ci@kernel.org wrote:
> This Fixes: tag covers the SOCK_OPS_GET_SK() portion of the fix,
> since 84f44df664e9 introduced that macro. However, the identical
> bug in SOCK_OPS_GET_FIELD() was introduced by an earlier commit:
>
> fd09af010788 ("bpf: sock_ops ctx access may stomp registers in corner case")
>
> That commit added the dst_reg == src_reg handling to
> SOCK_OPS_GET_FIELD() with the same missing zeroing in the
> !fullsock path. Should this also carry:
>
> Fixes: fd09af010788 ("bpf: sock_ops ctx access may stomp registers in corner case")
>
> Without it, kernels that have fd09af010788 but not 84f44df664e9
> would not receive the SOCK_OPS_GET_FIELD() fix via stable backport.
>
>
> ---
> 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/24017481706
Well, it's true. However, fd09af010788 and 84f44df664e9 are from the
same patchset (same author, same minute), so any stable branch carrying
one will have both. That's why I only included a single Fixes tag.
But if you prefer carrying both explicitly, I'm happy to add it in next
version.
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops
2026-04-06 4:54 ` Jiayuan Chen
@ 2026-04-06 23:09 ` Martin KaFai Lau
0 siblings, 0 replies; 9+ messages in thread
From: Martin KaFai Lau @ 2026-04-06 23:09 UTC (permalink / raw)
To: Jiayuan Chen
Cc: bot+bpf-ci, bpf, 2022090917019, dddddd, M202472210, dzm91, emil,
ast, daniel, andrii, eddyz87, memxor, song, yonghong.song, jolsa,
john.fastabend, sdf, davem, edumazet, kuba, pabeni, horms, shuah,
linux-kernel, netdev, linux-kselftest, martin.lau, clm,
ihor.solodrai
On Mon, Apr 06, 2026 at 12:54:04PM +0800, Jiayuan Chen wrote:
>
> On 4/6/26 11:47 AM, bot+bpf-ci@kernel.org wrote:
> > This Fixes: tag covers the SOCK_OPS_GET_SK() portion of the fix,
> > since 84f44df664e9 introduced that macro. However, the identical
> > bug in SOCK_OPS_GET_FIELD() was introduced by an earlier commit:
> >
> > fd09af010788 ("bpf: sock_ops ctx access may stomp registers in corner case")
> >
> > That commit added the dst_reg == src_reg handling to
> > SOCK_OPS_GET_FIELD() with the same missing zeroing in the
> > !fullsock path. Should this also carry:
> >
> > Fixes: fd09af010788 ("bpf: sock_ops ctx access may stomp registers in corner case")
> >
> > Without it, kernels that have fd09af010788 but not 84f44df664e9
> > would not receive the SOCK_OPS_GET_FIELD() fix via stable backport.
> >
> >
> > ---
> > 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/24017481706
> Well, it's true. However, fd09af010788 and 84f44df664e9 are from the
> same patchset (same author, same minute), so any stable branch carrying
> one will have both. That's why I only included a single Fixes tag.
>
> But if you prefer carrying both explicitly, I'm happy to add it in next
> version.
Please list both in the Fixes tags so that it is clear both bugs will
be fixed. Thanks.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops
2026-04-06 3:12 [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops Jiayuan Chen
2026-04-06 3:12 ` [PATCH bpf v2 2/2] selftests/bpf: Add tests for sock_ops ctx access with same src/dst register Jiayuan Chen
2026-04-06 3:47 ` [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops bot+bpf-ci
@ 2026-04-06 16:06 ` sun jian
2026-04-06 23:03 ` Martin KaFai Lau
3 siblings, 0 replies; 9+ messages in thread
From: sun jian @ 2026-04-06 16:06 UTC (permalink / raw)
To: Jiayuan Chen
Cc: bpf, Quan Sun, Yinhao Hu, Kaiyan Mei, Dongliang Mu,
Emil Tsalapatis, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
John Fastabend, Stanislav Fomichev, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
linux-kernel, netdev, linux-kselftest
On Mon, Apr 6, 2026 at 11:14 AM Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
>
> When a BPF sock_ops program accesses ctx fields with dst_reg == src_reg,
> the SOCK_OPS_GET_SK() and SOCK_OPS_GET_FIELD() macros fail to zero the
> destination register in the !fullsock / !locked_tcp_sock path.
>
> Both macros borrow a temporary register to check is_fullsock /
> is_locked_tcp_sock when dst_reg == src_reg, because dst_reg holds the
> ctx pointer. When the check is false (e.g., TCP_NEW_SYN_RECV state with
> a request_sock), dst_reg should be zeroed but is not, leaving the stale
> ctx pointer:
>
> - SOCK_OPS_GET_SK: dst_reg retains the ctx pointer, passes NULL checks
> as PTR_TO_SOCKET_OR_NULL, and can be used as a bogus socket pointer,
> leading to stack-out-of-bounds access in helpers like
> bpf_skc_to_tcp6_sock().
>
> - SOCK_OPS_GET_FIELD: dst_reg retains the ctx pointer which the
> verifier believes is a SCALAR_VALUE, leaking a kernel pointer.
>
> Fix both macros by:
> - Changing JMP_A(1) to JMP_A(2) in the fullsock path to skip the
> added instruction.
> - Adding BPF_MOV64_IMM(si->dst_reg, 0) after the temp register
> restore in the !fullsock path, placed after the restore because
> dst_reg == src_reg means we need src_reg intact to read ctx->temp.
>
> Fixes: 84f44df664e9 ("bpf: sock_ops sk access may stomp registers when dst_reg = src_reg")
> Reported-by: Quan Sun <2022090917019@std.uestc.edu.cn>
> Reported-by: Yinhao Hu <dddddd@hust.edu.cn>
> Reported-by: Kaiyan Mei <M202472210@hust.edu.cn>
> Reported-by: Dongliang Mu <dzm91@hust.edu.cn>
> Closes: https://lore.kernel.org/bpf/6fe1243e-149b-4d3b-99c7-fcc9e2f75787@std.uestc.edu.cn/T/#u
> Suggested-by: Emil Tsalapatis <emil@etsalapatis.com>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
> ---
> ---
> net/core/filter.c | 6 ++++--
> 1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/net/core/filter.c b/net/core/filter.c
> index 78b548158fb05..53ce06ed4a88e 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -10581,10 +10581,11 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type,
> si->dst_reg, si->dst_reg, \
> offsetof(OBJ, OBJ_FIELD)); \
> if (si->dst_reg == si->src_reg) { \
> - *insn++ = BPF_JMP_A(1); \
> + *insn++ = BPF_JMP_A(2); \
> *insn++ = BPF_LDX_MEM(BPF_DW, reg, si->src_reg, \
> offsetof(struct bpf_sock_ops_kern, \
> temp)); \
> + *insn++ = BPF_MOV64_IMM(si->dst_reg, 0); \
> } \
> } while (0)
>
> @@ -10618,10 +10619,11 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type,
> si->dst_reg, si->src_reg, \
> offsetof(struct bpf_sock_ops_kern, sk));\
> if (si->dst_reg == si->src_reg) { \
> - *insn++ = BPF_JMP_A(1); \
> + *insn++ = BPF_JMP_A(2); \
> *insn++ = BPF_LDX_MEM(BPF_DW, reg, si->src_reg, \
> offsetof(struct bpf_sock_ops_kern, \
> temp)); \
> + *insn++ = BPF_MOV64_IMM(si->dst_reg, 0); \
> } \
> } while (0)
>
> --
> 2.43.0
>
>
Reviewed-by Sun Jian <sun.jian.kdev@gmail.com>
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops
2026-04-06 3:12 [PATCH bpf v2 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops Jiayuan Chen
` (2 preceding siblings ...)
2026-04-06 16:06 ` sun jian
@ 2026-04-06 23:03 ` Martin KaFai Lau
3 siblings, 0 replies; 9+ messages in thread
From: Martin KaFai Lau @ 2026-04-06 23:03 UTC (permalink / raw)
To: Jiayuan Chen
Cc: bpf, Quan Sun, Yinhao Hu, Kaiyan Mei, Dongliang Mu,
Emil Tsalapatis, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa, John Fastabend,
Stanislav Fomichev, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan, linux-kernel, netdev,
linux-kselftest
On Mon, Apr 06, 2026 at 11:12:50AM +0800, Jiayuan Chen wrote:
> diff --git a/net/core/filter.c b/net/core/filter.c
> index 78b548158fb05..53ce06ed4a88e 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -10581,10 +10581,11 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type,
> si->dst_reg, si->dst_reg, \
> offsetof(OBJ, OBJ_FIELD)); \
> if (si->dst_reg == si->src_reg) { \
> - *insn++ = BPF_JMP_A(1); \
> + *insn++ = BPF_JMP_A(2); \
> *insn++ = BPF_LDX_MEM(BPF_DW, reg, si->src_reg, \
> offsetof(struct bpf_sock_ops_kern, \
> temp)); \
> + *insn++ = BPF_MOV64_IMM(si->dst_reg, 0); \
> } \
> } while (0)
>
> @@ -10618,10 +10619,11 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type,
> si->dst_reg, si->src_reg, \
> offsetof(struct bpf_sock_ops_kern, sk));\
> if (si->dst_reg == si->src_reg) { \
> - *insn++ = BPF_JMP_A(1); \
> + *insn++ = BPF_JMP_A(2); \
> *insn++ = BPF_LDX_MEM(BPF_DW, reg, si->src_reg, \
> offsetof(struct bpf_sock_ops_kern, \
> temp)); \
> + *insn++ = BPF_MOV64_IMM(si->dst_reg, 0); \
Acked-by: Martin KaFai Lau <martin.lau@kernel.org>
^ permalink raw reply [flat|nested] 9+ messages in thread