Skip to content

Commit d0e1a1b

Browse files
Eric Dumazetdavem330
authored andcommitted
tcp: better validation of received ack sequences
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]>
1 parent 367a8ce commit d0e1a1b

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
@@ -112,6 +112,7 @@ int sysctl_tcp_invalid_ratelimit __read_mostly = HZ/2;
112112
#define FLAG_DSACKING_ACK 0x800 /* SACK blocks contained D-SACK info */
113113
#define FLAG_SACK_RENEGING 0x2000 /* snd_una advanced to a sacked seq */
114114
#define FLAG_UPDATE_TS_RECENT 0x4000 /* tcp_replace_ts_recent() */
115+
#define FLAG_NO_CHALLENGE_ACK 0x8000 /* do not call tcp_send_challenge_ack() */
115116

116117
#define FLAG_ACKED (FLAG_DATA_ACKED|FLAG_SYN_ACKED)
117118
#define FLAG_NOT_DUP (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED)
@@ -3568,7 +3569,8 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
35683569
if (before(ack, prior_snd_una)) {
35693570
/* RFC 5961 5.2 [Blind Data Injection Attack].[Mitigation] */
35703571
if (before(ack, prior_snd_una - tp->max_window)) {
3571-
tcp_send_challenge_ack(sk, skb);
3572+
if (!(flag & FLAG_NO_CHALLENGE_ACK))
3573+
tcp_send_challenge_ack(sk, skb);
35723574
return -1;
35733575
}
35743576
goto old_ack;
@@ -5951,13 +5953,17 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
59515953

59525954
/* step 5: check the ACK field */
59535955
acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
5954-
FLAG_UPDATE_TS_RECENT) > 0;
5956+
FLAG_UPDATE_TS_RECENT |
5957+
FLAG_NO_CHALLENGE_ACK) > 0;
59555958

5959+
if (!acceptable) {
5960+
if (sk->sk_state == TCP_SYN_RECV)
5961+
return 1; /* send one RST */
5962+
tcp_send_challenge_ack(sk, skb);
5963+
goto discard;
5964+
}
59565965
switch (sk->sk_state) {
59575966
case TCP_SYN_RECV:
5958-
if (!acceptable)
5959-
return 1;
5960-
59615967
if (!tp->srtt_us)
59625968
tcp_synack_rtt_meas(sk, req);
59635969

@@ -6026,14 +6032,6 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
60266032
* our SYNACK so stop the SYNACK timer.
60276033
*/
60286034
if (req) {
6029-
/* Return RST if ack_seq is invalid.
6030-
* Note that RFC793 only says to generate a
6031-
* DUPACK for it but for TCP Fast Open it seems
6032-
* better to treat this case like TCP_SYN_RECV
6033-
* above.
6034-
*/
6035-
if (!acceptable)
6036-
return 1;
60376035
/* We no longer need the request sock. */
60386036
reqsk_fastopen_remove(sk, req, false);
60396037
tcp_rearm_rto(sk);

0 commit comments

Comments
 (0)