public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/3] bpf: fix sock_ops rtt_min OOB read and related guard issues
@ 2026-04-12  3:03 Werner Kasselman
  2026-04-12  3:03 ` [PATCH v2 1/3] bpf: zero dst_reg on sock_ops field guard failure when dst == src Werner Kasselman
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Werner Kasselman @ 2026-04-12  3:03 UTC (permalink / raw)
  To: Martin KaFai Lau, Alexei Starovoitov, Daniel Borkmann,
	Andrii Nakryiko
  Cc: John Fastabend, Lawrence Brakmo, Eduard Zingerman, Song Liu,
	Yonghong Song, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, bpf@vger.kernel.org, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, Werner Kasselman

Patch 3 fixes an out-of-bounds read in sock_ops_convert_ctx_access()
for the rtt_min context field. It is the only tcp_sock-backed field
that bypasses the is_locked_tcp_sock guard, so on request_sock-backed
sock_ops callbacks the converted BPF load reads past the end of a
tcp_request_sock.

Patches 1 and 2 are groundwork. Patch 1 fixes a pre-existing info
leak in SOCK_OPS_GET_FIELD() and SOCK_OPS_GET_SK() where dst_reg is
left holding the context pointer on the guard-failure branch when
dst_reg == src_reg, instead of being zeroed. Patch 2 extracts
SOCK_OPS_LOAD_TCP_SOCK_FIELD() from SOCK_OPS_GET_FIELD() so the
rtt_min sub-field access in patch 3 can reuse it.

Patches 1 and 3 carry Fixes: tags and Cc: stable. Patch 2 is a pure
refactor.

v1: https://lore.kernel.org/bpf/ (earlier single-patch posting)
  - Inlined the guarded load sequence by hand.
  - Feedback: please factor it through the existing helper instead
    of open-coding 30 lines.

v2:
  - Patch 1 (new): fix latent dst == src info leak in both macros.
  - Patch 2 (new): refactor SOCK_OPS_GET_FIELD().
  - Patch 3: use SOCK_OPS_LOAD_TCP_SOCK_FIELD() for rtt_min and use
    offsetof(struct minmax_sample, v) for the sub-field offset.

Werner Kasselman (3):
  bpf: zero dst_reg on sock_ops field guard failure when dst == src
  bpf: extract SOCK_OPS_LOAD_TCP_SOCK_FIELD from SOCK_OPS_GET_FIELD
  bpf: guard sock_ops rtt_min against non-locked tcp_sock

 net/core/filter.c | 37 +++++++++++++++++++++----------------
 1 file changed, 21 insertions(+), 16 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2 1/3] bpf: zero dst_reg on sock_ops field guard failure when dst == src
  2026-04-12  3:03 [PATCH v2 0/3] bpf: fix sock_ops rtt_min OOB read and related guard issues Werner Kasselman
@ 2026-04-12  3:03 ` Werner Kasselman
  2026-04-12  3:03 ` [PATCH v2 2/3] bpf: extract SOCK_OPS_LOAD_TCP_SOCK_FIELD from SOCK_OPS_GET_FIELD Werner Kasselman
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Werner Kasselman @ 2026-04-12  3:03 UTC (permalink / raw)
  To: Martin KaFai Lau, Alexei Starovoitov, Daniel Borkmann,
	Andrii Nakryiko
  Cc: John Fastabend, Lawrence Brakmo, Eduard Zingerman, Song Liu,
	Yonghong Song, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, bpf@vger.kernel.org, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, Werner Kasselman

When a BPF_PROG_TYPE_SOCK_OPS program reads a tcp_sock-backed context
field (e.g. ctx->snd_ssthresh) or ctx->sk using the same register for
source and destination, SOCK_OPS_GET_FIELD() and SOCK_OPS_GET_SK()
load is_locked_tcp_sock/is_fullsock into a scratch register rather
than into dst_reg. On the guard-failure branch the macro only
restores the scratch register before falling through, leaving
dst_reg holding the unchanged context pointer.

Callers expect dst_reg to read as a scalar 0 when the guard fails.
Instead the BPF program sees a kernel heap address, which the
verifier has already typed as a scalar, giving a narrow kernel
pointer leak. Clang does not emit the dst == src pattern for normal
C ctx field reads, but it is reachable via inline asm and
hand-written BPF.

Add an explicit BPF_MOV64_IMM(dst_reg, 0) on the failure path in
both macros and bump the success-path BPF_JMP_A() to skip over it.

Found via AST-based call-graph analysis using sqry.

Fixes: fd09af010788 ("bpf: sock_ops ctx access may stomp registers in corner case")
Fixes: 84f44df664e9 ("bpf: sock_ops sk access may stomp registers when dst_reg = src_reg")
Cc: stable@vger.kernel.org
Signed-off-by: Werner Kasselman <werner@verivus.com>
---
 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 78b548158fb0..53ce06ed4a88 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


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2 2/3] bpf: extract SOCK_OPS_LOAD_TCP_SOCK_FIELD from SOCK_OPS_GET_FIELD
  2026-04-12  3:03 [PATCH v2 0/3] bpf: fix sock_ops rtt_min OOB read and related guard issues Werner Kasselman
  2026-04-12  3:03 ` [PATCH v2 1/3] bpf: zero dst_reg on sock_ops field guard failure when dst == src Werner Kasselman
