Subject: Re: [PATCH net 0/2] tcp: symmetric challenge ACK for SEG.ACK > SND.NXT

From: Jakub Kicinski <[email-masked]>
Date: Mon, 20 Apr 2026 08:24:54 -0700

On Mon, 20 Apr 2026 10:54:07 +0800 Jiayuan Chen wrote:
> Commit 354e4aa391ed ("tcp: RFC 5961 5.2 Blind Data Injection Attack
> Mitigation") quotes RFC 5961 Section 5.2 in full, which requires
> that any incoming segment whose ACK value falls outside
> [SND.UNA - MAX.SND.WND, SND.NXT] MUST be discarded and an ACK sent
> back.  Linux currently sends that challenge ACK only on the lower
> edge (SEG.ACK < SND.UNA - MAX.SND.WND); on the symmetric upper edge
> (SEG.ACK > SND.NXT) the segment is silently dropped with
> SKB_DROP_REASON_TCP_ACK_UNSENT_DATA.
> 
> Patch 1 completes the mitigation by emitting a rate-limited challenge
> ACK on that branch, reusing tcp_send_challenge_ack() and honouring
> FLAG_NO_CHALLENGE_ACK for consistency with the lower-edge case.
> 
> Patch 2 adds a packetdrill selftest under
> tools/testing/selftests/net/packetdrill/ that verifies the new
> behaviour.

AI says:

Your patch "tcp: send a challenge ACK on SEG.ACK > SND.NXT" breaks an
existing packetdrill selftest:

  selftests/net/packetdrill/tcp_ts_recent_invalid_ack.pkt

Test output:
  tcp_ts_recent_invalid_ack.pkt:25: error handling packet:
  live packet field tcp_ack_seq: expected: 1001 (0x3e9) vs actual: 1 (0x1)
  script packet:  0.200125 . 1:1(0) ack 1001 <nop,nop,TS val 200 ecr 201>
  actual packet:  0.200119 . 1:1(0) ack 1 win 65535 <nop,nop,TS val 200 ecr 200>
  not ok 1 ipv4
  not ok 2 ipv6
  not ok 3 ipv4-mapped-ipv6

Root cause:

The test `tcp_ts_recent_invalid_ack.pkt` sends a FIN+ACK with ACK=9999
(which exceeds SND.NXT=1) to verify that the kernel does not update ts_recent
from an invalid packet.  Before your patch, this packet was silently dropped.
After your patch, the kernel now emits a challenge ACK (SEQ=1, ACK=1) in
response to the ACK=9999 segment.

The test script does not expect this challenge ACK, so when it subsequently
tries to match the expected "ack 1001" response to the following data segment,
it instead sees the challenge ACK "ack 1", causing a mismatch on all three
address families (ipv4, ipv6, ipv4-mapped-ipv6).

Fix: update `tcp_ts_recent_invalid_ack.pkt` to consume the new challenge ACK
before checking the response to the subsequent data segment.  For example,
add after the bad FIN+ACK line:

  +0 > . 1:1(0) ack 1

so that the challenge ACK is explicitly expected and the rest of the script
proceeds as before.