Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

conntrack: RFC5961 challenge ACK confuse conntrack LAST-ACK transition

In compliance with RFC5961, the network stack send challenge ACK in
response to spurious SYN packets, since commit 0c228e833c88 ("tcp:
Restore RFC5961-compliant behavior for SYN packets").

This pose a problem for netfilter conntrack in state LAST_ACK, because
this challenge ACK is (falsely) seen as ACKing last FIN, causing a
false state transition (into TIME_WAIT).

The challenge ACK is hard to distinguish from real last ACK. Thus,
solution introduce a flag that tracks the potential for seeing a
challenge ACK, in case a SYN packet is let through and current state
is LAST_ACK.

When conntrack transition LAST_ACK to TIME_WAIT happens, this flag is
used for determining if we are expecting a challenge ACK.

Scapy based reproducer script avail here:
https://github.com/netoptimizer/network-testing/blob/master/scapy/tcp_hacks_3WHS_LAST_ACK.py

Fixes: 0c228e833c88 ("tcp: Restore RFC5961-compliant behavior for SYN packets")
Signed-off-by: Jesper Dangaard Brouer <brouer@redhat.com>
Acked-by: Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Jesper Dangaard Brouer and committed by
Pablo Neira Ayuso
b3cad287 595ca588

+35 -3
+3
include/uapi/linux/netfilter/nf_conntrack_tcp.h
··· 42 42 /* The field td_maxack has been set */ 43 43 #define IP_CT_TCP_FLAG_MAXACK_SET 0x20 44 44 45 + /* Marks possibility for expected RFC5961 challenge ACK */ 46 + #define IP_CT_EXP_CHALLENGE_ACK 0x40 47 + 45 48 struct nf_ct_tcp_flags { 46 49 __u8 flags; 47 50 __u8 mask;
+32 -3
net/netfilter/nf_conntrack_proto_tcp.c
··· 202 202 * sES -> sES :-) 203 203 * sFW -> sCW Normal close request answered by ACK. 204 204 * sCW -> sCW 205 - * sLA -> sTW Last ACK detected. 205 + * sLA -> sTW Last ACK detected (RFC5961 challenged) 206 206 * sTW -> sTW Retransmitted last ACK. Remain in the same state. 207 207 * sCL -> sCL 208 208 */ ··· 261 261 * sES -> sES :-) 262 262 * sFW -> sCW Normal close request answered by ACK. 263 263 * sCW -> sCW 264 - * sLA -> sTW Last ACK detected. 264 + * sLA -> sTW Last ACK detected (RFC5961 challenged) 265 265 * sTW -> sTW Retransmitted last ACK. 266 266 * sCL -> sCL 267 267 */ ··· 906 906 1 : ct->proto.tcp.last_win; 907 907 ct->proto.tcp.seen[ct->proto.tcp.last_dir].td_scale = 908 908 ct->proto.tcp.last_wscale; 909 + ct->proto.tcp.last_flags &= ~IP_CT_EXP_CHALLENGE_ACK; 909 910 ct->proto.tcp.seen[ct->proto.tcp.last_dir].flags = 910 911 ct->proto.tcp.last_flags; 911 912 memset(&ct->proto.tcp.seen[dir], 0, ··· 924 923 * may be in sync but we are not. In that case, we annotate 925 924 * the TCP options and let the packet go through. If it is a 926 925 * valid SYN packet, the server will reply with a SYN/ACK, and 927 - * then we'll get in sync. Otherwise, the server ignores it. */ 926 + * then we'll get in sync. Otherwise, the server potentially 927 + * responds with a challenge ACK if implementing RFC5961. 928 + */ 928 929 if (index == TCP_SYN_SET && dir == IP_CT_DIR_ORIGINAL) { 929 930 struct ip_ct_tcp_state seen = {}; 930 931 ··· 942 939 ct->proto.tcp.last_flags |= 943 940 IP_CT_TCP_FLAG_SACK_PERM; 944 941 } 942 + /* Mark the potential for RFC5961 challenge ACK, 943 + * this pose a special problem for LAST_ACK state 944 + * as ACK is intrepretated as ACKing last FIN. 945 + */ 946 + if (old_state == TCP_CONNTRACK_LAST_ACK) 947 + ct->proto.tcp.last_flags |= 948 + IP_CT_EXP_CHALLENGE_ACK; 945 949 } 946 950 spin_unlock_bh(&ct->lock); 947 951 if (LOG_INVALID(net, IPPROTO_TCP)) ··· 980 970 nf_log_packet(net, pf, 0, skb, NULL, NULL, NULL, 981 971 "nf_ct_tcp: invalid state "); 982 972 return -NF_ACCEPT; 973 + case TCP_CONNTRACK_TIME_WAIT: 974 + /* RFC5961 compliance cause stack to send "challenge-ACK" 975 + * e.g. in response to spurious SYNs. Conntrack MUST 976 + * not believe this ACK is acking last FIN. 977 + */ 978 + if (old_state == TCP_CONNTRACK_LAST_ACK && 979 + index == TCP_ACK_SET && 980 + ct->proto.tcp.last_dir != dir && 981 + ct->proto.tcp.last_index == TCP_SYN_SET && 982 + (ct->proto.tcp.last_flags & IP_CT_EXP_CHALLENGE_ACK)) { 983 + /* Detected RFC5961 challenge ACK */ 984 + ct->proto.tcp.last_flags &= ~IP_CT_EXP_CHALLENGE_ACK; 985 + spin_unlock_bh(&ct->lock); 986 + if (LOG_INVALID(net, IPPROTO_TCP)) 987 + nf_log_packet(net, pf, 0, skb, NULL, NULL, NULL, 988 + "nf_ct_tcp: challenge-ACK ignored "); 989 + return NF_ACCEPT; /* Don't change state */ 990 + } 991 + break; 983 992 case TCP_CONNTRACK_CLOSE: 984 993 if (index == TCP_RST_SET 985 994 && (ct->proto.tcp.seen[!dir].flags & IP_CT_TCP_FLAG_MAXACK_SET)