Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next/20180718/v5 #3428

Merged
merged 6 commits into from Jul 19, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
  • Loading branch information
victorjulien committed Jul 18, 2018
commit 843d0b7a10bb45627f94764a6c5d468a24143345
5 changes: 4 additions & 1 deletion rules/stream-events.rules
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
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
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
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
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