From 23e93b126466f118965c1ba82a1fb80ee0ebef10 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 10 May 2018 17:23:05 +0200 Subject: [PATCH] stream: support RST getting lost/ignored In case of a valid RST on a SYN, the state is switched to 'TCP_CLOSED'. However, the target of the RST may not have received it, or may not have accepted it. Also, the RST may have been injected, so the supposed sender may not actually be aware of the RST that was sent in it's name. In this case the previous behavior was to switch the state to CLOSED and accept no further TCP updates or stream reassembly. This patch changes this. It still switches the state to CLOSED, as this is by far the most likely to be correct. However, it will reconsider the state if the receiver continues to talk. To do this on each state change the previous state will be recorded in TcpSession::pstate. If a non-RST packet is received after a RST, this TcpSession::pstate is used to try to continue the conversation. If the (supposed) sender of the RST is also continueing the conversation as normal, it's highly likely it didn't send the RST. In this case a stream event is generated. Ticket: #2501 Reported-By: Kirill Shipulin --- rules/stream-events.rules | 5 +- src/decode-events.c | 2 + src/decode-events.h | 1 + src/stream-tcp-private.h | 6 +- src/stream-tcp.c | 200 ++++++++++++++++++++++++++++---------- 5 files changed, 157 insertions(+), 57 deletions(-) diff --git a/rules/stream-events.rules b/rules/stream-events.rules index 21feab9dc8..6599101393 100644 --- a/rules/stream-events.rules +++ b/rules/stream-events.rules @@ -67,6 +67,9 @@ alert tcp any any -> any any (msg:"SURICATA STREAM SHUTDOWN RST invalid ack"; st alert tcp any any -> any any (msg:"SURICATA STREAM reassembly overlap with different data"; stream-event:reassembly_overlap_different_data; classtype:protocol-command-decode; sid:2210050; rev:2;) # Bad Window Update: see bug 1238 for an explanation alert tcp any any -> any any (msg:"SURICATA STREAM bad window update"; stream-event:pkt_bad_window_update; classtype:protocol-command-decode; sid:2210056; rev:1;) +# RST injection suspected. Alerts on packets *after* the RST, as these indicate the target +# rejected/ignored the RST. +alert tcp any any -> any any (msg:"SURICATA STREAM suspected RST injection"; stream-event:suspected_rst_inject; classtype:protocol-command-decode; sid:2210058; rev:1;) # retransmission detection # @@ -86,5 +89,5 @@ alert tcp any any -> any any (msg:"SURICATA STREAM Packet is retransmission"; st # rule to alert if a stream has excessive retransmissions alert tcp any any -> any any (msg:"SURICATA STREAM excessive retransmissions"; flowbits:isnotset,tcp.retransmission.alerted; flowint:tcp.retransmission.count,>=,10; flowbits:set,tcp.retransmission.alerted; classtype:protocol-command-decode; sid:2210054; rev:1;) -# next sid 2210058 +# next sid 2210059 diff --git a/src/decode-events.c b/src/decode-events.c index 2130207719..34ef6fe1f4 100644 --- a/src/decode-events.c +++ b/src/decode-events.c @@ -239,6 +239,8 @@ const struct DecodeEvents_ DEvents[] = { { "stream.pkt_retransmission", STREAM_PKT_RETRANSMISSION, }, { "stream.pkt_bad_window_update", STREAM_PKT_BAD_WINDOW_UPDATE, }, + { "stream.suspected_rst_inject", STREAM_SUSPECTED_RST_INJECT, }, + { "stream.reassembly_segment_before_base_seq", STREAM_REASSEMBLY_SEGMENT_BEFORE_BASE_SEQ, }, { "stream.reassembly_no_segment", STREAM_REASSEMBLY_NO_SEGMENT, }, { "stream.reassembly_seq_gap", STREAM_REASSEMBLY_SEQ_GAP, }, diff --git a/src/decode-events.h b/src/decode-events.h index 70afbd7a28..3d4178ca4e 100644 --- a/src/decode-events.h +++ b/src/decode-events.h @@ -248,6 +248,7 @@ enum { STREAM_RST_INVALID_ACK, STREAM_PKT_RETRANSMISSION, STREAM_PKT_BAD_WINDOW_UPDATE, + STREAM_SUSPECTED_RST_INJECT, STREAM_REASSEMBLY_SEGMENT_BEFORE_BASE_SEQ, STREAM_REASSEMBLY_NO_SEGMENT, diff --git a/src/stream-tcp-private.h b/src/stream-tcp-private.h index 6a0e36755d..aa954f9eaa 100644 --- a/src/stream-tcp-private.h +++ b/src/stream-tcp-private.h @@ -185,7 +185,8 @@ enum #define STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED 0x0200 /** Raw reassembly disabled completely */ #define STREAMTCP_STREAM_FLAG_DISABLE_RAW 0x400 -// vacancy 1x + +#define STREAMTCP_STREAM_FLAG_RST_RECV 0x800 /** NOTE: flags field is 12 bits */ @@ -220,7 +221,8 @@ enum typedef struct TcpSession_ { PoolThreadReserved res; - uint8_t state; + uint8_t state:4; /**< tcp state from state enum */ + uint8_t pstate:4; /**< previous state */ uint8_t queue_len; /**< length of queue list below */ int8_t data_first_seen_dir; /** track all the tcp flags we've seen */ diff --git a/src/stream-tcp.c b/src/stream-tcp.c index 2b5d8693cf..d854c749a5 100644 --- a/src/stream-tcp.c +++ b/src/stream-tcp.c @@ -105,6 +105,9 @@ static int StreamTcpValidateTimestamp(TcpSession * , Packet *); static int StreamTcpHandleTimestamp(TcpSession * , Packet *); static int StreamTcpValidateRst(TcpSession * , Packet *); static inline int StreamTcpValidateAck(TcpSession *ssn, TcpStream *, Packet *); +static int StreamTcpStateDispatch(ThreadVars *tv, Packet *p, + StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq, + uint8_t state); extern int g_detect_disabled; @@ -697,6 +700,7 @@ static void StreamTcpPacketSetState(Packet *p, TcpSession *ssn, if (state == ssn->state || PKT_IS_PSEUDOPKT(p)) return; + ssn->pstate = ssn->state; ssn->state = state; /* update the flow state */ @@ -1333,11 +1337,16 @@ static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p, SEQ_EQ(TCP_GET_WINDOW(p), 0) && SEQ_EQ(TCP_GET_ACK(p), (ssn->client.isn + 1))) { + SCLogDebug("ssn->server.flags |= STREAMTCP_STREAM_FLAG_RST_RECV"); + ssn->server.flags |= STREAMTCP_STREAM_FLAG_RST_RECV; + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); SCLogDebug("ssn %p: Reset received and state changed to " "TCP_CLOSED", ssn); } } else { + ssn->client.flags |= STREAMTCP_STREAM_FLAG_RST_RECV; + SCLogDebug("ssn->client.flags |= STREAMTCP_STREAM_FLAG_RST_RECV"); StreamTcpPacketSetState(p, ssn, TCP_CLOSED); SCLogDebug("ssn %p: Reset received and state changed to " "TCP_CLOSED", ssn); @@ -4189,6 +4198,68 @@ static int StreamTcpPacketStateTimeWait(ThreadVars *tv, Packet *p, return 0; } +static int StreamTcpPacketStateClosed(ThreadVars *tv, Packet *p, + StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq) +{ + if (ssn == NULL) + return -1; + + if (p->tcph->th_flags & TH_RST) { + SCLogDebug("RST on closed state"); + return 0; + } + + TcpStream *stream = NULL, *ostream = NULL; + if (PKT_IS_TOSERVER(p)) { + stream = &ssn->client; + ostream = &ssn->server; + } else { + stream = &ssn->server; + ostream = &ssn->client; + } + + SCLogDebug("stream %s ostream %s", + stream->flags & STREAMTCP_STREAM_FLAG_RST_RECV?"true":"false", + ostream->flags & STREAMTCP_STREAM_FLAG_RST_RECV ? "true":"false"); + + /* if we've seen a RST on our direction, but not on the other + * see if we perhaps need to continue processing anyway. */ + if ((stream->flags & STREAMTCP_STREAM_FLAG_RST_RECV) == 0) { + if (ostream->flags & STREAMTCP_STREAM_FLAG_RST_RECV) { + if (StreamTcpStateDispatch(tv, p, stt, ssn, &stt->pseudo_queue, ssn->pstate) < 0) + return -1; + } + } + return 0; +} + +static void StreamTcpPacketCheckPostRst(TcpSession *ssn, Packet *p) +{ + if (p->flags & PKT_PSEUDO_STREAM_END) { + return; + } + /* more RSTs are not unusual */ + if ((p->tcph->th_flags & (TH_RST)) != 0) { + return; + } + + TcpStream *ostream = NULL; + if (PKT_IS_TOSERVER(p)) { + ostream = &ssn->server; + } else { + ostream = &ssn->client; + } + + if (ostream->flags & STREAMTCP_STREAM_FLAG_RST_RECV) { + SCLogDebug("regular packet %"PRIu64" from same sender as " + "the previous RST. Looks like it injected!", p->pcap_cnt); + ostream->flags &= ~STREAMTCP_STREAM_FLAG_RST_RECV; + StreamTcpSetEvent(p, STREAM_SUSPECTED_RST_INJECT); + return; + } + return; +} + /** * \retval 1 packet is a keep alive pkt * \retval 0 packet is not a keep alive pkt @@ -4473,6 +4544,76 @@ static int StreamTcpPacketIsBadWindowUpdate(TcpSession *ssn, Packet *p) return 0; } +/** \internal + * \brief call packet handling function for 'state' + * \param state current TCP state + */ +static inline int StreamTcpStateDispatch(ThreadVars *tv, Packet *p, + StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq, + const uint8_t state) +{ + switch (state) { + case TCP_SYN_SENT: + if (StreamTcpPacketStateSynSent(tv, p, stt, ssn, pq)) { + return -1; + } + break; + case TCP_SYN_RECV: + if (StreamTcpPacketStateSynRecv(tv, p, stt, ssn, pq)) { + return -1; + } + break; + case TCP_ESTABLISHED: + if (StreamTcpPacketStateEstablished(tv, p, stt, ssn, pq)) { + return -1; + } + break; + case TCP_FIN_WAIT1: + if (StreamTcpPacketStateFinWait1(tv, p, stt, ssn, pq)) { + return -1; + } + break; + case TCP_FIN_WAIT2: + if (StreamTcpPacketStateFinWait2(tv, p, stt, ssn, pq)) { + return -1; + } + break; + case TCP_CLOSING: + if (StreamTcpPacketStateClosing(tv, p, stt, ssn, pq)) { + return -1; + } + break; + case TCP_CLOSE_WAIT: + if (StreamTcpPacketStateCloseWait(tv, p, stt, ssn, pq)) { + return -1; + } + break; + case TCP_LAST_ACK: + if (StreamTcpPacketStateLastAck(tv, p, stt, ssn, pq)) { + return -1; + } + break; + case TCP_TIME_WAIT: + if (StreamTcpPacketStateTimeWait(tv, p, stt, ssn, pq)) { + return -1; + } + break; + case TCP_CLOSED: + /* TCP session memory is not returned to pool until timeout. */ + SCLogDebug("packet received on closed state"); + + if (StreamTcpPacketStateClosed(tv, p, stt, ssn, pq)) { + return -1; + } + + break; + default: + SCLogDebug("packet received on default state"); + break; + } + return 0; +} + /* flow is and stays locked */ int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt, PacketQueue *pq) @@ -4588,61 +4729,12 @@ int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt, if (StreamTcpPacketIsBadWindowUpdate(ssn,p)) goto skip; - switch (ssn->state) { - case TCP_SYN_SENT: - if(StreamTcpPacketStateSynSent(tv, p, stt, ssn, &stt->pseudo_queue)) { - goto error; - } - break; - case TCP_SYN_RECV: - if(StreamTcpPacketStateSynRecv(tv, p, stt, ssn, &stt->pseudo_queue)) { - goto error; - } - break; - case TCP_ESTABLISHED: - if(StreamTcpPacketStateEstablished(tv, p, stt, ssn, &stt->pseudo_queue)) { - goto error; - } - break; - case TCP_FIN_WAIT1: - if(StreamTcpPacketStateFinWait1(tv, p, stt, ssn, &stt->pseudo_queue)) { - goto error; - } - break; - case TCP_FIN_WAIT2: - if(StreamTcpPacketStateFinWait2(tv, p, stt, ssn, &stt->pseudo_queue)) { - goto error; - } - break; - case TCP_CLOSING: - if(StreamTcpPacketStateClosing(tv, p, stt, ssn, &stt->pseudo_queue)) { - goto error; - } - break; - case TCP_CLOSE_WAIT: - if(StreamTcpPacketStateCloseWait(tv, p, stt, ssn, &stt->pseudo_queue)) { - goto error; - } - break; - case TCP_LAST_ACK: - if(StreamTcpPacketStateLastAck(tv, p, stt, ssn, &stt->pseudo_queue)) { - goto error; - } - break; - case TCP_TIME_WAIT: - if(StreamTcpPacketStateTimeWait(tv, p, stt, ssn, &stt->pseudo_queue)) { - goto error; - } - break; - case TCP_CLOSED: - /* TCP session memory is not returned to pool until timeout. */ - SCLogDebug("packet received on closed state"); - break; - default: - SCLogDebug("packet received on default state"); - break; - } + /* handle the per 'state' logic */ + if (StreamTcpStateDispatch(tv, p, stt, ssn, &stt->pseudo_queue, ssn->state) < 0) + goto error; + skip: + StreamTcpPacketCheckPostRst(ssn, p); if (ssn->state >= TCP_ESTABLISHED) { p->flags |= PKT_STREAM_EST;