tcp: refine tcp_sequence() for the FIN exception

Commit 0e24d17bd9 ("tcp: implement RFC 7323 window retraction
receiver requirements") removed the special FIN case that
was added in commit 1e3bb184e9 ("tcp: re-enable acceptance of
FIN packets when RWIN is 0").

If a peer sends a segment containing data and a FIN flag before
it learns about our window retraction and has a buggy TCP stack,
it might place the FIN one byte beyond what it thinks is the
right edge of the window (i.e., max_window_edge + 1).

The data portion (end_seq - th->fin) will end exactly at max_window_edge.
In this case, we will drop the packet if our receive queue is not empty,
even though the data was sent within the window we previously allowed.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Neal Cardwell <ncardwell@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Simon Baatz <gmbnomis@gmail.com>
Link: https://patch.msgid.link/20260608151452.706822-1-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Eric Dumazet 2026-06-08 15:14:52 +00:00 committed by Jakub Kicinski
parent 0969af4b64
commit 91934d4446

View File

@ -4812,18 +4812,20 @@ static enum skb_drop_reason tcp_sequence(const struct sock *sk,
const struct tcphdr *th)
{
const struct tcp_sock *tp = tcp_sk(sk);
u32 seq_limit;
if (before(end_seq, tp->rcv_wup))
return SKB_DROP_REASON_TCP_OLD_SEQUENCE;
if (unlikely(after(end_seq, tp->rcv_nxt + tcp_max_receive_window(tp)))) {
seq_limit = tp->rcv_nxt + tcp_max_receive_window(tp);
if (unlikely(after(end_seq, seq_limit))) {
/* Some stacks are known to handle FIN incorrectly; allow the
* FIN to extend beyond the window and check it in detail later.
*/
if (!after(end_seq - th->fin, tp->rcv_nxt + tcp_receive_window(tp)))
if (!after(end_seq - th->fin, seq_limit))
return SKB_NOT_DROPPED_YET;
if (after(seq, tp->rcv_nxt + tcp_max_receive_window(tp)))
if (after(seq, seq_limit))
return SKB_DROP_REASON_TCP_INVALID_SEQUENCE;
/* Only accept this packet if receive queue is empty. */