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
pull/3472/head
Victor Julien 7 years ago
parent f4d5af76a8
commit 23e93b1264

@ -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

@ -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, },

@ -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,

@ -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 */

@ -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)) {
/* handle the per 'state' logic */
if (StreamTcpStateDispatch(tv, p, stt, ssn, &stt->pseudo_queue, ssn->state) < 0)
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;
}
skip:
StreamTcpPacketCheckPostRst(ssn, p);
if (ssn->state >= TCP_ESTABLISHED) {
p->flags |= PKT_STREAM_EST;

Loading…
Cancel
Save