Skip to content

Commit 87d96d1

Browse files
edumazetgregkh
authored andcommitted
tcp: better validation of received ack sequences
[ Upstream commit d0e1a1b ] Paul Fiterau Brostean reported : <quote> Linux TCP stack we analyze exhibits behavior that seems odd to me. The scenario is as follows (all packets have empty payloads, no window scaling, rcv/snd window size should not be a factor): TEST HARNESS (CLIENT) LINUX SERVER 1. - LISTEN (server listen, then accepts) 2. - --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED 3. - <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED 4. - --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED 5. - <-- <SEQ=301><ACK=101><CTL=FIN,ACK> <-- FIN WAIT-1 (server opts to close the data connection calling "close" on the connection socket) 6. - --> <SEQ=101><ACK=99999><CTL=FIN,ACK> --> CLOSING (client sends FIN,ACK with not yet sent acknowledgement number) 7. - <-- <SEQ=302><ACK=102><CTL=ACK> <-- CLOSING (ACK is 102 instead of 101, why?) ... (silence from CLIENT) 8. - <-- <SEQ=301><ACK=102><CTL=FIN,ACK> <-- CLOSING (retransmission, again ACK is 102) Now, note that packet 6 while having the expected sequence number, acknowledges something that wasn't sent by the server. So I would expect the packet to maybe prompt an ACK response from the server, and then be ignored. Yet it is not ignored and actually leads to an increase of the acknowledgement number in the server's retransmission of the FIN,ACK packet. The explanation I found is that the FIN in packet 6 was processed, despite the acknowledgement number being unacceptable. Further experiments indeed show that the server processes this FIN, transitioning to CLOSING, then on receiving an ACK for the FIN it had send in packet 5, the server (or better said connection) transitions from CLOSING to TIME_WAIT (as signaled by netstat). </quote> Indeed, tcp_rcv_state_process() calls tcp_ack() but does not exploit the @acceptable status but for TCP_SYN_RECV state. What we want here is to send a challenge ACK, if not in TCP_SYN_RECV state. TCP_FIN_WAIT1 state is not the only state we should fix. Add a FLAG_NO_CHALLENGE_ACK so that tcp_rcv_state_process() can choose to send a challenge ACK and discard the packet instead of wrongly change socket state. With help from Neal Cardwell. Signed-off-by: Eric Dumazet <[email protected]> Reported-by: Paul Fiterau Brostean <[email protected]> Cc: Neal Cardwell <[email protected]> Cc: Yuchung Cheng <[email protected]> Cc: Soheil Hassas Yeganeh <[email protected]> Signed-off-by: David S. Miller <[email protected]> Signed-off-by: Sasha Levin <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent d8857ea commit 87d96d1

File tree

1 file changed

+11
-13
lines changed

1 file changed

+11
-13
lines changed

net/ipv4/tcp_input.c

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ int sysctl_tcp_invalid_ratelimit __read_mostly = HZ/2;
117117
#define FLAG_DSACKING_ACK 0x800 /* SACK blocks contained D-SACK info */
118118
#define FLAG_SACK_RENEGING 0x2000 /* snd_una advanced to a sacked seq */
119119
#define FLAG_UPDATE_TS_RECENT 0x4000 /* tcp_replace_ts_recent() */
120+
#define FLAG_NO_CHALLENGE_ACK 0x8000 /* do not call tcp_send_challenge_ack() */
120121

121122
#define FLAG_ACKED (FLAG_DATA_ACKED|FLAG_SYN_ACKED)
122123
#define FLAG_NOT_DUP (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED)
@@ -3543,7 +3544,8 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
35433544
if (before(ack, prior_snd_una)) {
35443545
/* RFC 5961 5.2 [Blind Data Injection Attack].[Mitigation] */
35453546
if (before(ack, prior_snd_una - tp->max_window)) {
3546-
tcp_send_challenge_ack(sk, skb);
3547+
if (!(flag & FLAG_NO_CHALLENGE_ACK))
3548+
tcp_send_challenge_ack(sk, skb);
35473549
return -1;
35483550
}
35493551
goto old_ack;
@@ -5832,13 +5834,17 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
58325834

58335835
/* step 5: check the ACK field */
58345836
acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
5835-
FLAG_UPDATE_TS_RECENT) > 0;
5837+
FLAG_UPDATE_TS_RECENT |
5838+
FLAG_NO_CHALLENGE_ACK) > 0;
58365839

5840+
if (!acceptable) {
5841+
if (sk->sk_state == TCP_SYN_RECV)
5842+
return 1; /* send one RST */
5843+
tcp_send_challenge_ack(sk, skb);
5844+
goto discard;
5845+
}
58375846
switch (sk->sk_state) {
58385847
case TCP_SYN_RECV:
5839-
if (!acceptable)
5840-
return 1;
5841-
58425848
if (!tp->srtt_us)
58435849
tcp_synack_rtt_meas(sk, req);
58445850

@@ -5907,14 +5913,6 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
59075913
* our SYNACK so stop the SYNACK timer.
59085914
*/
59095915
if (req) {
5910-
/* Return RST if ack_seq is invalid.
5911-
* Note that RFC793 only says to generate a
5912-
* DUPACK for it but for TCP Fast Open it seems
5913-
* better to treat this case like TCP_SYN_RECV
5914-
* above.
5915-
*/
5916-
if (!acceptable)
5917-
return 1;
59185916
/* We no longer need the request sock. */
59195917
reqsk_fastopen_remove(sk, req, false);
59205918
tcp_rearm_rto(sk);

0 commit comments

Comments
 (0)