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 c25978e079..57cadc59f9 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; @@ -737,6 +740,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 */ @@ -1375,11 +1379,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); @@ -4231,6 +4240,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 @@ -4515,6 +4586,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) @@ -4630,61 +4771,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;