netfilter: conntrack: handle tcp challenge acks during connection reuse

When a connection is re-used, following can happen:
[ connection starts to close, fin sent in either direction ]
 > syn   # initator quickly reuses connection
 < ack   # peer sends a challenge ack
 > rst   # rst, sequence number == ack_seq of previous challenge ack
 > syn   # this syn is expected to pass

Problem is that the rst will fail window validation, so it gets
tagged as invalid.

If ruleset drops such packets, we get repeated syn-retransmits until
initator gives up or peer starts responding with syn/ack.

Before the commit indicated in the "Fixes" tag below this used to work:

The challenge-ack made conntrack re-init state based on the challenge
ack itself, so the following rst would pass window validation.

Add challenge-ack support: If we get ack for syn, record the ack_seq,
and then check if the rst sequence number matches the last ack number
seen in reverse direction.

Fixes: c7aab4f170 ("netfilter: nf_conntrack_tcp: re-init for syn packets only")
Reported-by: Michal Tesar <mtesar@redhat.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
Florian Westphal 2023-01-11 14:42:32 +01:00 committed by Pablo Neira Ayuso
parent 1f3bd64ad9
commit c410cb974f
1 changed files with 15 additions and 0 deletions

View File

@ -1068,6 +1068,13 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct,
ct->proto.tcp.last_flags |=
IP_CT_EXP_CHALLENGE_ACK;
}
/* possible challenge ack reply to syn */
if (old_state == TCP_CONNTRACK_SYN_SENT &&
index == TCP_ACK_SET &&
dir == IP_CT_DIR_REPLY)
ct->proto.tcp.last_ack = ntohl(th->ack_seq);
spin_unlock_bh(&ct->lock);
nf_ct_l4proto_log_invalid(skb, ct, state,
"packet (index %d) in dir %d ignored, state %s",
@ -1193,6 +1200,14 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct,
* segments we ignored. */
goto in_window;
}
/* Reset in response to a challenge-ack we let through earlier */
if (old_state == TCP_CONNTRACK_SYN_SENT &&
ct->proto.tcp.last_index == TCP_ACK_SET &&
ct->proto.tcp.last_dir == IP_CT_DIR_REPLY &&
ntohl(th->seq) == ct->proto.tcp.last_ack)
goto in_window;
break;
default:
/* Keep compilers happy. */