@ 2026-04-12  3:03 ` Werner Kasselman
  2026-04-12  3:03 ` [PATCH v2 3/3] bpf: guard sock_ops rtt_min against non-locked tcp_sock Werner Kasselman
  2026-04-12 19:40 ` [PATCH v2 0/3] bpf: fix sock_ops rtt_min OOB read and related guard issues patchwork-bot+netdevbpf
  3 siblings, 0 replies; 5+ messages in thread
From: Werner Kasselman @ 2026-04-12  3:03 UTC (permalink / raw)
  To: Martin KaFai Lau, Alexei Starovoitov, Daniel Borkmann,
	Andrii Nakryiko
  Cc: John Fastabend, Lawrence Brakmo, Eduard Zingerman, Song Liu,
	Yonghong Song, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, bpf@vger.kernel.org, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, Werner Kasselman

Split the tcp_sock field load sequence out of SOCK_OPS_GET_FIELD()
into SOCK_OPS_LOAD_TCP_SOCK_FIELD(FIELD_SIZE, FIELD_OFFSET) so it can
be reused for fields that are not direct struct members.

No functional change.

Signed-off-by: Werner Kasselman <werner@verivus.com>
---
 net/core/filter.c | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/net/core/filter.c b/net/core/filter.c
index 53ce06ed4a88..385fc3e9eb4a 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -10544,12 +10544,10 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type,
 	struct bpf_insn *insn = insn_buf;
 	int off;
 
-/* Helper macro for adding read access to tcp_sock or sock fields. */
-#define SOCK_OPS_GET_FIELD(BPF_FIELD, OBJ_FIELD, OBJ)			      \
+/* Helper macro for adding guarded read access to tcp_sock fields. */
+#define SOCK_OPS_LOAD_TCP_SOCK_FIELD(FIELD_SIZE, FIELD_OFFSET)		      \
 	do {								      \
 		int fullsock_reg = si->dst_reg, reg = BPF_REG_9, jmp = 2;     \
-		BUILD_BUG_ON(sizeof_field(OBJ, OBJ_FIELD) >		      \
-			     sizeof_field(struct bpf_sock_ops, BPF_FIELD));   \
 		if (si->dst_reg == reg || si->src_reg == reg)		      \
 			reg--;						      \
 		if (si->dst_reg == reg || si->src_reg == reg)		      \
@@ -10576,10 +10574,9 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type,
 						struct bpf_sock_ops_kern, sk),\
 				      si->dst_reg, si->src_reg,		      \
 				      offsetof(struct bpf_sock_ops_kern, sk));\
-		*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(OBJ,		      \
-						       OBJ_FIELD),	      \
+		*insn++ = BPF_LDX_MEM(FIELD_SIZE,			      \
 				      si->dst_reg, si->dst_reg,		      \
-				      offsetof(OBJ, OBJ_FIELD));	      \
+				      FIELD_OFFSET);			      \
 		if (si->dst_reg == si->src_reg)	{			      \
 			*insn++ = BPF_JMP_A(2);				      \
 			*insn++ = BPF_LDX_MEM(BPF_DW, reg, si->src_reg,	      \
@@ -10589,6 +10586,14 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type,
 		}							      \
 	} while (0)
 
+#define SOCK_OPS_GET_FIELD(BPF_FIELD, OBJ_FIELD, OBJ)			      \
+	do {								      \
+		BUILD_BUG_ON(sizeof_field(OBJ, OBJ_FIELD) >		      \
+			     sizeof_field(struct bpf_sock_ops, BPF_FIELD));   \
+		SOCK_OPS_LOAD_TCP_SOCK_FIELD(BPF_FIELD_SIZEOF(OBJ, OBJ_FIELD),\
+					     offsetof(OBJ, OBJ_FIELD));       \
+	} while (0)
+
 #define SOCK_OPS_GET_SK()							      \
 	do {								      \
 		int fullsock_reg = si->dst_reg, reg = BPF_REG_9, jmp = 1;     \
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2 3/3] bpf: guard sock_ops rtt_min against non-locked tcp_sock
  2026-04-12  3:03 [PATCH v2 0/3] bpf: fix sock_ops rtt_min OOB read and related guard issues Werner Kasselman
  2026-04-12  3:03 ` [PATCH v2 1/3] bpf: zero dst_reg on sock_ops field guard failure when dst == src Werner Kasselman
  2026-04-12  3:03 ` [PATCH v2 2/3] bpf: extract SOCK_OPS_LOAD_TCP_SOCK_FIELD from SOCK_OPS_GET_FIELD Werner Kasselman
@ 2026-04-12  3:03 ` Werner Kasselman
  2026-04-12 19:40 ` [PATCH v2 0/3] bpf: fix sock_ops rtt_min OOB read and related guard issues patchwork-bot+netdevbpf
  3 siblings, 0 replies; 5+ messages in thread
From: Werner Kasselman @ 2026-04-12  3:03 UTC (permalink / raw)
  To: Martin KaFai Lau, Alexei Starovoitov, Daniel Borkmann,
	Andrii Nakryiko
  Cc: John Fastabend, Lawrence Brakmo, Eduard Zingerman, Song Liu,
	Yonghong Song, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, bpf@vger.kernel.org, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, Werner Kasselman

sock_ops_convert_ctx_access() reads rtt_min without the
is_locked_tcp_sock guard used for every other tcp_sock field. On
request_sock-backed sock_ops callbacks, sk points at a
tcp_request_sock and the converted load reads past the end of the
allocation.

Use SOCK_OPS_LOAD_TCP_SOCK_FIELD() so the load is guarded, and
compute the offset via offsetof(struct minmax_sample, v).

Found via AST-based call-graph analysis using sqry.

Fixes: 44f0e43037d3 ("bpf: Add support for reading sk_state and more")
Cc: stable@vger.kernel.org
Signed-off-by: Werner Kasselman <werner@verivus.com>
---
 net/core/filter.c | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/net/core/filter.c b/net/core/filter.c
index 385fc3e9eb4a..88fa290caeaa 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -10836,14 +10836,12 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type,
 			     sizeof(struct minmax));
 		BUILD_BUG_ON(sizeof(struct minmax) <
 			     sizeof(struct minmax_sample));
+		BUILD_BUG_ON(offsetof(struct tcp_sock, rtt_min) +
+			     offsetof(struct minmax_sample, v) > S16_MAX);
 
-		*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
-						struct bpf_sock_ops_kern, sk),
-				      si->dst_reg, si->src_reg,
-				      offsetof(struct bpf_sock_ops_kern, sk));
-		*insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
-				      offsetof(struct tcp_sock, rtt_min) +
-				      sizeof_field(struct minmax_sample, t));
+		off = offsetof(struct tcp_sock, rtt_min) +
+		      offsetof(struct minmax_sample, v);
+		SOCK_OPS_LOAD_TCP_SOCK_FIELD(BPF_W, off);
 		break;
 
 	case offsetof(struct bpf_sock_ops, bpf_sock_ops_cb_flags):
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v2 0/3] bpf: fix sock_ops rtt_min OOB read and related guard issues
  2026-04-12  3:03 [PATCH v2 0/3] bpf: fix sock_ops rtt_min OOB read and related guard issues Werner Kasselman
                   ` (2 preceding siblings ...)
  2026-04-12  3:03 ` [PATCH v2 3/3] bpf: guard sock_ops rtt_min against non-locked tcp_sock Werner Kasselman
@ 2026-04-12 19:40 ` patchwork-bot+netdevbpf
  3 siblings, 0 replies; 5+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-04-12 19:40 UTC (permalink / raw)
  To: Werner Kasselman
  Cc: martin.lau, ast, daniel, andrii, john.fastabend, brakmo, eddyz87,
	song, yonghong.song, kpsingh, sdf, haoluo, jolsa, davem, edumazet,
	kuba, pabeni, horms, bpf, netdev, linux-kernel

Hello:

This series was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Sun, 12 Apr 2026 03:03:08 +0000 you wrote:
> Patch 3 fixes an out-of-bounds read in sock_ops_convert_ctx_access()
> for the rtt_min context field. It is the only tcp_sock-backed field
> that bypasses the is_locked_tcp_sock guard, so on request_sock-backed
> sock_ops callbacks the converted BPF load reads past the end of a
> tcp_request_sock.
> 
> Patches 1 and 2 are groundwork. Patch 1 fixes a pre-existing info
> leak in SOCK_OPS_GET_FIELD() and SOCK_OPS_GET_SK() where dst_reg is
> left holding the context pointer on the guard-failure branch when
> dst_reg == src_reg, instead of being zeroed. Patch 2 extracts
> SOCK_OPS_LOAD_TCP_SOCK_FIELD() from SOCK_OPS_GET_FIELD() so the
> rtt_min sub-field access in patch 3 can reuse it.
> 
> [...]

Here is the summary with links:
  - [v2,1/3] bpf: zero dst_reg on sock_ops field guard failure when dst == src
    https://git.kernel.org/netdev/net/c/10f86a2a5c91
  - [v2,2/3] bpf: extract SOCK_OPS_LOAD_TCP_SOCK_FIELD from SOCK_OPS_GET_FIELD
    (no matching commit)
  - [v2,3/3] bpf: guard sock_ops rtt_min against non-locked tcp_sock
    (no matching commit)

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-04-12 19:40 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-12  3:03 [PATCH v2 0/3] bpf: fix sock_ops rtt_min OOB read and related guard issues Werner Kasselman
2026-04-12  3:03 ` [PATCH v2 1/3] bpf: zero dst_reg on sock_ops field guard failure when dst == src Werner Kasselman
2026-04-12  3:03 ` [PATCH v2 2/3] bpf: extract SOCK_OPS_LOAD_TCP_SOCK_FIELD from SOCK_OPS_GET_FIELD Werner Kasselman
2026-04-12  3:03 ` [PATCH v2 3/3] bpf: guard sock_ops rtt_min against non-locked tcp_sock Werner Kasselman
2026-04-12 19:40 ` [PATCH v2 0/3] bpf: fix sock_ops rtt_min OOB read and related guard issues patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox