On 25/05/2025 08:12, Alexander Bluhm wrote: > On Mon, May 19, 2025 at 12:24:59PM +0000, Hu Ruinan wrote: >>> Description: >> When a TCP connection is in the ESTABLISHED state, OpenBSD's TCP >> implementation incorrectly accepts a packet with an acknowledgment >> number (ACK) smaller than expected and containing the FIN+ACK flags. >> In my test, I sent such a FIN+ACK packet with a smaller ACK value and >> OpenBSD accepted it, responded with FIN+ACK. > > I can confirm this behavior. > >> According to RFC 9293, if the ACK is a duplicate, it can be ignored. If the >> ACK acks something not yet sent, then send an ACK, drop the segment, and >> return. > > I guess you refer to this section of the RFC. Qouting the relevant > path and conditions. > > 3. Functional Specification > 3.10. Event Processing > 3.10.7. SEGMENT ARRIVES > 3.10.7.4. Other States > Fifth, check the ACK field: > - if the ACK bit is on, > > o RFC 5961 [9], Section 5 describes a potential blind data > injection attack, and mitigation that implementations MAY > choose to include (MAY-12). TCP stacks that implement RFC > 5961 MUST add an input check that the ACK value is > acceptable only if it is in the range of ((SND.UNA - > MAX.SND.WND) =< SEG.ACK =< SND.NXT). All incoming segments > whose ACK value doesn't satisfy the above condition MUST be > discarded and an ACK sent back. The new state variable > MAX.SND.WND is defined as the largest window that the local > sender has ever received from its peer (subject to window > scaling) or may be hard-coded to a maximum permissible > window value. When the ACK value is acceptable, the per- > state processing below applies: > > o ESTABLISHED STATE > > + If SND.UNA < SEG.ACK =< SND.NXT, then set SND.UNA <- > SEG.ACK. Any segments on the retransmission queue that > are thereby entirely acknowledged are removed. Users > should receive positive acknowledgments for buffers that > have been SENT and fully acknowledged (i.e., SEND buffer > should be returned with "ok" response). If the ACK is a > duplicate (SEG.ACK =< SND.UNA), it can be ignored. If > the ACK acks something not yet sent (SEG.ACK > SND.NXT), > then send an ACK, drop the segment, and return. > > Eighth, check the FIN bit: > > The question is, whether our behavior is illegal. > > Before we do "Eighth, check the FIN bit" we must comply to "Fifth, > check the ACK field". > > There the hard requirement to drop the ACK packet is ((SND.UNA - > MAX.SND.WND) =< SEG.ACK =< SND.NXT). In your script you use > ack=ack_ack-5. As 5 is less than MAX.SND.WND the condition is not > met. > > The part (SEG.ACK > SND.NXT) does not apply, as your ack number is > too small. The text "not yet sent" refers to some potential data > in the future, not never existing data in the past. > > So the case you refer to is (SEG.ACK =< SND.UNA). It says "it can > be ignored". Note that "can" is not "MUST". And what does "it" > mean? The acknowledgement or the whole packet that carries the > additional FIN? > > So while our behavior is questionable, I think it is not illegal. > Usually I am very carefull here, as every change has a high risk. > Interoperability may suffer or even DoS is possible when sending > too many ACK packets. And just changing some line in tcp_input() > can have unexpected consequences for other corner cases. > > Do you see a clear violation of the RFC? > Do you see a potential DoS by not being strict enough? > What would be the benefit if we would ignore your ACK packet? > > Thanks for testing our TCP stack and searching for corner cases. > > bluhm
Thank you for your detailed explanation. I've conducted further testing about the threshold of this behavior. When I send `ack=ack_ack-67035`, I receive the FIN+ACK from OpenBSD. But when I send `ack=ack_ack-67036`, OpenBSD send nothing. I also modified the window size in my script, and the behavior remains the same. Is the -67035 also larger than SND.UNA-MAX.SND.WND? And there is no ACK packets send back when the ack number is too small, is this behavior as expected? Trace of `ack=ack_ack-67035`: 10:52:02.851556 enp45s0 Out IP 192.168.31.251.49825 > 192.168.31.131.12347: Flags [S], seq 1505569531, win 64224, options [mss 1460,nop,eol], length 0 10:52:02.851819 enp45s0 In IP 192.168.31.131.12347 > 192.168.31.251.49825: Flags [S.], seq 2019486834, ack 1505569532, win 16384, options [mss 1460], length 0 10:52:02.878657 enp45s0 Out IP 192.168.31.251.49825 > 192.168.31.131.12347: Flags [.], ack 1, win 502, length 0 10:52:02.897896 enp45s0 Out IP 192.168.31.251.49825 > 192.168.31.131.12347: Flags [F.], seq 1, ack 4294900262, win 502, length 0 10:52:02.898131 enp45s0 In IP 192.168.31.131.12347 > 192.168.31.251.49825: Flags [.], ack 2, win 17520, length 0 10:52:02.898644 enp45s0 In IP 192.168.31.131.12347 > 192.168.31.251.49825: Flags [F.], seq 1, ack 2, win 17520, length 0 10:52:03.225831 enp45s0 Out IP 192.168.31.251.49825 > 192.168.31.131.12347: Flags [.], ack 2, win 502, options [nop,nop,eol], length 0 Trace of `ack=ack_ack-67036`: 10:52:20.772672 enp45s0 Out IP 192.168.31.251.47692 > 192.168.31.131.12347: Flags [S], seq 1033802970, win 64224, options [mss 1460,nop,eol], length 0 10:52:20.772934 enp45s0 In IP 192.168.31.131.12347 > 192.168.31.251.47692: Flags [S.], seq 621784845, ack 1033802971, win 16384, options [mss 1460], length 0 10:52:20.790977 enp45s0 Out IP 192.168.31.251.47692 > 192.168.31.131.12347: Flags [.], ack 1, win 502, length 0 10:52:20.832912 enp45s0 Out IP 192.168.31.251.47692 > 192.168.31.131.12347: Flags [F.], seq 1, ack 4294900261, win 502, length 0 10:52:21.166072 enp45s0 Out IP 192.168.31.251.47692 > 192.168.31.131.12347: Flags [.], ack 2, win 502, options [nop,nop,eol], length 0 10:52:21.166361 enp45s0 In IP 192.168.31.131.12347 > 192.168.31.251.47692: Flags [.], ack 1, win 17520, length 0 Ruinan
