Skip to content

Commit

Permalink
conntrack: RFC5961 challenge ACK confuse conntrack LAST-ACK transition
Browse files Browse the repository at this point in the history
In compliance with RFC5961, the network stack send challenge ACK in
response to spurious SYN packets, since commit 0c228e8 ("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: 0c228e8 ("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>
  • Loading branch information
Jesper Dangaard Brouer authored and Pablo Neira Ayuso committed May 15, 2015
1 parent 595ca58 commit b3cad28
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 3 deletions.
3 changes: 3 additions & 0 deletions include/uapi/linux/netfilter/nf_conntrack_tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ enum tcp_conntrack {
/* The field td_maxack has been set */
#define IP_CT_TCP_FLAG_MAXACK_SET 0x20

/* Marks possibility for expected RFC5961 challenge ACK */
#define IP_CT_EXP_CHALLENGE_ACK 0x40

struct nf_ct_tcp_flags {
__u8 flags;
__u8 mask;
Expand Down
35 changes: 32 additions & 3 deletions net/netfilter/nf_conntrack_proto_tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ static const u8 tcp_conntracks[2][6][TCP_CONNTRACK_MAX] = {
* sES -> sES :-)
* sFW -> sCW Normal close request answered by ACK.
* sCW -> sCW
* sLA -> sTW Last ACK detected.
* sLA -> sTW Last ACK detected (RFC5961 challenged)
* sTW -> sTW Retransmitted last ACK. Remain in the same state.
* sCL -> sCL
*/
Expand Down Expand Up @@ -261,7 +261,7 @@ static const u8 tcp_conntracks[2][6][TCP_CONNTRACK_MAX] = {
* sES -> sES :-)
* sFW -> sCW Normal close request answered by ACK.
* sCW -> sCW
* sLA -> sTW Last ACK detected.
* sLA -> sTW Last ACK detected (RFC5961 challenged)
* sTW -> sTW Retransmitted last ACK.
* sCL -> sCL
*/
Expand Down Expand Up @@ -906,6 +906,7 @@ static int tcp_packet(struct nf_conn *ct,
1 : ct->proto.tcp.last_win;
ct->proto.tcp.seen[ct->proto.tcp.last_dir].td_scale =
ct->proto.tcp.last_wscale;
ct->proto.tcp.last_flags &= ~IP_CT_EXP_CHALLENGE_ACK;
ct->proto.tcp.seen[ct->proto.tcp.last_dir].flags =
ct->proto.tcp.last_flags;
memset(&ct->proto.tcp.seen[dir], 0,
Expand All @@ -923,7 +924,9 @@ static int tcp_packet(struct nf_conn *ct,
* may be in sync but we are not. In that case, we annotate
* the TCP options and let the packet go through. If it is a
* valid SYN packet, the server will reply with a SYN/ACK, and
* then we'll get in sync. Otherwise, the server ignores it. */
* then we'll get in sync. Otherwise, the server potentially
* responds with a challenge ACK if implementing RFC5961.
*/
if (index == TCP_SYN_SET && dir == IP_CT_DIR_ORIGINAL) {
struct ip_ct_tcp_state seen = {};

Expand All @@ -939,6 +942,13 @@ static int tcp_packet(struct nf_conn *ct,
ct->proto.tcp.last_flags |=
IP_CT_TCP_FLAG_SACK_PERM;
}
/* Mark the potential for RFC5961 challenge ACK,
* this pose a special problem for LAST_ACK state
* as ACK is intrepretated as ACKing last FIN.
*/
if (old_state == TCP_CONNTRACK_LAST_ACK)
ct->proto.tcp.last_flags |=
IP_CT_EXP_CHALLENGE_ACK;
}
spin_unlock_bh(&ct->lock);
if (LOG_INVALID(net, IPPROTO_TCP))
Expand Down Expand Up @@ -970,6 +980,25 @@ static int tcp_packet(struct nf_conn *ct,
nf_log_packet(net, pf, 0, skb, NULL, NULL, NULL,
"nf_ct_tcp: invalid state ");
return -NF_ACCEPT;
case TCP_CONNTRACK_TIME_WAIT:
/* RFC5961 compliance cause stack to send "challenge-ACK"
* e.g. in response to spurious SYNs. Conntrack MUST
* not believe this ACK is acking last FIN.
*/
if (old_state == TCP_CONNTRACK_LAST_ACK &&
index == TCP_ACK_SET &&
ct->proto.tcp.last_dir != dir &&
ct->proto.tcp.last_index == TCP_SYN_SET &&
(ct->proto.tcp.last_flags & IP_CT_EXP_CHALLENGE_ACK)) {
/* Detected RFC5961 challenge ACK */
ct->proto.tcp.last_flags &= ~IP_CT_EXP_CHALLENGE_ACK;
spin_unlock_bh(&ct->lock);
if (LOG_INVALID(net, IPPROTO_TCP))
nf_log_packet(net, pf, 0, skb, NULL, NULL, NULL,
"nf_ct_tcp: challenge-ACK ignored ");
return NF_ACCEPT; /* Don't change state */
}
break;
case TCP_CONNTRACK_CLOSE:
if (index == TCP_RST_SET
&& (ct->proto.tcp.seen[!dir].flags & IP_CT_TCP_FLAG_MAXACK_SET)
Expand Down

0 comments on commit b3cad28

Please sign in to comment.