From nobody Sun Jun 14 22:55:30 2026 Received: from out-171.mta0.migadu.com (out-171.mta0.migadu.com [91.218.175.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 97CE734F48A for ; Tue, 7 Apr 2026 02:28:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775528887; cv=none; b=O09gbQN5TxEEjOm+GXqyjGUqlaqfz9yPqYURNqLUR/LBUlQVdYYP5Cfd34JjjnZfeWSXcw1bceP9k1e8fY5p9suMifW8krKm4SJbcXNSx2cOV+HoWwKfoYHgNHTBINnz7b+y5I+rIIalpsrK/W5HmUUk1piwH5HmyWCLhUPGtmA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775528887; c=relaxed/simple; bh=DtWCpX0Y7Wziob7R+T50C6L34Jslab04GZwvfTcdn2Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=K4YU3juT5RZKhG0MIY4XZMCUau04kJiSQXDIriZzrq9hdKJ67Khhvw+0LtXLAX91TlQUi1U3ybOSxnNiKH4CdhTQk5qTMTXjNkR8tLFqGy+9Ujits51NCXS3TZPGpoifKek0Qg1V7RmBqXFOdHPagDFopZXdYz/J8du7hsWmJ3U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=lhLVsCC3; arc=none smtp.client-ip=91.218.175.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="lhLVsCC3" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1775528883; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=BLNnb3jXRMLQyY/e94DI3Ql+r/h7olt/Wmz1vv4kMSM=; b=lhLVsCC38No1vHbljGAihvS2QSvu+rHvjpQntwmCrLCf18+DEbksgjf6C18piYJEMlBCX/ oB/9ShLEKVEqBBvhkVpkROicVw3UdvUkCK2hxiI5JWj2plCsHB+y+aJ6liWjbDZe7pf9El pMJRcmTsNRQ+OJnOAiOpasOdTTk4vks= From: Jiayuan Chen To: bpf@vger.kernel.org Cc: werner@verivus.ai, Jiayuan Chen , Quan Sun <2022090917019@std.uestc.edu.cn>, Yinhao Hu , Kaiyan Mei , Dongliang Mu , Emil Tsalapatis , 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 , Sun Jian , linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-kselftest@vger.kernel.org Subject: [PATCH bpf v3 1/2] bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops Date: Tue, 7 Apr 2026 10:26:27 +0800 Message-ID: <20260407022720.162151-2-jiayuan.chen@linux.dev> In-Reply-To: <20260407022720.162151-1-jiayuan.chen@linux.dev> References: <20260407022720.162151-1-jiayuan.chen@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" When a BPF sock_ops program accesses ctx fields with dst_reg =3D=3D 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 =3D=3D 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 =3D=3D src_reg means we need src_reg intact to read ctx->temp. Fixes: fd09af010788 ("bpf: sock_ops ctx access may stomp registers in corne= r case") Fixes: 84f44df664e9 ("bpf: sock_ops sk access may stomp registers when dst_= reg =3D src_reg") Reported-by: Quan Sun <2022090917019@std.uestc.edu.cn> Reported-by: Yinhao Hu Reported-by: Kaiyan Mei Reported-by: Dongliang Mu Reviewed-by: Emil Tsalapatis Closes: https://lore.kernel.org/bpf/6fe1243e-149b-4d3b-99c7-fcc9e2f75787@st= d.uestc.edu.cn/T/#u Signed-off-by: Jiayuan Chen Acked-by: Martin KaFai Lau --- 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_a= ccess_type type, si->dst_reg, si->dst_reg, \ offsetof(OBJ, OBJ_FIELD)); \ if (si->dst_reg =3D=3D si->src_reg) { \ - *insn++ =3D BPF_JMP_A(1); \ + *insn++ =3D BPF_JMP_A(2); \ *insn++ =3D BPF_LDX_MEM(BPF_DW, reg, si->src_reg, \ offsetof(struct bpf_sock_ops_kern, \ temp)); \ + *insn++ =3D BPF_MOV64_IMM(si->dst_reg, 0); \ } \ } while (0) =20 @@ -10618,10 +10619,11 @@ static u32 sock_ops_convert_ctx_access(enum bpf_a= ccess_type type, si->dst_reg, si->src_reg, \ offsetof(struct bpf_sock_ops_kern, sk));\ if (si->dst_reg =3D=3D si->src_reg) { \ - *insn++ =3D BPF_JMP_A(1); \ + *insn++ =3D BPF_JMP_A(2); \ *insn++ =3D BPF_LDX_MEM(BPF_DW, reg, si->src_reg, \ offsetof(struct bpf_sock_ops_kern, \ temp)); \ + *insn++ =3D BPF_MOV64_IMM(si->dst_reg, 0); \ } \ } while (0) =20 --=20 2.43.0 From nobody Sun Jun 14 22:55:30 2026 Received: from out-185.mta0.migadu.com (out-185.mta0.migadu.com [91.218.175.185]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D9A10351C07 for ; Tue, 7 Apr 2026 02:28:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.185 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775528909; cv=none; b=cVwG4eMexnUZrj+JGgTBk2ULO/AbgduP1gZ/sx4IzbdXcPMh0OGc4NPrNh/v2Hs3yJJ7WecpPp3BNH8dUpTCTPIzoaidrFuB54CkJAzEMJ+OjLxixlNtCc+ACLYdpsATHYQXHz3JDw7ksZdTf41FC/t1tbNmVF6uyMe+Yxp5pFs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775528909; c=relaxed/simple; bh=4DLr8fmQA7w/BSu3doLzsfLae70vyzknOfPGkamv3p8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dBPdkc1ZvHmUPxQvoyahxeRJ7TJh26RyOfkfsXv+0nt2oQfVApTmfFQ922OVbTIOjBHLc/rgGC9zylhHve9BicdBm78Oy+6BBDRDBsEf9oNJJimNlDtJPfDs1ybs200z1DE+tWITO1SG4CpEzzJHMnhnZuA9PtQuZhy4N+tpo+0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=EQTGaOyq; arc=none smtp.client-ip=91.218.175.185 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="EQTGaOyq" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1775528903; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=/UCL1UTPbeTkxlCFboHUyYHOG6gkIs2lk0DovDddN4g=; b=EQTGaOyqBEf+7c42TUTyU/TlGKvJFkMSr2/PR6tYezKeaL4fN5BI4N82f2srRn1IVYCYz6 y+leKHq6BIiJUkeVNBmmvje0i23c4gXE0X8CjKroj6EsMfryxdol3hQHqDKapDXXMcXuEY 99rbNaopjQmikkq2+9p1Uq39EmAxz5s= From: Jiayuan Chen To: bpf@vger.kernel.org Cc: werner@verivus.ai, Jiayuan Chen , Sun Jian , 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@vger.kernel.org, netdev@vger.kernel.org, linux-kselftest@vger.kernel.org Subject: [PATCH bpf v3 2/2] selftests/bpf: Add tests for sock_ops ctx access with same src/dst register Date: Tue, 7 Apr 2026 10:26:28 +0800 Message-ID: <20260407022720.162151-3-jiayuan.chen@linux.dev> In-Reply-To: <20260407022720.162151-1-jiayuan.chen@linux.dev> References: <20260407022720.162151-1-jiayuan.chen@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" Add selftests to verify SOCK_OPS_GET_SK() and SOCK_OPS_GET_FIELD() correctly return NULL/zero when dst_reg =3D=3D src_reg and is_fullsock =3D=3D 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 =3D=3D 0. Reviewed-by: Sun Jian Signed-off-by: Jiayuan Chen Acked-by: Martin KaFai Lau --- .../bpf/prog_tests/sock_ops_get_sk.c | 76 ++++++++++++ .../selftests/bpf/progs/sock_ops_get_sk.c | 117 ++++++++++++++++++ 2 files changed, 193 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/too= ls/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c new file mode 100644 index 0000000000000..343d92c4df30d --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/sock_ops_get_sk.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#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(int cgroup_fd, int prog_fd) +{ + int server_fd, client_fd, err; + + err =3D bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_SOCK_OPS, 0); + if (!ASSERT_OK(err, "prog_attach")) + return; + + server_fd =3D start_server(AF_INET, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_OK_FD(server_fd, "start_server")) + goto detach; + + /* Trigger TCP handshake which causes TCP_NEW_SYN_RECV state where + * is_fullsock =3D=3D 0 and is_locked_tcp_sock =3D=3D 0. + */ + client_fd =3D connect_to_fd(server_fd, 0); + if (!ASSERT_OK_FD(client_fd, "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_ns_sock_ops_get_sk(void) +{ + struct sock_ops_get_sk *skel; + int cgroup_fd; + + cgroup_fd =3D test__join_cgroup("/sock_ops_get_sk"); + if (!ASSERT_OK_FD(cgroup_fd, "join_cgroup")) + return; + + skel =3D 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(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(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(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_detecte= d"); + } + + 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/te= sting/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 +#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 =3D=3D src_reg. + * + * When dst_reg =3D=3D 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 =3D=3D 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 kern= el + * address as a scalar (GET_FIELD). + * + * When dst_reg !=3D src_reg, dst_reg itself is used to load is_fullsock, = so + * the JEQ (dst_reg =3D=3D 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 =3D *(u32 *)(r1 + %[is_fullsock_off]);" + "r1 =3D *(u64 *)(r1 + %[sk_off]);" + "if r7 !=3D 0 goto 2f;" + "if r1 =3D=3D 0 goto 1f;" + "r1 =3D %[bug_detected] ll;" + "r2 =3D 1;" + "*(u32 *)(r1 + 0) =3D r2;" + "goto 2f;" + "1:" + "r1 =3D %[null_seen] ll;" + "r2 =3D 1;" + "*(u32 *)(r1 + 0) =3D r2;" + "2:" + "r0 =3D 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 =3D=3D 0 path. */ +int field_bug_detected; +int field_null_seen; + +SEC("sockops") +__naked void sock_ops_get_field_same_reg(void) +{ + asm volatile ( + "r7 =3D *(u32 *)(r1 + %[is_fullsock_off]);" + "r1 =3D *(u32 *)(r1 + %[snd_cwnd_off]);" + "if r7 !=3D 0 goto 2f;" + "if r1 =3D=3D 0 goto 1f;" + "r1 =3D %[field_bug_detected] ll;" + "r2 =3D 1;" + "*(u32 *)(r1 + 0) =3D r2;" + "goto 2f;" + "1:" + "r1 =3D %[field_null_seen] ll;" + "r2 =3D 1;" + "*(u32 *)(r1 + 0) =3D r2;" + "2:" + "r0 =3D 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 =3D=3D 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 =3D r1;" + "r6 =3D *(u32 *)(r7 + %[is_fullsock_off]);" + "r2 =3D *(u64 *)(r7 + %[sk_off]);" + "if r6 !=3D 0 goto 2f;" + "if r2 =3D=3D 0 goto 1f;" + "r1 =3D %[diff_reg_bug_detected] ll;" + "r3 =3D 1;" + "*(u32 *)(r1 + 0) =3D r3;" + "goto 2f;" + "1:" + "r1 =3D %[diff_reg_null_seen] ll;" + "r3 =3D 1;" + "*(u32 *)(r1 + 0) =3D r3;" + "2:" + "r0 =3D 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") =3D "GPL"; --=20 2.43.0