diff --git a/src/decode.h b/src/decode.h index ae01dc50c8..1f6e2ff6a9 100644 --- a/src/decode.h +++ b/src/decode.h @@ -743,6 +743,7 @@ void AddressDebugPrint(Address *); #define PKT_STREAM_ADD 0x10 /**< Packet payload was added to reassembled stream */ #define PKT_STREAM_EOF 0x20 /**< Stream is in eof state */ #define PKT_HAS_FLOW 0x40 +#define PKT_PSEUDO_STREAM_END 0x80 /**< Pseudo packet to end the stream */ #endif /* __DECODE_H__ */ diff --git a/src/stream-tcp-reassemble.c b/src/stream-tcp-reassemble.c index 7c76f3dec1..c9656d8027 100644 --- a/src/stream-tcp-reassemble.c +++ b/src/stream-tcp-reassemble.c @@ -1454,6 +1454,9 @@ static int StreamTcpReassembleRawCheckLimit(TcpSession *ssn, TcpStream *stream, if (ssn->state >= TCP_TIME_WAIT) SCReturnInt(1); + if (p->flags & PKT_PSEUDO_STREAM_END) + SCReturnInt(1); + /* check if we have enough data to send to L7 */ if (p->flowflags & FLOW_PKT_TOCLIENT) { SCLogDebug("StreamMsgQueueGetMinInitChunkLen(STREAM_TOSERVER) %"PRIu32, @@ -2368,10 +2371,17 @@ int StreamTcpReassembleProcessAppLayer(TcpReassemblyThreadCtx *ra_ctx) do { smsg = StreamMsgGetFromQueue(ra_ctx->stream_q); if (smsg != NULL) { - SCLogDebug("smsg %p, next %p, prev %p, flow %p, q->len %u", smsg, smsg->next, smsg->prev, smsg->flow, ra_ctx->stream_q->len); + SCLogDebug("smsg %p, next %p, prev %p, flow %p, q->len %u, " + "smsg->data.datalen %u, direction %s%s", + smsg, smsg->next, smsg->prev, smsg->flow, + ra_ctx->stream_q->len, smsg->data.data_len, + smsg->flags & STREAM_TOSERVER : "toserver":"", + smsg->flags & STREAM_TOCLIENT : "toclient":""); BUG_ON(smsg->flow == NULL); + //PrintRawDataFp(stderr, smsg->data.data, smsg->data.data_len); + /* Handle the stream msg. No need to use locking, flow is * already locked at this point. Don't break out of the * loop if we encounter an error. */ @@ -2402,9 +2412,6 @@ int StreamTcpReassembleHandleSegment(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ opposing_stream = &ssn->client; } - /* Create the pseudo packet from the reassembled stream to detect the attack */ - StreamTcpReassemblePseudoPacketCreate(opposing_stream, p, pq); - /* handle ack received */ if (StreamTcpReassembleHandleSegmentUpdateACK(ra_ctx, ssn, opposing_stream, p) != 0) { @@ -2428,95 +2435,6 @@ int StreamTcpReassembleHandleSegment(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ SCReturnInt(0); } -/** - * \brief Function to fetch a packet from the packet allocation queue for - * creation of the pseudo packet from the reassembled stream. - * - * @param parent Pointer to the parent of the pseudo packet - * @param pkt pointer to the raw packet of the parent - * @param len length of the packet - * @return upon success returns the pointer to the new pseudo packet - * otherwise NULL - */ -Packet *StreamTcpReassemblePseudoSetup(Packet *parent, uint8_t *pkt, uint32_t len) -{ - Packet *p = PacketGetFromQueueOrAlloc(); - if (p == NULL || len == 0) { - return NULL; - } - - /* set the root ptr to the lowest layer */ - if (parent->root != NULL) - p->root = parent->root; - else - p->root = parent; - - /* copy packet and set lenght, proto */ - p->tunnel_proto = parent->proto; - p->pktlen = len; - memcpy(&p->pkt, pkt, (len - parent->payload_len)); - p->recursion_level = parent->recursion_level + 1; - p->ts.tv_sec = parent->ts.tv_sec; - p->ts.tv_usec = parent->ts.tv_usec; - - /* set tunnel flags */ - - /* tell new packet it's part of a tunnel */ - SET_TUNNEL_PKT(p); - /* tell parent packet it's part of a tunnel */ - SET_TUNNEL_PKT(parent); - - /* increment tunnel packet refcnt in the root packet */ - TUNNEL_INCR_PKT_TPR(p); - - return p; -} - -/** - * \brief Function to setup the IP and TCP header of the pseudo packet from - * the newly copied raw packet contents of the parent. - * - * @param np pointer to the pseudo packet - * @param p pointer to the original packet - */ -void StreamTcpReassemblePseudoPacketSetupHeader(Packet *np, Packet *p) -{ - /* Setup the IP header */ - if (PKT_IS_IPV4(p)) { - np->ip4h = (IPV4Hdr *)(np->pkt + (np->pktlen - IPV4_GET_IPLEN(p))); - PSUEDO_PKT_SET_IPV4HDR(np->ip4h, p->ip4h); - - /* Similarly setup the TCP header with ports in opposite direction */ - np->tcph = (TCPHdr *)(np->ip4h + IPV4_GET_HLEN(p)); - PSUEDO_PKT_SET_TCPHDR(np->tcph, p->tcph); - - /* Setup the adress and port details */ - SET_IPV4_SRC_ADDR(np, &np->src); - SET_IPV4_DST_ADDR(np, &np->dst); - SET_TCP_SRC_PORT(np, &np->sp); - SET_TCP_DST_PORT(np, &np->dp); - - } else if (PKT_IS_IPV6(p)) { - np->ip6h = (IPV6Hdr *)(np->pkt + (np->pktlen - IPV6_GET_PLEN(p) - IPV6_HEADER_LEN)); - PSUEDO_PKT_SET_IPV6HDR(np->ip6h, p->ip6h); - - /* Similarly setup the TCP header with ports in opposite direction */ - np->tcph = (TCPHdr *)(np->ip6h + IPV6_HEADER_LEN); - PSUEDO_PKT_SET_TCPHDR(np->tcph, p->tcph); - - /* Setup the adress and port details */ - SET_IPV6_SRC_ADDR(np, &np->src); - SET_IPV6_DST_ADDR(np, &np->dst); - SET_TCP_SRC_PORT(np, &np->sp); - SET_TCP_DST_PORT(np, &np->dp); - } - - /* Setup the payload pointer to the starting of payload location as in the - original packet, so that we don't overwrite the protocols headers */ - np->payload = np->pkt + (np->pktlen - p->payload_len); - np->payload_len = 0; -} - /** * \brief Function to copy the reassembled stream segments in to the pseudo * packet payload. @@ -2535,14 +2453,14 @@ void StreamTcpReassemblePseudoPacketCreate(TcpStream *stream, Packet *p, SCReturn; } - Packet *np = StreamTcpReassemblePseudoSetup(p, p->pkt, p->pktlen); + Packet *np = StreamTcpPseudoSetup(p, p->pkt, p->pktlen); if (np == NULL) { SCLogDebug("The packet received from packet allocation is NULL"); SCReturn; } /* Setup the IP and TCP headers */ - StreamTcpReassemblePseudoPacketSetupHeader(np,p); + StreamTcpPseudoPacketSetupHeader(np,p); /* Start the reassembling of received reassembled segments in to the pseudo packet to scan and detect the unwanted attacks. Resistance is @@ -2599,13 +2517,13 @@ void StreamTcpReassemblePseudoPacketCreate(TcpStream *stream, Packet *p, np->ip6h->s_ip6_plen = (TCP_GET_HLEN(np)) + np->payload_len; } PacketEnqueue(pq, np); - Packet *gap = StreamTcpReassemblePseudoSetup(p, p->pkt, p->pktlen); + Packet *gap = StreamTcpPseudoSetup(p, p->pkt, p->pktlen); if (gap == NULL) { SCLogDebug("The packet received from packet allocation is NULL"); SCReturn; } np = gap; - StreamTcpReassemblePseudoPacketSetupHeader(np,p); + StreamTcpPseudoPacketSetupHeader(np,p); } } @@ -2675,13 +2593,13 @@ void StreamTcpReassemblePseudoPacketCreate(TcpStream *stream, Packet *p, np->ip6h->s_ip6_plen = (TCP_GET_HLEN(np)) + np->payload_len; } PacketEnqueue(pq, np); - Packet *newp = StreamTcpReassemblePseudoSetup(p, p->pkt, p->pktlen); + Packet *newp = StreamTcpPseudoSetup(p, p->pkt, p->pktlen); if (newp == NULL) { SCLogDebug("The packet received from Allocation queue is NULL"); SCReturn; } np = newp; - StreamTcpReassemblePseudoPacketSetupHeader(np,p); + StreamTcpPseudoPacketSetupHeader(np,p); } /* if the payload len is bigger than what we copied, we handle the diff --git a/src/stream-tcp.c b/src/stream-tcp.c index b5745ab489..05a997b727 100644 --- a/src/stream-tcp.c +++ b/src/stream-tcp.c @@ -74,6 +74,11 @@ typedef struct StreamTcpThread_ { uint64_t pkts; + /** queue for pseudo packet(s) that were created in the stream + * process and need further handling. Currently only used when + * receiving (valid) RST packets */ + PacketQueue pseudo_queue; + uint16_t counter_tcp_sessions; /** sessions not picked up because memcap was reached */ uint16_t counter_tcp_ssn_memcap; @@ -94,6 +99,7 @@ extern void StreamTcpSegmentReturntoPool(TcpSegment *); int StreamTcpGetFlowState(void *); static int ValidTimestamp(TcpSession * , Packet *); void StreamTcpSetOSPolicy(TcpStream*, Packet*); +void StreamTcpPseudoPacketCreateStreamEndPacket(Packet *, TcpSession *, PacketQueue *); static Pool *ssn_pool = NULL; static SCMutex ssn_pool_mutex; @@ -1726,6 +1732,9 @@ static int StreamTcpPacketStateEstablished(ThreadVars *tv, Packet *p, case TH_RST|TH_ACK|TH_ECN|TH_CWR: if(ValidReset(ssn, p)) { + /* force both streams to reassemble, if necessary */ + StreamTcpPseudoPacketCreateStreamEndPacket(p, ssn, pq); + if(PKT_IS_TOSERVER(p)) { StreamTcpPacketSetState(p, ssn, TCP_CLOSED); SCLogDebug("ssn %p: Reset received and state changed to " @@ -2038,7 +2047,10 @@ static int StreamTcpPacketStateFinWait1(ThreadVars *tv, Packet *p, case TH_RST|TH_ACK|TH_ECN: case TH_RST|TH_ACK|TH_ECN|TH_CWR: - if(ValidReset(ssn, p)) { + if (ValidReset(ssn, p)) { + /* force both streams to reassemble, if necessary */ + StreamTcpPseudoPacketCreateStreamEndPacket(p, ssn, pq); + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); SCLogDebug("ssn %p: Reset received state changed to TCP_CLOSED", ssn); @@ -2152,7 +2164,10 @@ static int StreamTcpPacketStateFinWait2(ThreadVars *tv, Packet *p, case TH_RST|TH_ACK|TH_ECN: case TH_RST|TH_ACK|TH_ECN|TH_CWR: - if(ValidReset(ssn, p)) { + if (ValidReset(ssn, p)) { + /* force both streams to reassemble, if necessary */ + StreamTcpPseudoPacketCreateStreamEndPacket(p, ssn, pq); + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); SCLogDebug("ssn %p: Reset received state changed to TCP_CLOSED", ssn); @@ -2657,8 +2672,9 @@ static int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt, } if (ssn == NULL || ssn->state == TCP_NONE) { - if (StreamTcpPacketStateNone(tv, p, stt, ssn, pq) == -1) - SCReturnInt(-1); + if (StreamTcpPacketStateNone(tv, p, stt, ssn, &stt->pseudo_queue) == -1) { + goto error; + } if (ssn != NULL) SCLogDebug("ssn->alproto %"PRIu16"", p->flow->alproto); @@ -2670,40 +2686,49 @@ static int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt, switch (ssn->state) { case TCP_SYN_SENT: - if(StreamTcpPacketStateSynSent(tv, p, stt, ssn, pq)) - SCReturnInt(-1); + if(StreamTcpPacketStateSynSent(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } break; case TCP_SYN_RECV: - if(StreamTcpPacketStateSynRecv(tv, p, stt, ssn, pq)) - SCReturnInt(-1); + if(StreamTcpPacketStateSynRecv(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } break; case TCP_ESTABLISHED: - if(StreamTcpPacketStateEstablished(tv, p, stt, ssn, pq)) - SCReturnInt(-1); + if(StreamTcpPacketStateEstablished(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } break; case TCP_FIN_WAIT1: - if(StreamTcpPacketStateFinWait1(tv, p, stt, ssn, pq)) - SCReturnInt(-1); + if(StreamTcpPacketStateFinWait1(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } break; case TCP_FIN_WAIT2: - if(StreamTcpPacketStateFinWait2(tv, p, stt, ssn, pq)) - SCReturnInt(-1); + if(StreamTcpPacketStateFinWait2(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } break; case TCP_CLOSING: - if(StreamTcpPacketStateClosing(tv, p, stt, ssn, pq)) - SCReturnInt(-1); + if(StreamTcpPacketStateClosing(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } break; case TCP_CLOSE_WAIT: - if(StreamTcpPacketStateCloseWait(tv, p, stt, ssn, pq)) - SCReturnInt(-1); + if(StreamTcpPacketStateCloseWait(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } break; case TCP_LAST_ACK: - if(StreamTcpPakcetStateLastAck(tv, p, stt, ssn, pq)) - SCReturnInt(-1); + if(StreamTcpPakcetStateLastAck(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } break; case TCP_TIME_WAIT: - if(StreamTcpPacketStateTimeWait(tv, p, stt, ssn, pq)) - SCReturnInt(-1); + 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. @@ -2721,7 +2746,7 @@ static int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt, { SCLogDebug("reusing closed TCP session"); - if (StreamTcpPacketStateNone(tv,p,stt,ssn)) { + if (StreamTcpPacketStateNone(tv,p,stt,ssn, &stt->pseudo_queue)) { SCReturnInt(-1); } } else { @@ -2738,11 +2763,48 @@ static int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt, } } + /* deal with a pseudo packet that is created upon receiving a RST + * segment. To be sure we process both sides of the connection, we + * inject a fake packet into the system, forcing reassembly of the + * opposing direction. + * There should be only one, but to be sure we do a while loop. */ + while (stt->pseudo_queue.len > 0) { + SCLogDebug("processing pseudo packet / stream end"); + Packet *np = PacketDequeue(&stt->pseudo_queue); + if (np != NULL) { + /* process the opposing direction of the original packet */ + if (PKT_IS_TOSERVER(np)) { + SCLogDebug("pseudo packet is to server"); + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, np, NULL); + } else { + SCLogDebug("pseudo packet is to client"); + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, np, NULL); + } + + /* enqueue this packet so we inspect it in detect etc */ + PacketEnqueue(pq, np); + } + SCLogDebug("processing pseudo packet / stream end done"); + } + /* Process stream smsgs we may have in queue */ - if (StreamTcpReassembleProcessAppLayer(stt->ra_ctx) < 0) - SCReturnInt(-1); + if (StreamTcpReassembleProcessAppLayer(stt->ra_ctx) < 0) { + goto error; + } SCReturnInt(0); + +error: + /* make sure we don't leave packets in our pseudo queue */ + while (stt->pseudo_queue.len > 0) { + Packet *np = PacketDequeue(&stt->pseudo_queue); + if (np != NULL) { + PacketEnqueue(pq, np); + } + } + SCReturnInt(-1); } /** @@ -3210,6 +3272,156 @@ void StreamTcpSetSessionNoReassemblyFlag (TcpSession *ssn, char direction) (ssn->client.flags |= STREAMTCP_STREAM_FLAG_NOREASSEMBLY); } +/** + * \brief Function to fetch a packet from the packet allocation queue for + * creation of the pseudo packet from the reassembled stream. + * + * @param parent Pointer to the parent of the pseudo packet + * @param pkt pointer to the raw packet of the parent + * @param len length of the packet + * @return upon success returns the pointer to the new pseudo packet + * otherwise NULL + */ +Packet *StreamTcpPseudoSetup(Packet *parent, uint8_t *pkt, uint32_t len) +{ + Packet *p = PacketGetFromQueueOrAlloc(); + if (p == NULL || len == 0) { + return NULL; + } + + /* set the root ptr to the lowest layer */ + if (parent->root != NULL) + p->root = parent->root; + else + p->root = parent; + + /* copy packet and set lenght, proto */ + p->tunnel_proto = parent->proto; + p->pktlen = len; + memcpy(&p->pkt, pkt, (len - parent->payload_len)); + p->recursion_level = parent->recursion_level + 1; + p->ts.tv_sec = parent->ts.tv_sec; + p->ts.tv_usec = parent->ts.tv_usec; + + p->flow = parent->flow; + + /* set tunnel flags */ + + /* tell new packet it's part of a tunnel */ + SET_TUNNEL_PKT(p); + /* tell parent packet it's part of a tunnel */ + SET_TUNNEL_PKT(parent); + + /* increment tunnel packet refcnt in the root packet */ + TUNNEL_INCR_PKT_TPR(p); + + return p; +} + +/** + * \brief Function to setup the IP and TCP header of the pseudo packet from + * the newly copied raw packet contents of the parent. + * + * @param np pointer to the pseudo packet + * @param p pointer to the original packet + */ +void StreamTcpPseudoPacketSetupHeader(Packet *np, Packet *p) +{ + /* Setup the IP header */ + if (PKT_IS_IPV4(p)) { + np->ip4h = (IPV4Hdr *)(np->pkt + (np->pktlen - IPV4_GET_IPLEN(p))); + PSUEDO_PKT_SET_IPV4HDR(np->ip4h, p->ip4h); + + /* Similarly setup the TCP header with ports in opposite direction */ + np->tcph = (TCPHdr *)(np->ip4h + IPV4_GET_HLEN(p)); + PSUEDO_PKT_SET_TCPHDR(np->tcph, p->tcph); + + /* Setup the adress and port details */ + SET_IPV4_SRC_ADDR(np, &np->src); + SET_IPV4_DST_ADDR(np, &np->dst); + SET_TCP_SRC_PORT(np, &np->sp); + SET_TCP_DST_PORT(np, &np->dp); + + } else if (PKT_IS_IPV6(p)) { + np->ip6h = (IPV6Hdr *)(np->pkt + (np->pktlen - IPV6_GET_PLEN(p) - IPV6_HEADER_LEN)); + PSUEDO_PKT_SET_IPV6HDR(np->ip6h, p->ip6h); + + /* Similarly setup the TCP header with ports in opposite direction */ + np->tcph = (TCPHdr *)(np->ip6h + IPV6_HEADER_LEN); + PSUEDO_PKT_SET_TCPHDR(np->tcph, p->tcph); + + /* Setup the adress and port details */ + SET_IPV6_SRC_ADDR(np, &np->src); + SET_IPV6_DST_ADDR(np, &np->dst); + SET_TCP_SRC_PORT(np, &np->sp); + SET_TCP_DST_PORT(np, &np->dp); + } + + /* Setup the payload pointer to the starting of payload location as in the + original packet, so that we don't overwrite the protocols headers */ + np->payload = NULL; //= np->pkt + (np->pktlen - p->payload_len); + np->payload_len = 0; +} + +/** \brief Create a pseudo packet injected into the engine to signal the + * opposing direction of this stream to wrap up stream reassembly. + * + * \param p real packet + * \param pq packet queue to store the new pseudo packet in + */ +void StreamTcpPseudoPacketCreateStreamEndPacket(Packet *p, TcpSession *ssn, PacketQueue *pq) +{ + SCEnter(); + + /* no need for a pseudo packet if there is nothing left to reassemble */ + if (PKT_IS_TOSERVER(p)) { + if (ssn->server.seg_list == NULL) { + SCReturn; + } + } else if (PKT_IS_TOCLIENT(p)) { + if (ssn->client.seg_list == NULL) { + SCReturn; + } + } + + Packet *np = StreamTcpPseudoSetup(p, p->pkt, p->pktlen); + if (np == NULL) { + SCLogDebug("The packet received from packet allocation is NULL"); + SCReturn; + } + + /* Setup the IP and TCP headers */ + StreamTcpPseudoPacketSetupHeader(np,p); + + np->flags |= PKT_STREAM_EOF; + np->flags |= PKT_HAS_FLOW; + np->flags |= PKT_PSEUDO_STREAM_END; + + np->flowflags = p->flowflags; + + if (PKT_IS_TOSERVER(p)) { + SCLogDebug("original is to_server, so pseudo is to_client"); + np->flowflags &= ~FLOW_PKT_TOSERVER; + np->flowflags |= FLOW_PKT_TOCLIENT; +#ifdef DEBUG + BUG_ON(!(PKT_IS_TOCLIENT(np))); + BUG_ON((PKT_IS_TOSERVER(np))); +#endif + } else if (PKT_IS_TOCLIENT(p)) { + SCLogDebug("original is to_client, so pseudo is to_server"); + np->flowflags &= ~FLOW_PKT_TOCLIENT; + np->flowflags |= FLOW_PKT_TOSERVER; +#ifdef DEBUG + BUG_ON(!(PKT_IS_TOSERVER(np))); + BUG_ON((PKT_IS_TOCLIENT(np))); +#endif + } + + PacketEnqueue(pq, np); + + SCReturn; +} + #ifdef UNITTESTS /** diff --git a/src/stream-tcp.h b/src/stream-tcp.h index 0f742e070b..eedee6e8ba 100644 --- a/src/stream-tcp.h +++ b/src/stream-tcp.h @@ -63,6 +63,8 @@ void StreamTcpIncrMemuse(uint32_t); void StreamTcpDecrMemuse(uint32_t); int StreamTcpCheckMemcap(uint32_t); +void StreamTcpPseudoPacketSetupHeader(Packet *, Packet *); +Packet *StreamTcpPseudoSetup(Packet *, uint8_t *, uint32_t); /** ------- Inline functions: ------ */ diff --git a/src/stream.c b/src/stream.c index 1f0642486e..c0967cef39 100644 --- a/src/stream.c +++ b/src/stream.c @@ -154,7 +154,7 @@ void StreamMsgPutInQueue(StreamMsgQueue *q, StreamMsg *s) void StreamMsgQueuesInit(void) { SCMutexInit(&stream_pool_memuse_mutex, NULL); - stream_msg_pool = PoolInit(5000,250,StreamMsgAlloc,NULL,StreamMsgFree); + stream_msg_pool = PoolInit(0,250,StreamMsgAlloc,NULL,StreamMsgFree); if (stream_msg_pool == NULL) exit(EXIT_FAILURE); /* XXX */ }