|
|
|
/* Copyright (C) 2007-2016 Open Information Security Foundation
|
|
|
|
*
|
|
|
|
* You can copy, redistribute or modify this Program under the terms of
|
|
|
|
* the GNU General Public License version 2 as published by the Free
|
|
|
|
* Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* version 2 along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
* 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \file
|
|
|
|
*
|
|
|
|
* \author Victor Julien <victor@inliniac.net>
|
|
|
|
*
|
|
|
|
* Functions for the "inline mode" of the stream engine.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "suricata-common.h"
|
|
|
|
#include "stream-tcp-private.h"
|
|
|
|
#include "stream-tcp-inline.h"
|
|
|
|
|
|
|
|
#include "util-memcmp.h"
|
|
|
|
#include "util-print.h"
|
|
|
|
|
|
|
|
#include "util-unittest.h"
|
|
|
|
#include "util-unittest-helper.h"
|
|
|
|
|
|
|
|
/** defined in stream-tcp-reassemble.c */
|
|
|
|
extern int stream_inline;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief See if stream engine is operating in inline mode
|
|
|
|
*
|
|
|
|
* \retval 0 no
|
|
|
|
* \retval 1 yes
|
|
|
|
*/
|
|
|
|
int StreamTcpInlineMode(void)
|
|
|
|
{
|
|
|
|
return stream_inline;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Compare the shared data portion of two segments
|
|
|
|
*
|
|
|
|
* If no data is shared, 0 will be returned.
|
|
|
|
*
|
|
|
|
* \param seg1 first segment
|
|
|
|
* \param seg2 second segment
|
|
|
|
*
|
|
|
|
* \retval 0 shared data is the same (or no data is shared)
|
|
|
|
* \retval 1 shared data is different
|
|
|
|
*/
|
|
|
|
int StreamTcpInlineSegmentCompare(TcpStream *stream, Packet *p, TcpSegment *seg)
|
|
|
|
{
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
if (p == NULL || seg == NULL) {
|
|
|
|
SCReturnInt(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t *seg_data;
|
|
|
|
uint32_t seg_datalen;
|
|
|
|
StreamingBufferSegmentGetData(stream->sb, &seg->sbseg, &seg_data, &seg_datalen);
|
|
|
|
if (seg_data == NULL || seg_datalen == 0)
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
|
|
|
const uint32_t pkt_seq = TCP_GET_SEQ(p);
|
|
|
|
|
|
|
|
if (SEQ_EQ(pkt_seq, seg->seq) && p->payload_len == seg_datalen) {
|
|
|
|
int r = SCMemcmp(p->payload, seg_data, seg_datalen);
|
|
|
|
SCReturnInt(r);
|
|
|
|
} else if (SEQ_GT(pkt_seq, (seg->seq + seg_datalen))) {
|
|
|
|
SCReturnInt(0);
|
|
|
|
} else if (SEQ_GT(seg->seq, (pkt_seq + p->payload_len))) {
|
|
|
|
SCReturnInt(0);
|
|
|
|
} else {
|
|
|
|
SCLogDebug("p %u (%u), seg2 %u (%u)", pkt_seq,
|
|
|
|
p->payload_len, seg->seq, seg_datalen);
|
|
|
|
|
|
|
|
uint32_t pkt_end = pkt_seq + p->payload_len;
|
|
|
|
uint32_t seg_end = seg->seq + seg_datalen;
|
|
|
|
SCLogDebug("pkt_end %u, seg_end %u", pkt_end, seg_end);
|
|
|
|
|
|
|
|
/* get the minimal seg*_end */
|
|
|
|
uint32_t end = (SEQ_GT(pkt_end, seg_end)) ? seg_end : pkt_end;
|
|
|
|
/* and the max seq */
|
|
|
|
uint32_t seq = (SEQ_LT(pkt_seq, seg->seq)) ? seg->seq : pkt_seq;
|
|
|
|
|
|
|
|
SCLogDebug("seq %u, end %u", seq, end);
|
|
|
|
|
|
|
|
uint16_t pkt_off = seq - pkt_seq;
|
|
|
|
uint16_t seg_off = seq - seg->seq;
|
|
|
|
SCLogDebug("pkt_off %u, seg_off %u", pkt_off, seg_off);
|
|
|
|
|
|
|
|
uint32_t range = end - seq;
|
|
|
|
SCLogDebug("range %u", range);
|
|
|
|
BUG_ON(range > 65536);
|
|
|
|
|
|
|
|
if (range) {
|
|
|
|
int r = SCMemcmp(p->payload + pkt_off, seg_data + seg_off, range);
|
|
|
|
SCReturnInt(r);
|
|
|
|
}
|
|
|
|
SCReturnInt(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Replace (part of) the payload portion of a packet by the data
|
|
|
|
* in a TCP segment
|
|
|
|
*
|
|
|
|
* \param p Packet
|
|
|
|
* \param seg TCP segment
|
|
|
|
*
|
|
|
|
* \todo What about reassembled fragments?
|
|
|
|
* \todo What about unwrapped tunnel packets?
|
|
|
|
*/
|
|
|
|
void StreamTcpInlineSegmentReplacePacket(TcpStream *stream, Packet *p, TcpSegment *seg)
|
|
|
|
{
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
uint32_t pseq = TCP_GET_SEQ(p);
|
|
|
|
uint32_t tseq = seg->seq;
|
|
|
|
|
|
|
|
/* check if segment is within the packet */
|
|
|
|
if (tseq + TCP_SEG_LEN(seg) < pseq) {
|
|
|
|
SCReturn;
|
|
|
|
} else if (pseq + p->payload_len < tseq) {
|
|
|
|
SCReturn;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t *seg_data;
|
|
|
|
uint32_t seg_datalen;
|
|
|
|
StreamingBufferSegmentGetData(stream->sb, &seg->sbseg, &seg_data, &seg_datalen);
|
|
|
|
|
|
|
|
uint32_t pend = pseq + p->payload_len;
|
|
|
|
uint32_t tend = tseq + seg_datalen;
|
|
|
|
SCLogDebug("pend %u, tend %u", pend, tend);
|
|
|
|
|
|
|
|
/* get the minimal seg*_end */
|
|
|
|
uint32_t end = (SEQ_GT(pend, tend)) ? tend : pend;
|
|
|
|
/* and the max seq */
|
|
|
|
uint32_t seq = (SEQ_LT(pseq, tseq)) ? tseq : pseq;
|
|
|
|
SCLogDebug("seq %u, end %u", seq, end);
|
|
|
|
|
|
|
|
uint16_t poff = seq - pseq;
|
|
|
|
uint16_t toff = seq - tseq;
|
|
|
|
SCLogDebug("poff %u, toff %u", poff, toff);
|
|
|
|
|
|
|
|
uint32_t range = end - seq;
|
|
|
|
SCLogDebug("range %u", range);
|
|
|
|
BUG_ON(range > 65536);
|
|
|
|
|
|
|
|
if (range) {
|
|
|
|
/* update the packets payload. As payload is a ptr to either
|
|
|
|
* p->pkt or p->ext_pkt that is updated as well */
|
|
|
|
memcpy(p->payload+poff, seg_data+toff, range);
|
|
|
|
|
|
|
|
/* flag as modified so we can reinject / replace after
|
|
|
|
* recalculating the checksum */
|
|
|
|
p->flags |= PKT_STREAM_MODIFIED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef UNITTESTS
|
|
|
|
|
|
|
|
#include "stream-tcp-util.h"
|
|
|
|
|
|
|
|
static int VALIDATE(TcpStream *stream, uint8_t *data, uint32_t data_len)
|
|
|
|
{
|
|
|
|
if (StreamingBufferCompareRawData(stream->sb,
|
|
|
|
data, data_len) == 0)
|
|
|
|
{
|
|
|
|
SCReturnInt(0);
|
|
|
|
}
|
|
|
|
SCLogInfo("OK");
|
|
|
|
PrintRawDataFp(stdout, data, data_len);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define INLINE_START(isn) \
|
|
|
|
Packet *p; \
|
|
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL; \
|
|
|
|
TcpSession ssn; \
|
|
|
|
ThreadVars tv; \
|
|
|
|
memset(&tv, 0, sizeof(tv)); \
|
|
|
|
\
|
|
|
|
StreamTcpUTInit(&ra_ctx); \
|
|
|
|
StreamTcpUTInitInline(); \
|
|
|
|
\
|
|
|
|
StreamTcpUTSetupSession(&ssn); \
|
|
|
|
StreamTcpUTSetupStream(&ssn.server, (isn)); \
|
|
|
|
StreamTcpUTSetupStream(&ssn.client, (isn)); \
|
|
|
|
\
|
|
|
|
TcpStream *stream = &ssn.client;
|
|
|
|
|
|
|
|
#define INLINE_END \
|
|
|
|
StreamTcpUTClearSession(&ssn); \
|
|
|
|
StreamTcpUTDeinit(ra_ctx); \
|
|
|
|
PASS
|
|
|
|
|
|
|
|
#define INLINE_STEP(rseq, seg, seglen, buf, buflen, packet, packetlen) \
|
|
|
|
p = UTHBuildPacketReal((uint8_t *)(seg), (seglen), IPPROTO_TCP, "1.1.1.1", "2.2.2.2", 1024, 80); \
|
|
|
|
FAIL_IF(p == NULL); \
|
|
|
|
p->tcph->th_seq = htonl(stream->isn + (rseq)); \
|
|
|
|
p->tcph->th_ack = htonl(31); \
|
|
|
|
FAIL_IF (StreamTcpReassembleHandleSegmentHandleData(&tv, ra_ctx, &ssn, stream, p) < 0); \
|
|
|
|
FAIL_IF (memcmp(p->payload, packet, MIN((packetlen),p->payload_len)) != 0); \
|
|
|
|
UTHFreePacket(p); \
|
|
|
|
FAIL_IF(!(VALIDATE(stream, (uint8_t *)(buf), (buflen))));
|
|
|
|
|
|
|
|
/** \test full overlap */
|
|
|
|
static int StreamTcpInlineTest01(void)
|
|
|
|
{
|
|
|
|
INLINE_START(0);
|
|
|
|
INLINE_STEP(1, "AAC", 3, "AAC", 3, "AAC", 3);
|
|
|
|
INLINE_STEP(1, "ABC", 3, "AAC", 3, "AAC", 3);
|
|
|
|
INLINE_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \test full overlap */
|
|
|
|
static int StreamTcpInlineTest02(void)
|
|
|
|
{
|
|
|
|
INLINE_START(0);
|
|
|
|
INLINE_STEP(1, "ABCDE", 5, "ABCDE", 5, "ABCDE", 5);
|
|
|
|
INLINE_STEP(2, "xxx", 3, "ABCDE", 5, "BCD", 3);
|
|
|
|
INLINE_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \test partial overlap */
|
|
|
|
static int StreamTcpInlineTest03(void)
|
|
|
|
{
|
|
|
|
INLINE_START(0);
|
|
|
|
INLINE_STEP(1, "ABCDE", 5, "ABCDE", 5, "ABCDE", 5);
|
|
|
|
INLINE_STEP(3, "xxxxx", 5, "ABCDExx", 7, "CDExx", 5);
|
|
|
|
INLINE_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \test partial overlap */
|
|
|
|
static int StreamTcpInlineTest04(void)
|
|
|
|
{
|
|
|
|
INLINE_START(0);
|
|
|
|
INLINE_STEP(3, "ABCDE", 5, "\0\0ABCDE", 7, "ABCDE", 5);
|
|
|
|
INLINE_STEP(1, "xxxxx", 5, "xxABCDE", 7, "xxABC", 5);
|
|
|
|
INLINE_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \test no overlap */
|
|
|
|
static int StreamTcpInlineTest05(void)
|
|
|
|
{
|
|
|
|
INLINE_START(0);
|
|
|
|
INLINE_STEP(8, "ABCDE", 5, "\0\0\0\0\0\0\0ABCDE", 12, "ABCDE", 5);
|
|
|
|
INLINE_STEP(1, "xxxxx", 5, "xxxxx\0\0ABCDE", 12, "xxxxx", 5);
|
|
|
|
INLINE_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \test multiple overlaps */
|
|
|
|
static int StreamTcpInlineTest06(void)
|
|
|
|
{
|
|
|
|
INLINE_START(0);
|
|
|
|
INLINE_STEP(2, "A", 1, "\0A", 2, "A", 1);
|
|
|
|
INLINE_STEP(4, "A", 1, "\0A\0A", 4, "A", 1);
|
|
|
|
INLINE_STEP(6, "A", 1, "\0A\0A\0A", 6, "A", 1);
|
|
|
|
INLINE_STEP(8, "A", 1, "\0A\0A\0A\0A", 8, "A", 1);
|
|
|
|
INLINE_STEP(1, "xxxxxxxxx", 9, "xAxAxAxAx", 9, "xAxAxAxAx", 9);
|
|
|
|
INLINE_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \test overlap, data not different */
|
|
|
|
static int StreamTcpInlineTest07(void)
|
|
|
|
{
|
|
|
|
INLINE_START(0);
|
|
|
|
INLINE_STEP(3, "ABCDE", 5, "\0\0ABCDE", 7, "ABCDE", 5);
|
|
|
|
INLINE_STEP(1, "XXABC", 5, "XXABCDE", 7, "XXABC", 5);
|
|
|
|
INLINE_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int StreamTcpInlineTest08(void)
|
|
|
|
{
|
|
|
|
INLINE_START(0);
|
|
|
|
INLINE_STEP(1, "AAAAA", 5, "AAAAA", 5, "AAAAA", 5);
|
|
|
|
INLINE_STEP(1, "BBBBB", 5, "AAAAA", 5, "AAAAA", 5);
|
|
|
|
INLINE_STEP(1, "CCCCCCCCCC", 10, "AAAAACCCCC", 10, "AAAAACCCCC", 10);
|
|
|
|
INLINE_STEP(10, "X", 1, "AAAAACCCCC", 10, "C", 1);
|
|
|
|
INLINE_STEP(11, "X", 1, "AAAAACCCCCX", 11, "X", 1);
|
|
|
|
INLINE_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* UNITTESTS */
|
|
|
|
|
|
|
|
void StreamTcpInlineRegisterTests(void)
|
|
|
|
{
|
|
|
|
#ifdef UNITTESTS
|
|
|
|
UtRegisterTest("StreamTcpInlineTest01", StreamTcpInlineTest01);
|
|
|
|
UtRegisterTest("StreamTcpInlineTest02", StreamTcpInlineTest02);
|
|
|
|
UtRegisterTest("StreamTcpInlineTest03", StreamTcpInlineTest03);
|
|
|
|
UtRegisterTest("StreamTcpInlineTest04", StreamTcpInlineTest04);
|
|
|
|
UtRegisterTest("StreamTcpInlineTest05", StreamTcpInlineTest05);
|
|
|
|
UtRegisterTest("StreamTcpInlineTest06", StreamTcpInlineTest06);
|
|
|
|
UtRegisterTest("StreamTcpInlineTest07", StreamTcpInlineTest07);
|
|
|
|
UtRegisterTest("StreamTcpInlineTest08", StreamTcpInlineTest08);
|
|
|
|
#endif /* UNITTESTS */
|
|
|
|
}
|
|
|
|
|