[Bug 296594] TCP: RFC 5961 sends a challenge ACK instead of resetting when a valid RST arrives with SEG.SEQ == RCV.NXT and the receiver has delayed-ACKed data, leaving the connection half-open
[email protected] Fri, 10 Jul 2026 09:33:10 +0000
| Newsgroups | gmane.os.freebsd.devel.net |
|---|---|
| Message-ID | <[email protected]/bugzilla/> |
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=296594 --- Comment #7 from Thomas Grainger <[email protected]> --- Updating the patch with a third change. The RFC 5961 RST handling has three sub-cases where a valid RST at rcv_nxt fails to reset under a delayed ACK (rcv_nxt > last_ack_sent), not two: 1. in-window, rcv_wnd large: the inner exact-match test accepts only last_ack_sent, so the RST draws a challenge ACK instead of resetting; 2. rcv_wnd shrunk below the delayed-ACK gap: the RST at rcv_nxt falls beyond last_ack_sent + rcv_wnd and is silently dropped by the outer window check; 3. rcv_wnd == 0: with the outer edge fixed to rcv_nxt + rcv_wnd, arm A can never admit a RST at rcv_nxt when the window is closed -- SEQ_LT(rcv_nxt, rcv_nxt + 0) is false -- so acceptance falls to the zero-window arm, which also tests last_ack_sent only and rejects it. The updated patch (freebsd-current-tcp-rst-rcvnxt.patch, against main) makes all three changes -- accept rcv_nxt in the inner reset test, anchor the outer window edge on rcv_nxt + rcv_wnd, and accept rcv_nxt in the zero-window arm: if ((SEQ_GEQ(th->th_seq, tp->last_ack_sent) && SEQ_LT(th->th_seq, tp->rcv_nxt + tp->rcv_wnd)) || (tp->rcv_wnd == 0 && (tp->last_ack_sent == th->th_seq || tp->rcv_nxt == th->th_seq))) { KASSERT(tp->t_state != TCPS_SYN_SENT, ...); if (V_tcp_insecure_rst || tp->last_ack_sent == th->th_seq || tp->rcv_nxt == th->th_seq) { None of this weakens the anti-spoofing intent: an off-path attacker must still guess rcv_nxt (or last_ack_sent) exactly; in-window but non-exact RSTs still draw a challenge ACK. Validation. Cases 1 and 2 arise organically -- changes 1 + 2 already take a stock GENERIC kernel from 15/1,919,209 hangs to 0/31,651,215 connections with c/repro.c under QEMU/KVM. Case 3 is not reached by a conforming loopback flow (a sender will not push data past a zero window), which is why the organic run does not exercise it, but it is directly reachable with crafted packets. I built two 15.1-RELEASE GENERIC kernels differing ONLY by change 3 -- RSTBOTH (changes 1 + 2) and RSTARMB (changes 1 + 2 + 3), uname -i verified -- and ran three deterministic packetdrill tests on each: test stock RSTBOTH(1+2) RSTARMB(1+2+3) rst-rcvnxt-challenge-ack.pkt (case 1) RED GREEN GREEN rst-rcvnxt-window-drop.pkt (case 2) RED GREEN GREEN rst-rcvnxt-zero-window.pkt (case 3) RED RED GREEN RED = read() returns EAGAIN (RST not honored); GREEN = read() returns ECONNRESET. Only the zero-window test flips RED->GREEN across the single-line change 3, with the other two tests GREEN on both kernels as controls -- so the zero-window gap is real and change 3 is what closes it. -- You are receiving this mail because: You are on the CC list for the bug.