diff --git a/rules/stream-events.rules b/rules/stream-events.rules index 0c03b92437..fe4c6cb00e 100644 --- a/rules/stream-events.rules +++ b/rules/stream-events.rules @@ -60,6 +60,8 @@ alert tcp any any -> any any (msg:"SURICATA STREAM SHUTDOWN RST invalid ack"; st # Sequence gap: missing data in the reassembly engine. Usually due to packet loss. Will be very noisy on a overloaded link / sensor. #alert tcp any any -> any any (msg:"SURICATA STREAM reassembly sequence GAP -- missing packet(s)"; stream-event:reassembly_seq_gap; classtype:protocol-command-decode; sid:2210048; rev:2;) 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;) # retransmission detection # @@ -79,5 +81,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 2210056 +# next sid 2210057 diff --git a/src/decode-events.h b/src/decode-events.h index ceb8c64013..3080764621 100644 --- a/src/decode-events.h +++ b/src/decode-events.h @@ -198,6 +198,7 @@ enum { STREAM_PKT_BROKEN_ACK, STREAM_RST_INVALID_ACK, STREAM_PKT_RETRANSMISSION, + STREAM_PKT_BAD_WINDOW_UPDATE, STREAM_REASSEMBLY_SEGMENT_BEFORE_BASE_SEQ, STREAM_REASSEMBLY_NO_SEGMENT, diff --git a/src/detect-engine-event.h b/src/detect-engine-event.h index 5b42f6e743..fb6089b92a 100644 --- a/src/detect-engine-event.h +++ b/src/detect-engine-event.h @@ -211,6 +211,7 @@ struct DetectEngineEvents_ { { "stream.reassembly_no_segment", STREAM_REASSEMBLY_NO_SEGMENT, }, { "stream.reassembly_seq_gap", STREAM_REASSEMBLY_SEQ_GAP, }, { "stream.reassembly_overlap_different_data", STREAM_REASSEMBLY_OVERLAP_DIFFERENT_DATA, }, + { "stream.pkt_bad_window_update", STREAM_PKT_BAD_WINDOW_UPDATE, }, /* SCTP EVENTS */ { "sctp.pkt_too_small", SCTP_PKT_TOO_SMALL, }, diff --git a/src/stream-tcp.c b/src/stream-tcp.c index 607377c1a1..d76496a926 100644 --- a/src/stream-tcp.c +++ b/src/stream-tcp.c @@ -4210,6 +4210,75 @@ static int StreamTcpPacketIsWindowUpdate(TcpSession *ssn, Packet *p) return 0; } +/** + * Try to detect packets doing bad window updates + * + * See bug 1238. + * + * Find packets that are unexpected, and shrink the window to the point + * where the packets we do expect are rejected for being out of window. + * + * The logic we use here is: + * - packet seq > next_seq + * - packet acq > next_seq (packet acks unseen data) + * - packet shrinks window more than it's own data size + * (in case of no data, any shrinking is rejected) + * + * Packets coming in after packet loss can look quite a bit like this. + */ +static int StreamTcpPacketIsBadWindowUpdate(TcpSession *ssn, Packet *p) +{ + TcpStream *stream = NULL, *ostream = NULL; + uint32_t seq; + uint32_t ack; + uint32_t pkt_win; + + if (p->flags & PKT_PSEUDO_STREAM_END) + return 0; + + if (ssn->state < TCP_ESTABLISHED) + return 0; + + if ((p->tcph->th_flags & (TH_SYN|TH_FIN|TH_RST)) != 0) + return 0; + + if (PKT_IS_TOSERVER(p)) { + stream = &ssn->client; + ostream = &ssn->server; + } else { + stream = &ssn->server; + ostream = &ssn->client; + } + + seq = TCP_GET_SEQ(p); + ack = TCP_GET_ACK(p); + + pkt_win = TCP_GET_WINDOW(p) << ostream->wscale; + + if (pkt_win < ostream->window) { + uint32_t diff = ostream->window - pkt_win; + if (diff > p->payload_len && + SEQ_GT(ack, ostream->next_seq) && + SEQ_GT(seq, stream->next_seq)) + { + SCLogDebug("%"PRIu64", pkt_win %u, stream win %u, diff %u, dsize %u", + p->pcap_cnt, pkt_win, ostream->window, diff, p->payload_len); + SCLogDebug("%"PRIu64", pkt_win %u, stream win %u", + p->pcap_cnt, pkt_win, ostream->window); + SCLogDebug("%"PRIu64", seq %u ack %u ostream->next_seq %u stream->last_ack %u, diff %u (%u)", + p->pcap_cnt, seq, ack, ostream->next_seq, stream->last_ack, + ostream->next_seq - ostream->last_ack, stream->next_seq - stream->last_ack); + + StreamTcpSetEvent(p, STREAM_PKT_BAD_WINDOW_UPDATE); + return 1; + } + + } + SCLogDebug("seq %u (%u), ack %u (%u)", seq, stream->next_seq, ack, ostream->last_ack); + return 0; +} + + /* flow is and stays locked */ int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt, PacketQueue *pq) @@ -4274,7 +4343,6 @@ int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt, if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM_SYNACK) StreamTcpPacketSwitchDir(ssn, p); - StreamTcpPacketIsWindowUpdate(ssn, p); if (StreamTcpPacketIsKeepAlive(ssn, p) == 1) { goto skip; } @@ -4284,6 +4352,12 @@ int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt, } StreamTcpClearKeepAliveFlag(ssn, p); + /* if packet is not a valid window update, check if it is perhaps + * a bad window update that we should ignore (and alert on) */ + if (StreamTcpPacketIsWindowUpdate(ssn, p) == 0) + if (StreamTcpPacketIsBadWindowUpdate(ssn,p)) + goto skip; + switch (ssn->state) { case TCP_SYN_SENT: if(StreamTcpPacketStateSynSent(tv, p, stt, ssn, &stt->pseudo_queue)) {