Skip to content

Commit

Permalink
stream: support RST getting lost/ignored
Browse files Browse the repository at this point in the history
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
  • Loading branch information
victorjulien committed Jul 18, 2018
1 parent 33614fc commit 843d0b7
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 57 deletions.
5 changes: 4 additions & 1 deletion rules/stream-events.rules
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand All @@ -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

2 changes: 2 additions & 0 deletions src/decode-events.c
Original file line number Diff line number Diff line change
Expand Up @@ -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, },
Expand Down
1 change: 1 addition & 0 deletions src/decode-events.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions src/stream-tcp-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down Expand Up @@ -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 */
Expand Down
200 changes: 146 additions & 54 deletions src/stream-tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 843d0b7

Please sign in to comment.