* [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops
@ 2026-04-04 14:09 Jiayuan Chen
2026-04-04 14:09 ` [PATCH bpf v1 2/2] selftests/bpf: Add test for SOCK_OPS_GET_SK with same src/dst register Jiayuan Chen
2026-04-05 23:49 ` [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops Emil Tsalapatis
0 siblings, 2 replies; 7+ messages in thread
From: Jiayuan Chen @ 2026-04-04 14:09 UTC (permalink / raw)
To: bpf
Cc: Jiayuan Chen, Quan Sun, Yinhao Hu, Kaiyan Mei, Dongliang Mu,
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
When a BPF sock_ops program reads ctx->sk with dst_reg == src_reg
(e.g., r1 = *(u64 *)(r1 + offsetof(sk))), the SOCK_OPS_GET_SK() macro
fails to zero the destination register in the is_fullsock == 0 path.
The macro saves/restores a temporary register and checks is_fullsock.
When is_fullsock == 0 (e.g., TCP_NEW_SYN_RECV state with a request_sock),
it should set dst_reg = 0 (NULL) so the verifier's PTR_TO_SOCKET_OR_NULL
type is correct at runtime. Instead, dst_reg retains the original ctx
pointer, which passes subsequent NULL checks and can be used as a bogus
socket pointer, leading to stack-out-of-bounds access in helpers like
bpf_skc_to_tcp6_sock().
Fix 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
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
Apologies for the Easter timing!
---
net/core/filter.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/core/filter.c b/net/core/filter.c
index 78b548158fb05..8fee00e6adef4 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -10618,10 +10618,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
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH bpf v1 2/2] selftests/bpf: Add test for SOCK_OPS_GET_SK with same src/dst register
2026-04-04 14:09 [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops Jiayuan Chen
@ 2026-04-04 14:09 ` Jiayuan Chen
2026-04-06 1:03 ` Emil Tsalapatis
2026-04-05 23:49 ` [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops Emil Tsalapatis
1 sibling, 1 reply; 7+ messages in thread
From: Jiayuan Chen @ 2026-04-04 14:09 UTC (permalink / raw)
To: bpf
Cc: Jiayuan Chen, 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
Add a selftest that verifies SOCK_OPS_GET_SK() correctly returns NULL
when dst_reg == src_reg and is_fullsock == 0.
The BPF program reads ctx->is_fullsock, then loads ctx->sk using the
same register for source and destination. When is_fullsock == 0, sk
must be NULL. The test triggers this path via a TCP handshake (which
causes TCP_NEW_SYN_RECV state) and checks:
- null_seen == 1: the is_fullsock==0 path was hit with correct NULL sk
- bug_detected == 0: sk was never non-NULL when is_fullsock==0
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
.../bpf/prog_tests/sock_ops_get_sk.c | 62 +++++++++++++++++++
.../selftests/bpf/progs/sock_ops_get_sk.c | 44 +++++++++++++
2 files changed, 106 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..9eaf97786c1d9
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c
@@ -0,0 +1,62 @@
+// 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"
+
+/*
+ * Test that reading ctx->sk with dst_reg == src_reg in a sock_ops program
+ * correctly returns NULL when is_fullsock == 0 (TCP_NEW_SYN_RECV state).
+ *
+ * The bug was in SOCK_OPS_GET_SK() macro which failed to zero the destination
+ * register when it was the same as the source register and the socket was not
+ * a full socket. This left the ctx pointer in the register, which then passed
+ * the NULL check and could be used as a socket pointer, causing OOB access.
+ */
+void test_sock_ops_get_sk(void)
+{
+ struct sock_ops_get_sk *skel;
+ int cgroup_fd, server_fd, client_fd;
+ int prog_fd, err;
+
+ 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;
+
+ prog_fd = bpf_program__fd(skel->progs.sock_ops_get_sk_same_reg);
+ err = bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_SOCK_OPS, 0);
+ if (!ASSERT_OK(err, "prog_attach"))
+ goto destroy_skel;
+
+ 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. With the bug, ctx->sk loaded with same src/dst
+ * register would return a stale ctx pointer instead of NULL.
+ */
+ client_fd = connect_to_fd(server_fd, 0);
+ if (!ASSERT_GE(client_fd, 0, "connect_to_fd"))
+ goto close_server;
+
+ close(client_fd);
+
+ /* Verify that the is_fullsock == 0 path was hit and sk was NULL */
+ ASSERT_EQ(skel->bss->null_seen, 1, "null_seen");
+ ASSERT_EQ(skel->bss->bug_detected, 0, "bug_not_detected");
+
+close_server:
+ close(server_fd);
+detach:
+ bpf_prog_detach(cgroup_fd, BPF_CGROUP_SOCK_OPS);
+destroy_skel:
+ 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..4a75614b21eb5
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+/*
+ * Test that SOCK_OPS_GET_SK() correctly returns NULL when dst_reg == src_reg
+ * and is_fullsock == 0 (e.g., during TCP_NEW_SYN_RECV). Before the fix, the
+ * macro failed to zero the destination register, leaving a stale ctx pointer
+ * that bypassed the NULL check.
+ */
+
+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);
+}
+
+char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops
2026-04-04 14:09 [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops Jiayuan Chen
2026-04-04 14:09 ` [PATCH bpf v1 2/2] selftests/bpf: Add test for SOCK_OPS_GET_SK with same src/dst register Jiayuan Chen
@ 2026-04-05 23:49 ` Emil Tsalapatis
2026-04-05 23:54 ` Emil Tsalapatis
1 sibling, 1 reply; 7+ messages in thread
From: Emil Tsalapatis @ 2026-04-05 23:49 UTC (permalink / raw)
To: Jiayuan Chen, bpf
Cc: Quan Sun, Yinhao Hu, Kaiyan Mei, Dongliang Mu, 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 Sat Apr 4, 2026 at 10:09 AM EDT, Jiayuan Chen wrote:
> When a BPF sock_ops program reads ctx->sk with dst_reg == src_reg
> (e.g., r1 = *(u64 *)(r1 + offsetof(sk))), the SOCK_OPS_GET_SK() macro
> fails to zero the destination register in the is_fullsock == 0 path.
>
> The macro saves/restores a temporary register and checks is_fullsock.
> When is_fullsock == 0 (e.g., TCP_NEW_SYN_RECV state with a request_sock),
> it should set dst_reg = 0 (NULL) so the verifier's PTR_TO_SOCKET_OR_NULL
> type is correct at runtime. Instead, dst_reg retains the original ctx
> pointer, which passes subsequent NULL checks and can be used as a bogus
> socket pointer, leading to stack-out-of-bounds access in helpers like
> bpf_skc_to_tcp6_sock().
>
> Fix 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
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
This patch only seems to fix the problem when dst_reg == src_reg.
Why is this not an issue when is_fullsock == 0, but dst_reg != src_reg?
In that case the dst_reg is unmodified by the whole macro but is still
marked as PTR_TO_SOCKET_OR_NULL. Isn't that a problem? Can you add
a test case for is_fullsock == 0 but dst_reg != src_reg in patch 2?
> ---
> Apologies for the Easter timing!
> ---
> net/core/filter.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/net/core/filter.c b/net/core/filter.c
> index 78b548158fb05..8fee00e6adef4 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -10618,10 +10618,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)
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops
2026-04-05 23:49 ` [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops Emil Tsalapatis
@ 2026-04-05 23:54 ` Emil Tsalapatis
2026-04-06 2:58 ` Jiayuan Chen
0 siblings, 1 reply; 7+ messages in thread
From: Emil Tsalapatis @ 2026-04-05 23:54 UTC (permalink / raw)
To: Emil Tsalapatis, Jiayuan Chen, bpf
Cc: Quan Sun, Yinhao Hu, Kaiyan Mei, Dongliang Mu, 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 Sun Apr 5, 2026 at 7:49 PM EDT, Emil Tsalapatis wrote:
> On Sat Apr 4, 2026 at 10:09 AM EDT, Jiayuan Chen wrote:
>> When a BPF sock_ops program reads ctx->sk with dst_reg == src_reg
>> (e.g., r1 = *(u64 *)(r1 + offsetof(sk))), the SOCK_OPS_GET_SK() macro
>> fails to zero the destination register in the is_fullsock == 0 path.
>>
>> The macro saves/restores a temporary register and checks is_fullsock.
>> When is_fullsock == 0 (e.g., TCP_NEW_SYN_RECV state with a request_sock),
>> it should set dst_reg = 0 (NULL) so the verifier's PTR_TO_SOCKET_OR_NULL
>> type is correct at runtime. Instead, dst_reg retains the original ctx
>> pointer, which passes subsequent NULL checks and can be used as a bogus
>> socket pointer, leading to stack-out-of-bounds access in helpers like
>> bpf_skc_to_tcp6_sock().
>>
>> Fix 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
>> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
>
> This patch only seems to fix the problem when dst_reg == src_reg.
> Why is this not an issue when is_fullsock == 0, but dst_reg != src_reg?
> In that case the dst_reg is unmodified by the whole macro but is still
> marked as PTR_TO_SOCKET_OR_NULL. Isn't that a problem? Can you add
> a test case for is_fullsock == 0 but dst_reg != src_reg in patch 2?
Sorry for the double post, but also check sashiko.dev:
SOSK_OPTS_GET_FIELD seems to have the same issue as the
SOCK_OPTS_GET_SK. Can you add the same fix to it?
>
>> ---
>> Apologies for the Easter timing!
>> ---
>> net/core/filter.c | 3 ++-
>> 1 file changed, 2 insertions(+), 1 deletion(-)
>>
>> diff --git a/net/core/filter.c b/net/core/filter.c
>> index 78b548158fb05..8fee00e6adef4 100644
>> --- a/net/core/filter.c
>> +++ b/net/core/filter.c
>> @@ -10618,10 +10618,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)
>>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf v1 2/2] selftests/bpf: Add test for SOCK_OPS_GET_SK with same src/dst register
2026-04-04 14:09 ` [PATCH bpf v1 2/2] selftests/bpf: Add test for SOCK_OPS_GET_SK with same src/dst register Jiayuan Chen
@ 2026-04-06 1:03 ` Emil Tsalapatis
0 siblings, 0 replies; 7+ messages in thread
From: Emil Tsalapatis @ 2026-04-06 1:03 UTC (permalink / raw)
To: Jiayuan Chen, bpf
Cc: 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 Sat Apr 4, 2026 at 10:09 AM EDT, Jiayuan Chen wrote:
> Add a selftest that verifies SOCK_OPS_GET_SK() correctly returns NULL
> when dst_reg == src_reg and is_fullsock == 0.
>
> The BPF program reads ctx->is_fullsock, then loads ctx->sk using the
> same register for source and destination. When is_fullsock == 0, sk
> must be NULL. The test triggers this path via a TCP handshake (which
> causes TCP_NEW_SYN_RECV state) and checks:
> - null_seen == 1: the is_fullsock==0 path was hit with correct NULL sk
> - bug_detected == 0: sk was never non-NULL when is_fullsock==0
Can you add a test for dst_reg != src_reg, too?
>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
> ---
> .../bpf/prog_tests/sock_ops_get_sk.c | 62 +++++++++++++++++++
> .../selftests/bpf/progs/sock_ops_get_sk.c | 44 +++++++++++++
> 2 files changed, 106 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..9eaf97786c1d9
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c
> @@ -0,0 +1,62 @@
> +// 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"
> +
> +/*
> + * Test that reading ctx->sk with dst_reg == src_reg in a sock_ops program
> + * correctly returns NULL when is_fullsock == 0 (TCP_NEW_SYN_RECV state).
> + *
> + * The bug was in SOCK_OPS_GET_SK() macro which failed to zero the destination
> + * register when it was the same as the source register and the socket was not
> + * a full socket. This left the ctx pointer in the register, which then passed
> + * the NULL check and could be used as a socket pointer, causing OOB access.
The reader of this file will not have context on the original bug, and
the dst_reg == src_reg condition is more obvious in the BPF file. Can
you move it there?
> + */
> +void test_sock_ops_get_sk(void)
> +{
> + struct sock_ops_get_sk *skel;
> + int cgroup_fd, server_fd, client_fd;
> + int prog_fd, err;
> +
> + 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;
> +
> + prog_fd = bpf_program__fd(skel->progs.sock_ops_get_sk_same_reg);
> + err = bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_SOCK_OPS, 0);
> + if (!ASSERT_OK(err, "prog_attach"))
> + goto destroy_skel;
> +
> + 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. With the bug, ctx->sk loaded with same src/dst
> + * register would return a stale ctx pointer instead of NULL.
> + */
> + client_fd = connect_to_fd(server_fd, 0);
> + if (!ASSERT_GE(client_fd, 0, "connect_to_fd"))
> + goto close_server;
> +
> + close(client_fd);
> +
> + /* Verify that the is_fullsock == 0 path was hit and sk was NULL */
> + ASSERT_EQ(skel->bss->null_seen, 1, "null_seen");
> + ASSERT_EQ(skel->bss->bug_detected, 0, "bug_not_detected");
> +
> +close_server:
> + close(server_fd);
> +detach:
> + bpf_prog_detach(cgroup_fd, BPF_CGROUP_SOCK_OPS);
> +destroy_skel:
> + 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..4a75614b21eb5
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
> @@ -0,0 +1,44 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include "vmlinux.h"
> +#include <bpf/bpf_helpers.h>
> +#include "bpf_misc.h"
> +
> +/*
> + * Test that SOCK_OPS_GET_SK() correctly returns NULL when dst_reg == src_reg
> + * and is_fullsock == 0 (e.g., during TCP_NEW_SYN_RECV). Before the fix, the
Again, writing "the fix" here is vague.
> + * macro failed to zero the destination register, leaving a stale ctx pointer
> + * that bypassed the NULL check.
> + */
> +
> +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);
> +}
> +
> +char _license[] SEC("license") = "GPL";
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops
2026-04-05 23:54 ` Emil Tsalapatis
@ 2026-04-06 2:58 ` Jiayuan Chen
2026-04-06 3:13 ` Emil Tsalapatis
0 siblings, 1 reply; 7+ messages in thread
From: Jiayuan Chen @ 2026-04-06 2:58 UTC (permalink / raw)
To: Emil Tsalapatis, bpf
Cc: Quan Sun, Yinhao Hu, Kaiyan Mei, Dongliang Mu, 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 4/6/26 7:54 AM, Emil Tsalapatis wrote:
> On Sun Apr 5, 2026 at 7:49 PM EDT, Emil Tsalapatis wrote:
>> On Sat Apr 4, 2026 at 10:09 AM EDT, Jiayuan Chen wrote:
>>> When a BPF sock_ops program reads ctx->sk with dst_reg == src_reg
>>> (e.g., r1 = *(u64 *)(r1 + offsetof(sk))), the SOCK_OPS_GET_SK() macro
>>> fails to zero the destination register in the is_fullsock == 0 path.
>>>
>>> The macro saves/restores a temporary register and checks is_fullsock.
>>> When is_fullsock == 0 (e.g., TCP_NEW_SYN_RECV state with a request_sock),
>>> it should set dst_reg = 0 (NULL) so the verifier's PTR_TO_SOCKET_OR_NULL
>>> type is correct at runtime. Instead, dst_reg retains the original ctx
>>> pointer, which passes subsequent NULL checks and can be used as a bogus
>>> socket pointer, leading to stack-out-of-bounds access in helpers like
>>> bpf_skc_to_tcp6_sock().
>>>
>>> Fix 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
>>> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
>> This patch only seems to fix the problem when dst_reg == src_reg.
>> Why is this not an issue when is_fullsock == 0, but dst_reg != src_reg?
>> In that case the dst_reg is unmodified by the whole macro but is still
>> marked as PTR_TO_SOCKET_OR_NULL. Isn't that a problem? Can you add
>> a test case for is_fullsock == 0 but dst_reg != src_reg in patch 2?
> Sorry for the double post, but also check sashiko.dev:
> SOSK_OPTS_GET_FIELD seems to have the same issue as the
> SOCK_OPTS_GET_SK. Can you add the same fix to it?
>
Thanks for the review!
The AI reviewer's observation about SOCK_OPS_GET_FIELD() is correct —
it has the same bug when dst_reg == src_reg and is_locked_tcp_sock == 0.
I've folded that fix into patch 1 in v2.
Regarding dst_reg != src_reg: this case is actually safe. When
dst_reg != src_reg, fullsock_reg is dst_reg itself, and the generated
sequence is:
LDX_MEM dst_reg = is_fullsock
JEQ dst_reg == 0, +jmp
LDX_MEM dst_reg = sk
The JEQ only branches when dst_reg == 0, so dst_reg is naturally
zeroed on that path — no extra MOV_IMM needed. The same-register bug
exists precisely because dst_reg == src_reg forces the macro to borrow
a temporary register for the is_fullsock check, leaving dst_reg (the
ctx pointer) untouched.
I will add a get_sk_diff_reg subtest in v2.
The other suggestions (moving the detailed comment to the BPF program
file, avoiding vague "the fix" wording) are good points — addressed
in v2 as well.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops
2026-04-06 2:58 ` Jiayuan Chen
@ 2026-04-06 3:13 ` Emil Tsalapatis
0 siblings, 0 replies; 7+ messages in thread
From: Emil Tsalapatis @ 2026-04-06 3:13 UTC (permalink / raw)
To: Jiayuan Chen, Emil Tsalapatis, bpf
Cc: Quan Sun, Yinhao Hu, Kaiyan Mei, Dongliang Mu, 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 Sun Apr 5, 2026 at 10:58 PM EDT, Jiayuan Chen wrote:
>
> On 4/6/26 7:54 AM, Emil Tsalapatis wrote:
>> On Sun Apr 5, 2026 at 7:49 PM EDT, Emil Tsalapatis wrote:
>>> On Sat Apr 4, 2026 at 10:09 AM EDT, Jiayuan Chen wrote:
>>>> When a BPF sock_ops program reads ctx->sk with dst_reg == src_reg
>>>> (e.g., r1 = *(u64 *)(r1 + offsetof(sk))), the SOCK_OPS_GET_SK() macro
>>>> fails to zero the destination register in the is_fullsock == 0 path.
>>>>
>>>> The macro saves/restores a temporary register and checks is_fullsock.
>>>> When is_fullsock == 0 (e.g., TCP_NEW_SYN_RECV state with a request_sock),
>>>> it should set dst_reg = 0 (NULL) so the verifier's PTR_TO_SOCKET_OR_NULL
>>>> type is correct at runtime. Instead, dst_reg retains the original ctx
>>>> pointer, which passes subsequent NULL checks and can be used as a bogus
>>>> socket pointer, leading to stack-out-of-bounds access in helpers like
>>>> bpf_skc_to_tcp6_sock().
>>>>
>>>> Fix 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
>>>> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
>>> This patch only seems to fix the problem when dst_reg == src_reg.
>>> Why is this not an issue when is_fullsock == 0, but dst_reg != src_reg?
>>> In that case the dst_reg is unmodified by the whole macro but is still
>>> marked as PTR_TO_SOCKET_OR_NULL. Isn't that a problem? Can you add
>>> a test case for is_fullsock == 0 but dst_reg != src_reg in patch 2?
>> Sorry for the double post, but also check sashiko.dev:
>> SOSK_OPTS_GET_FIELD seems to have the same issue as the
>> SOCK_OPTS_GET_SK. Can you add the same fix to it?
>>
>
> Thanks for the review!
>
> The AI reviewer's observation about SOCK_OPS_GET_FIELD() is correct —
> it has the same bug when dst_reg == src_reg and is_locked_tcp_sock == 0.
> I've folded that fix into patch 1 in v2.
>
> Regarding dst_reg != src_reg: this case is actually safe. When
> dst_reg != src_reg, fullsock_reg is dst_reg itself, and the generated
> sequence is:
>
> LDX_MEM dst_reg = is_fullsock
> JEQ dst_reg == 0, +jmp
> LDX_MEM dst_reg = sk
>
Yes, I missed the dst is the is_fullsock_reg assignment. v1 looks
correct then.
> The JEQ only branches when dst_reg == 0, so dst_reg is naturally
> zeroed on that path — no extra MOV_IMM needed. The same-register bug
> exists precisely because dst_reg == src_reg forces the macro to borrow
> a temporary register for the is_fullsock check, leaving dst_reg (the
> ctx pointer) untouched.
>
> I will add a get_sk_diff_reg subtest in v2.
>
> The other suggestions (moving the detailed comment to the BPF program
> file, avoiding vague "the fix" wording) are good points — addressed
> in v2 as well.
With the changes, feel free to add to both patches:
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-04-06 3:13 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-04 14:09 [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops Jiayuan Chen
2026-04-04 14:09 ` [PATCH bpf v1 2/2] selftests/bpf: Add test for SOCK_OPS_GET_SK with same src/dst register Jiayuan Chen
2026-04-06 1:03 ` Emil Tsalapatis
2026-04-05 23:49 ` [PATCH bpf v1 1/2] bpf: Fix SOCK_OPS_GET_SK same-register OOB read in sock_ops Emil Tsalapatis
2026-04-05 23:54 ` Emil Tsalapatis
2026-04-06 2:58 ` Jiayuan Chen
2026-04-06 3:13 ` Emil Tsalapatis
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox