[PATCH net-next 1/7] tcp: keep rcv_wnd/rcv_ssthresh clamped by window_clamp if no scaling in use

From: Simon Baatz via B4 Relay

Date: Wed Apr 08 2026 - 17:51:03 EST


From: Simon Baatz <gmbnomis@xxxxxxxxx>

When window scaling is not used, we already clamp window_clamp to
65535, but rcv_wnd/rcv_ssthresh can still remain larger than this.

Fix this by capping rcv_wnd to 65535 in the non-scaling paths of both
active and passive opens. Since the advertised window in SYN and
SYN/ACK segments is unscaled, this cannot shrink the window
advertised to the peer.

Also ensure that tcp_select_initial_window() always keeps rcv_wnd less
than or equal to the (scale-limited) window_clamp.

Signed-off-by: Simon Baatz <gmbnomis@xxxxxxxxx>
---
net/ipv4/tcp_input.c | 7 ++++++-
net/ipv4/tcp_minisocks.c | 8 ++++++--
net/ipv4/tcp_output.c | 5 +++--
3 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 7171442c3ed7a..505884dcb7a2b 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -6898,7 +6898,6 @@ static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
*/
WRITE_ONCE(tp->rcv_nxt, TCP_SKB_CB(skb)->seq + 1);
tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;
- tp->rcv_mwnd_seq = tp->rcv_wup + tp->rcv_wnd;

/* RFC1323: The window in SYN & SYN/ACK segments is
* never scaled.
@@ -6909,7 +6908,13 @@ static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0;
WRITE_ONCE(tp->window_clamp,
min(tp->window_clamp, 65535U));
+ tp->rcv_ssthresh = min(tp->rcv_ssthresh, 65535U);
+ /* As the window in the SYN was not scaled,
+ * we did not advertise more than 65535.
+ */
+ tp->rcv_wnd = min(tp->rcv_wnd, 65535U);
}
+ tp->rcv_mwnd_seq = tp->rcv_wup + tp->rcv_wnd;

if (tp->rx_opt.saw_tstamp) {
tp->rx_opt.tstamp_ok = 1;
diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c
index 199f0b579e89c..6496fe3b9e139 100644
--- a/net/ipv4/tcp_minisocks.c
+++ b/net/ipv4/tcp_minisocks.c
@@ -600,9 +600,7 @@ struct sock *tcp_create_openreq_child(const struct sock *sk,
newtp->rx_opt.tstamp_ok = ireq->tstamp_ok;
newtp->rx_opt.sack_ok = ireq->sack_ok;
newtp->window_clamp = req->rsk_window_clamp;
- newtp->rcv_ssthresh = req->rsk_rcv_wnd;
newtp->rcv_wnd = req->rsk_rcv_wnd;
- newtp->rcv_mwnd_seq = newtp->rcv_wup + req->rsk_rcv_wnd;
newtp->rx_opt.wscale_ok = ireq->wscale_ok;
if (newtp->rx_opt.wscale_ok) {
newtp->rx_opt.snd_wscale = ireq->snd_wscale;
@@ -610,7 +608,13 @@ struct sock *tcp_create_openreq_child(const struct sock *sk,
} else {
newtp->rx_opt.snd_wscale = newtp->rx_opt.rcv_wscale = 0;
newtp->window_clamp = min(newtp->window_clamp, 65535U);
+ /* As the window in the SYN/ACK was not scaled,
+ * we did not advertise more than 65535.
+ */
+ newtp->rcv_wnd = min(newtp->rcv_wnd, 65535U);
}
+ newtp->rcv_ssthresh = newtp->rcv_wnd;
+ newtp->rcv_mwnd_seq = newtp->rcv_wup + newtp->rcv_wnd;
newtp->snd_wnd = ntohs(tcp_hdr(skb)->window) << newtp->rx_opt.snd_wscale;
newtp->max_window = newtp->snd_wnd;

diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index 8e99687526a64..bf7a12872acb3 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -269,8 +269,9 @@ void tcp_select_initial_window(const struct sock *sk, int __space, __u32 mss,
0, TCP_MAX_WSCALE);
}
/* Set the clamp no higher than max representable value */
- WRITE_ONCE(*__window_clamp,
- min_t(__u32, U16_MAX << (*rcv_wscale), window_clamp));
+ window_clamp = min_t(u32, U16_MAX << (*rcv_wscale), window_clamp);
+ WRITE_ONCE(*__window_clamp, window_clamp);
+ *rcv_wnd = min(*rcv_wnd, window_clamp);
}

/* Chose a new window to advertise, update state in tcp_sock for the

--
2.53.0