diff options
author | Jesper Dangaard Brouer <brouer@redhat.com> | 2015-05-07 08:54:16 -0400 |
---|---|---|
committer | Pablo Neira Ayuso <pablo@netfilter.org> | 2015-05-15 14:50:56 -0400 |
commit | b3cad287d13b5f6695c6b4aab72969cd64bf0171 (patch) | |
tree | 35cb16bad9e76c3cf30e01e71b4ea5031de0a564 /net | |
parent | 595ca5880b37d4aa3c292d75531577175d36b225 (diff) |
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>
Diffstat (limited to 'net')
-rw-r--r-- | net/netfilter/nf_conntrack_proto_tcp.c | 35 |
1 files changed, 32 insertions, 3 deletions
diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c index 5caa0c41bf26..70383de72054 100644 --- a/net/netfilter/nf_conntrack_proto_tcp.c +++ b/net/netfilter/nf_conntrack_proto_tcp.c | |||
@@ -202,7 +202,7 @@ static const u8 tcp_conntracks[2][6][TCP_CONNTRACK_MAX] = { | |||
202 | * sES -> sES :-) | 202 | * sES -> sES :-) |
203 | * sFW -> sCW Normal close request answered by ACK. | 203 | * sFW -> sCW Normal close request answered by ACK. |
204 | * sCW -> sCW | 204 | * sCW -> sCW |
205 | * sLA -> sTW Last ACK detected. | 205 | * sLA -> sTW Last ACK detected (RFC5961 challenged) |
206 | * sTW -> sTW Retransmitted last ACK. Remain in the same state. | 206 | * sTW -> sTW Retransmitted last ACK. Remain in the same state. |
207 | * sCL -> sCL | 207 | * sCL -> sCL |
208 | */ | 208 | */ |
@@ -261,7 +261,7 @@ static const u8 tcp_conntracks[2][6][TCP_CONNTRACK_MAX] = { | |||
261 | * sES -> sES :-) | 261 | * sES -> sES :-) |
262 | * sFW -> sCW Normal close request answered by ACK. | 262 | * sFW -> sCW Normal close request answered by ACK. |
263 | * sCW -> sCW | 263 | * sCW -> sCW |
264 | * sLA -> sTW Last ACK detected. | 264 | * sLA -> sTW Last ACK detected (RFC5961 challenged) |
265 | * sTW -> sTW Retransmitted last ACK. | 265 | * sTW -> sTW Retransmitted last ACK. |
266 | * sCL -> sCL | 266 | * sCL -> sCL |
267 | */ | 267 | */ |
@@ -906,6 +906,7 @@ static int tcp_packet(struct nf_conn *ct, | |||
906 | 1 : ct->proto.tcp.last_win; | 906 | 1 : ct->proto.tcp.last_win; |
907 | ct->proto.tcp.seen[ct->proto.tcp.last_dir].td_scale = | 907 | ct->proto.tcp.seen[ct->proto.tcp.last_dir].td_scale = |
908 | ct->proto.tcp.last_wscale; | 908 | ct->proto.tcp.last_wscale; |
909 | ct->proto.tcp.last_flags &= ~IP_CT_EXP_CHALLENGE_ACK; | ||
909 | ct->proto.tcp.seen[ct->proto.tcp.last_dir].flags = | 910 | ct->proto.tcp.seen[ct->proto.tcp.last_dir].flags = |
910 | ct->proto.tcp.last_flags; | 911 | ct->proto.tcp.last_flags; |
911 | memset(&ct->proto.tcp.seen[dir], 0, | 912 | memset(&ct->proto.tcp.seen[dir], 0, |
@@ -923,7 +924,9 @@ static int tcp_packet(struct nf_conn *ct, | |||
923 | * may be in sync but we are not. In that case, we annotate | 924 | * may be in sync but we are not. In that case, we annotate |
924 | * the TCP options and let the packet go through. If it is a | 925 | * the TCP options and let the packet go through. If it is a |
925 | * valid SYN packet, the server will reply with a SYN/ACK, and | 926 | * valid SYN packet, the server will reply with a SYN/ACK, and |
926 | * then we'll get in sync. Otherwise, the server ignores it. */ | 927 | * then we'll get in sync. Otherwise, the server potentially |
928 | * responds with a challenge ACK if implementing RFC5961. | ||
929 | */ | ||
927 | if (index == TCP_SYN_SET && dir == IP_CT_DIR_ORIGINAL) { | 930 | if (index == TCP_SYN_SET && dir == IP_CT_DIR_ORIGINAL) { |
928 | struct ip_ct_tcp_state seen = {}; | 931 | struct ip_ct_tcp_state seen = {}; |
929 | 932 | ||
@@ -939,6 +942,13 @@ static int tcp_packet(struct nf_conn *ct, | |||
939 | ct->proto.tcp.last_flags |= | 942 | ct->proto.tcp.last_flags |= |
940 | IP_CT_TCP_FLAG_SACK_PERM; | 943 | IP_CT_TCP_FLAG_SACK_PERM; |
941 | } | 944 | } |
945 | /* Mark the potential for RFC5961 challenge ACK, | ||
946 | * this pose a special problem for LAST_ACK state | ||
947 | * as ACK is intrepretated as ACKing last FIN. | ||
948 | */ | ||
949 | if (old_state == TCP_CONNTRACK_LAST_ACK) | ||
950 | ct->proto.tcp.last_flags |= | ||
951 | IP_CT_EXP_CHALLENGE_ACK; | ||
942 | } | 952 | } |
943 | spin_unlock_bh(&ct->lock); | 953 | spin_unlock_bh(&ct->lock); |
944 | if (LOG_INVALID(net, IPPROTO_TCP)) | 954 | if (LOG_INVALID(net, IPPROTO_TCP)) |
@@ -970,6 +980,25 @@ static int tcp_packet(struct nf_conn *ct, | |||
970 | nf_log_packet(net, pf, 0, skb, NULL, NULL, NULL, | 980 | nf_log_packet(net, pf, 0, skb, NULL, NULL, NULL, |
971 | "nf_ct_tcp: invalid state "); | 981 | "nf_ct_tcp: invalid state "); |
972 | return -NF_ACCEPT; | 982 | return -NF_ACCEPT; |
983 | case TCP_CONNTRACK_TIME_WAIT: | ||
984 | /* RFC5961 compliance cause stack to send "challenge-ACK" | ||
985 | * e.g. in response to spurious SYNs. Conntrack MUST | ||
986 | * not believe this ACK is acking last FIN. | ||
987 | */ | ||
988 | if (old_state == TCP_CONNTRACK_LAST_ACK && | ||
989 | index == TCP_ACK_SET && | ||
990 | ct->proto.tcp.last_dir != dir && | ||
991 | ct->proto.tcp.last_index == TCP_SYN_SET && | ||
992 | (ct->proto.tcp.last_flags & IP_CT_EXP_CHALLENGE_ACK)) { | ||
993 | /* Detected RFC5961 challenge ACK */ | ||
994 | ct->proto.tcp.last_flags &= ~IP_CT_EXP_CHALLENGE_ACK; | ||
995 | spin_unlock_bh(&ct->lock); | ||
996 | if (LOG_INVALID(net, IPPROTO_TCP)) | ||
997 | nf_log_packet(net, pf, 0, skb, NULL, NULL, NULL, | ||
998 | "nf_ct_tcp: challenge-ACK ignored "); | ||
999 | return NF_ACCEPT; /* Don't change state */ | ||
1000 | } | ||
1001 | break; | ||
973 | case TCP_CONNTRACK_CLOSE: | 1002 | case TCP_CONNTRACK_CLOSE: |
974 | if (index == TCP_RST_SET | 1003 | if (index == TCP_RST_SET |
975 | && (ct->proto.tcp.seen[!dir].flags & IP_CT_TCP_FLAG_MAXACK_SET) | 1004 | && (ct->proto.tcp.seen[!dir].flags & IP_CT_TCP_FLAG_MAXACK_SET) |