mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
702 lines
23 KiB
C
702 lines
23 KiB
C
/* Copyright (C) 2011-2021 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.
|
|
*/
|
|
|
|
/**
|
|
* \ingroup decode
|
|
*
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*
|
|
* \author Eric Leblond <eric@regit.org>
|
|
*
|
|
* Decode SCTP
|
|
*/
|
|
|
|
#include "suricata-common.h"
|
|
#include "decode.h"
|
|
#include "decode-sctp.h"
|
|
#include "decode-events.h"
|
|
|
|
#include "util-validate.h"
|
|
#include "util-unittest.h"
|
|
#include "util-debug.h"
|
|
#include "util-optimize.h"
|
|
#include "flow.h"
|
|
|
|
/**
|
|
* \brief Parse SCTP chunks after the common header.
|
|
*
|
|
* Iterates over chunks, validates each chunk header, and populates
|
|
* SCTPVars with chunk metadata.
|
|
*
|
|
* \param p Packet to decode
|
|
* \param pkt Pointer to the start of chunk data (after 12-byte common header)
|
|
* \param len Length of chunk data remaining
|
|
*
|
|
* \retval 0 on success (even if some events were set)
|
|
* \retval -1 on fatal error (packet should be rejected)
|
|
*/
|
|
static int DecodeSCTPChunks(Packet *p, const uint8_t *pkt, uint16_t len)
|
|
{
|
|
const SCTPHdr *sctph = PacketGetSCTP(p);
|
|
const uint32_t vtag = SCTP_GET_RAW_VTAG(sctph);
|
|
uint32_t offset = 0;
|
|
uint16_t chunk_cnt = 0;
|
|
uint8_t tracked_chunk_cnt = 0;
|
|
bool has_init = false;
|
|
bool has_init_ack = false;
|
|
bool has_data = false;
|
|
bool has_abort = false;
|
|
uint8_t data_chunk_cnt = 0;
|
|
int ret = 0;
|
|
|
|
while (offset < len) {
|
|
/* need at least a chunk header */
|
|
if (len - offset < SCTP_CHUNK_HDR_LEN) {
|
|
ENGINE_SET_INVALID_EVENT(p, SCTP_CHUNK_TOO_SMALL);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
SCTPChunkHdr chunk;
|
|
memcpy(&chunk, pkt + offset, sizeof(chunk));
|
|
const uint16_t chunk_len = SCNtohs(chunk.length);
|
|
|
|
/* RFC 4960 sec 3.2: chunk length includes the header and must be >= 4 */
|
|
if (chunk_len < SCTP_CHUNK_HDR_LEN) {
|
|
ENGINE_SET_INVALID_EVENT(p, SCTP_CHUNK_LEN_INVALID);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
/* chunk must not extend beyond available data */
|
|
if (chunk_len > (len - offset)) {
|
|
ENGINE_SET_INVALID_EVENT(p, SCTP_CHUNK_LEN_INVALID);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
if (chunk_cnt < SCTP_MAX_TRACKED_CHUNKS) {
|
|
p->l4.vars.sctp.chunk_types[chunk_cnt] = chunk.type;
|
|
tracked_chunk_cnt++;
|
|
} else if (chunk_cnt == SCTP_MAX_TRACKED_CHUNKS) {
|
|
ENGINE_SET_EVENT(p, SCTP_TOO_MANY_CHUNKS);
|
|
}
|
|
chunk_cnt++;
|
|
|
|
switch (chunk.type) {
|
|
case SCTP_CHUNK_TYPE_INIT:
|
|
has_init = true;
|
|
/* RFC 4960 sec 8.5.1: INIT must have vtag == 0 */
|
|
if (vtag != 0) {
|
|
ENGINE_SET_EVENT(p, SCTP_INIT_WITH_NON_ZERO_VTAG);
|
|
}
|
|
break;
|
|
case SCTP_CHUNK_TYPE_INIT_ACK:
|
|
has_init_ack = true;
|
|
break;
|
|
case SCTP_CHUNK_TYPE_DATA:
|
|
if (data_chunk_cnt < SCTP_MAX_DATA_CHUNKS && chunk_len >= SCTP_DATA_CHUNK_HDR_LEN) {
|
|
p->l4.vars.sctp.data_offsets[data_chunk_cnt] =
|
|
(uint16_t)(SCTP_HEADER_LEN + offset + SCTP_DATA_CHUNK_HDR_LEN);
|
|
p->l4.vars.sctp.data_lens[data_chunk_cnt] = chunk_len - SCTP_DATA_CHUNK_HDR_LEN;
|
|
data_chunk_cnt++;
|
|
} else if (data_chunk_cnt == SCTP_MAX_DATA_CHUNKS &&
|
|
chunk_len >= SCTP_DATA_CHUNK_HDR_LEN) {
|
|
ENGINE_SET_EVENT(p, SCTP_TOO_MANY_DATA_CHUNKS);
|
|
}
|
|
has_data = true;
|
|
/* DATA chunks must not have vtag == 0 */
|
|
if (vtag == 0) {
|
|
ENGINE_SET_EVENT(p, SCTP_DATA_WITH_ZERO_VTAG);
|
|
}
|
|
break;
|
|
case SCTP_CHUNK_TYPE_ABORT:
|
|
has_abort = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* advance to next chunk: padded to 4-byte boundary (RFC 4960 sec 3.2) */
|
|
uint32_t padded_len = (chunk_len + 3) & ~3U;
|
|
/* guard against infinite loop with zero-padding overshoot */
|
|
if (padded_len < SCTP_CHUNK_HDR_LEN) {
|
|
padded_len = SCTP_CHUNK_HDR_LEN;
|
|
}
|
|
offset += padded_len;
|
|
DEBUG_VALIDATE_BUG_ON(offset > (uint32_t)len + 3);
|
|
}
|
|
|
|
/* RFC 4960 sec 6.10: INIT/INIT_ACK must be the only chunk in the packet */
|
|
if ((has_init || has_init_ack) && chunk_cnt > 1) {
|
|
ENGINE_SET_EVENT(p, SCTP_INIT_CHUNK_BUNDLED);
|
|
}
|
|
|
|
p->l4.vars.sctp.hlen = (uint16_t)(SCTP_HEADER_LEN + MIN(offset, len));
|
|
p->l4.vars.sctp.chunk_cnt = chunk_cnt;
|
|
p->l4.vars.sctp.tracked_chunk_cnt = tracked_chunk_cnt;
|
|
p->l4.vars.sctp.data_chunk_cnt = data_chunk_cnt;
|
|
p->l4.vars.sctp.has_init = has_init;
|
|
p->l4.vars.sctp.has_init_ack = has_init_ack;
|
|
p->l4.vars.sctp.has_data = has_data;
|
|
p->l4.vars.sctp.has_abort = has_abort;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int DecodeSCTPPacket(ThreadVars *tv, Packet *p, const uint8_t *pkt, uint16_t len)
|
|
{
|
|
DEBUG_VALIDATE_BUG_ON(pkt == NULL);
|
|
|
|
if (unlikely(len < SCTP_HEADER_LEN)) {
|
|
ENGINE_SET_INVALID_EVENT(p, SCTP_PKT_TOO_SMALL);
|
|
return -1;
|
|
}
|
|
|
|
const SCTPHdr *sctph = PacketSetSCTP(p, pkt);
|
|
|
|
p->sp = SCTP_GET_RAW_SRC_PORT(sctph);
|
|
p->dp = SCTP_GET_RAW_DST_PORT(sctph);
|
|
p->payload = (uint8_t *)pkt + SCTP_HEADER_LEN;
|
|
p->payload_len = len - SCTP_HEADER_LEN;
|
|
p->proto = IPPROTO_SCTP;
|
|
|
|
if (p->payload_len > 0) {
|
|
if (DecodeSCTPChunks(p, p->payload, p->payload_len) < 0) {
|
|
p->payload_len = 0;
|
|
return -1;
|
|
}
|
|
|
|
if (p->l4.vars.sctp.data_chunk_cnt > 0) {
|
|
/* Point p->payload at the user data of the first DATA chunk,
|
|
* past the SCTP common header and the 16-byte DATA chunk
|
|
* header. This is what content will inspect. */
|
|
p->payload = (uint8_t *)pkt + p->l4.vars.sctp.data_offsets[0];
|
|
p->payload_len = p->l4.vars.sctp.data_lens[0];
|
|
} else {
|
|
p->payload_len = 0;
|
|
}
|
|
} else {
|
|
p->l4.vars.sctp.hlen = SCTP_HEADER_LEN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int DecodeSCTP(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, const uint8_t *pkt, uint16_t len)
|
|
{
|
|
StatsCounterIncr(&tv->stats, dtv->counter_sctp);
|
|
|
|
if (unlikely(DecodeSCTPPacket(tv, p, pkt, len) < 0)) {
|
|
PacketClearL4(p);
|
|
return TM_ECODE_FAILED;
|
|
}
|
|
|
|
SCLogDebug("SCTP sp: %u -> dp: %u", p->sp, p->dp);
|
|
|
|
if (p->l4.vars.sctp.has_init) {
|
|
StatsCounterIncr(&tv->stats, dtv->counter_sctp_init);
|
|
}
|
|
if (p->l4.vars.sctp.has_init_ack) {
|
|
StatsCounterIncr(&tv->stats, dtv->counter_sctp_init_ack);
|
|
}
|
|
if (p->l4.vars.sctp.has_data) {
|
|
StatsCounterIncr(&tv->stats, dtv->counter_sctp_data);
|
|
}
|
|
if (p->l4.vars.sctp.has_abort) {
|
|
StatsCounterIncr(&tv->stats, dtv->counter_sctp_abort);
|
|
}
|
|
for (uint8_t i = 0; i < p->l4.vars.sctp.tracked_chunk_cnt; i++) {
|
|
if (p->l4.vars.sctp.chunk_types[i] == SCTP_CHUNK_TYPE_SHUTDOWN) {
|
|
StatsCounterIncr(&tv->stats, dtv->counter_sctp_shutdown);
|
|
break;
|
|
}
|
|
}
|
|
|
|
FlowSetupPacket(p);
|
|
|
|
return TM_ECODE_OK;
|
|
}
|
|
|
|
#ifdef UNITTESTS
|
|
|
|
/** \test Valid SCTP packet with INIT chunk */
|
|
static int SCTPDecodeValidInitTest01(void)
|
|
{
|
|
/* SCTP common header: sport=1234 dport=80 vtag=0 checksum=0
|
|
* followed by INIT chunk: type=0x01 flags=0x00 length=20
|
|
* with 16 bytes of INIT-specific data (initiate_tag, a_rwnd, etc.) */
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x00, /* vtag=0 */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
0x01, 0x00, 0x00, 0x14, /* chunk: INIT, flags=0, len=20 */
|
|
0x00, 0x00, 0x00, 0x01, /* initiate_tag=1 */
|
|
0x00, 0x01, 0x00, 0x00, /* a_rwnd=65536 */
|
|
0x00, 0x01, 0x00, 0x01, /* num_outbound=1, num_inbound=1 */
|
|
0x00, 0x00, 0x00, 0x01, /* initial_tsn=1 */
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF_NOT(PacketIsSCTP(p));
|
|
|
|
FAIL_IF(p->sp != 1234);
|
|
FAIL_IF(p->dp != 80);
|
|
FAIL_IF(p->l4.vars.sctp.chunk_types[0] != SCTP_CHUNK_TYPE_INIT);
|
|
FAIL_IF(p->l4.vars.sctp.chunk_cnt != 1);
|
|
FAIL_IF(!p->l4.vars.sctp.has_init);
|
|
FAIL_IF(p->l4.vars.sctp.has_data);
|
|
FAIL_IF(p->l4.vars.sctp.has_abort);
|
|
|
|
/* no protocol violation events expected */
|
|
FAIL_IF(ENGINE_ISSET_EVENT(p, SCTP_INIT_WITH_NON_ZERO_VTAG));
|
|
FAIL_IF(ENGINE_ISSET_EVENT(p, SCTP_INIT_CHUNK_BUNDLED));
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test Packet too small (< 12 bytes) */
|
|
static int SCTPDecodePktTooSmallTest02(void)
|
|
{
|
|
uint8_t raw_sctp[] = { 0x04, 0xd2, 0x00, 0x50, 0x00, 0x00 };
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
int ret = DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF(ret != TM_ECODE_FAILED);
|
|
FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, SCTP_PKT_TOO_SMALL));
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test Chunk too small - header + 2 bytes garbage (not enough for chunk header) */
|
|
static int SCTPDecodeChunkTooSmallTest03(void)
|
|
{
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x01, /* vtag=1 */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
0x01, 0x00, /* only 2 bytes of chunk data */
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
int ret = DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF(ret != TM_ECODE_FAILED);
|
|
FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, SCTP_CHUNK_TOO_SMALL));
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test Invalid chunk length (chunk_len < 4) */
|
|
static int SCTPDecodeChunkLenInvalidTest04(void)
|
|
{
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x01, /* vtag=1 */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
0x00, 0x00, 0x00, 0x02, /* chunk: DATA, flags=0, len=2 (invalid < 4) */
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
int ret = DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF(ret != TM_ECODE_FAILED);
|
|
FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, SCTP_CHUNK_LEN_INVALID));
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test INIT with non-zero verification tag */
|
|
static int SCTPDecodeInitNonZeroVtagTest05(void)
|
|
{
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x42, /* vtag=0x42 (non-zero, invalid for INIT) */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
0x01, 0x00, 0x00, 0x14, /* chunk: INIT, flags=0, len=20 */
|
|
0x00, 0x00, 0x00, 0x01, /* initiate_tag=1 */
|
|
0x00, 0x01, 0x00, 0x00, /* a_rwnd=65536 */
|
|
0x00, 0x01, 0x00, 0x01, /* num_outbound=1, num_inbound=1 */
|
|
0x00, 0x00, 0x00, 0x01, /* initial_tsn=1 */
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF_NOT(PacketIsSCTP(p));
|
|
FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, SCTP_INIT_WITH_NON_ZERO_VTAG));
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test Multiple chunks: DATA + SACK */
|
|
static int SCTPDecodeMultiChunkTest06(void)
|
|
{
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x01, /* vtag=1 */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
/* DATA chunk: type=0x00, flags=0x03, len=20 */
|
|
0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, /* TSN=0 */
|
|
0x00, 0x01, 0x00, 0x00, /* stream_id=1, stream_seq=0 */
|
|
0x00, 0x00, 0x00, 0x00, /* PPID=0 */
|
|
0x41, 0x42, 0x43, 0x44, /* data="ABCD" */
|
|
/* SACK chunk: type=0x03, flags=0x00, len=16 */
|
|
0x03, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, /* cumulative_tsn_ack=1 */
|
|
0x00, 0x01, 0x00, 0x00, /* a_rwnd=65536 */
|
|
0x00, 0x00, 0x00, 0x00, /* num_gap_blocks=0, num_dup_tsns=0 */
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF_NOT(PacketIsSCTP(p));
|
|
|
|
FAIL_IF(p->l4.vars.sctp.chunk_cnt != 2);
|
|
FAIL_IF(p->l4.vars.sctp.chunk_types[0] != SCTP_CHUNK_TYPE_DATA);
|
|
FAIL_IF(p->l4.vars.sctp.chunk_types[1] != SCTP_CHUNK_TYPE_SACK);
|
|
FAIL_IF(!p->l4.vars.sctp.has_data);
|
|
FAIL_IF(p->l4.vars.sctp.has_init);
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test INIT bundled with another chunk (violates RFC 4960 sec 6.10) */
|
|
static int SCTPDecodeInitNotAloneTest07(void)
|
|
{
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x00, /* vtag=0 */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
/* INIT chunk: type=0x01, flags=0, len=20 */
|
|
0x01, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, /* initiate_tag=1 */
|
|
0x00, 0x01, 0x00, 0x00, /* a_rwnd=65536 */
|
|
0x00, 0x01, 0x00, 0x01, /* num_outbound=1, num_inbound=1 */
|
|
0x00, 0x00, 0x00, 0x01, /* initial_tsn=1 */
|
|
/* DATA chunk: type=0x00, flags=0, len=16 (bundled illegally) */
|
|
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF_NOT(PacketIsSCTP(p));
|
|
FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, SCTP_INIT_CHUNK_BUNDLED));
|
|
FAIL_IF(p->l4.vars.sctp.chunk_cnt != 2);
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test DATA chunk payload extraction - p->payload points to user data */
|
|
static int SCTPDecodeDataPayloadTest08(void)
|
|
{
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x01, /* vtag=1 */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
/* DATA chunk: type=0x00, flags=0x03, len=20 (16 hdr + 4 data) */
|
|
0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, /* TSN=1 */
|
|
0x00, 0x01, 0x00, 0x00, /* stream_id=1, stream_seq=0 */
|
|
0x00, 0x00, 0x00, 0x00, /* PPID=0 */
|
|
0x41, 0x42, 0x43, 0x44, /* data="ABCD" */
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF_NOT(PacketIsSCTP(p));
|
|
|
|
FAIL_IF(p->payload_len != 4);
|
|
FAIL_IF(memcmp(p->payload, "ABCD", 4) != 0);
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test INIT-only packet - payload_len must be 0 (no application data) */
|
|
static int SCTPDecodeNoDataPayloadTest09(void)
|
|
{
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x00, /* vtag=0 */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
0x01, 0x00, 0x00, 0x14, /* chunk: INIT, flags=0, len=20 */
|
|
0x00, 0x00, 0x00, 0x01, /* initiate_tag=1 */
|
|
0x00, 0x01, 0x00, 0x00, /* a_rwnd=65536 */
|
|
0x00, 0x01, 0x00, 0x01, /* num_outbound=1, num_inbound=1 */
|
|
0x00, 0x00, 0x00, 0x01, /* initial_tsn=1 */
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF_NOT(PacketIsSCTP(p));
|
|
|
|
FAIL_IF(p->payload_len != 0);
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test Verify data_offset and data_len in SCTPVars */
|
|
static int SCTPDecodeDataOffsetTest10(void)
|
|
{
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x01, /* vtag=1 */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
/* DATA chunk: type=0x00, flags=0x03, len=20 (16 hdr + 4 data) */
|
|
0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, /* TSN=1 */
|
|
0x00, 0x01, 0x00, 0x00, /* stream_id=1, stream_seq=0 */
|
|
0x00, 0x00, 0x00, 0x00, /* PPID=0 */
|
|
0x41, 0x42, 0x43, 0x44, /* data="ABCD" */
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF_NOT(PacketIsSCTP(p));
|
|
|
|
/* data_offsets[0] = SCTP_HEADER_LEN(12) + chunk_offset(0) + DATA_CHUNK_HDR_LEN(16) = 28 */
|
|
FAIL_IF(p->l4.vars.sctp.data_chunk_cnt != 1);
|
|
FAIL_IF(p->l4.vars.sctp.data_offsets[0] != 28);
|
|
FAIL_IF(p->l4.vars.sctp.data_lens[0] != 4);
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test Two DATA chunks - both tracked, p->payload is first DATA's user data */
|
|
static int SCTPDecodeMultiDataTest11(void)
|
|
{
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x01, /* vtag=1 */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
/* DATA chunk 1: type=0x00, flags=0x02, len=20 (16 hdr + 4 data) */
|
|
0x00, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, /* TSN=1 */
|
|
0x00, 0x01, 0x00, 0x00, /* stream_id=1, stream_seq=0 */
|
|
0x00, 0x00, 0x00, 0x00, /* PPID=0 */
|
|
0x41, 0x42, 0x43, 0x44, /* data="ABCD" */
|
|
/* DATA chunk 2: type=0x00, flags=0x01, len=20 (16 hdr + 4 data) */
|
|
0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, /* TSN=2 */
|
|
0x00, 0x01, 0x00, 0x01, /* stream_id=1, stream_seq=1 */
|
|
0x00, 0x00, 0x00, 0x00, /* PPID=0 */
|
|
0x45, 0x46, 0x47, 0x48, /* data="EFGH" */
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF_NOT(PacketIsSCTP(p));
|
|
|
|
FAIL_IF(p->l4.vars.sctp.chunk_cnt != 2);
|
|
FAIL_IF(p->l4.vars.sctp.data_chunk_cnt != 2);
|
|
/* first DATA chunk: offset = 12 + 0 + 16 = 28, len = 4 */
|
|
FAIL_IF(p->l4.vars.sctp.data_offsets[0] != 28);
|
|
FAIL_IF(p->l4.vars.sctp.data_lens[0] != 4);
|
|
/* second DATA chunk: offset = 12 + 20 + 16 = 48, len = 4 */
|
|
FAIL_IF(p->l4.vars.sctp.data_offsets[1] != 48);
|
|
FAIL_IF(p->l4.vars.sctp.data_lens[1] != 4);
|
|
/* p->payload still points to first DATA's user data */
|
|
FAIL_IF(p->payload_len != 4);
|
|
FAIL_IF(memcmp(p->payload, "ABCD", 4) != 0);
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
/** \test DATA chunk with chunk_len < 16 - malformed, payload_len must be 0 */
|
|
static int SCTPDecodeSmallDataChunkTest12(void)
|
|
{
|
|
// clang-format off
|
|
uint8_t raw_sctp[] = {
|
|
0x04, 0xd2, 0x00, 0x50, /* sport=1234, dport=80 */
|
|
0x00, 0x00, 0x00, 0x01, /* vtag=1 */
|
|
0x00, 0x00, 0x00, 0x00, /* checksum=0 */
|
|
/* DATA chunk: type=0x00, flags=0x00, len=8 (< 16, malformed) */
|
|
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
|
|
0x00, /* 4 bytes of value (not enough for DATA header) */
|
|
};
|
|
// clang-format on
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
ThreadVars tv;
|
|
DecodeThreadVars dtv;
|
|
|
|
memset(&tv, 0, sizeof(ThreadVars));
|
|
memset(&dtv, 0, sizeof(DecodeThreadVars));
|
|
|
|
FlowInitConfig(FLOW_QUIET);
|
|
DecodeSCTP(&tv, &dtv, p, raw_sctp, sizeof(raw_sctp));
|
|
FAIL_IF_NOT(PacketIsSCTP(p));
|
|
|
|
FAIL_IF(p->l4.vars.sctp.data_chunk_cnt != 0);
|
|
FAIL_IF(p->payload_len != 0);
|
|
|
|
PacketFree(p);
|
|
FlowShutdown();
|
|
PASS;
|
|
}
|
|
|
|
#endif /* UNITTESTS */
|
|
|
|
void DecodeSCTPRegisterTests(void)
|
|
{
|
|
#ifdef UNITTESTS
|
|
UtRegisterTest("SCTPDecodeValidInitTest01", SCTPDecodeValidInitTest01);
|
|
UtRegisterTest("SCTPDecodePktTooSmallTest02", SCTPDecodePktTooSmallTest02);
|
|
UtRegisterTest("SCTPDecodeChunkTooSmallTest03", SCTPDecodeChunkTooSmallTest03);
|
|
UtRegisterTest("SCTPDecodeChunkLenInvalidTest04", SCTPDecodeChunkLenInvalidTest04);
|
|
UtRegisterTest("SCTPDecodeInitNonZeroVtagTest05", SCTPDecodeInitNonZeroVtagTest05);
|
|
UtRegisterTest("SCTPDecodeMultiChunkTest06", SCTPDecodeMultiChunkTest06);
|
|
UtRegisterTest("SCTPDecodeInitNotAloneTest07", SCTPDecodeInitNotAloneTest07);
|
|
UtRegisterTest("SCTPDecodeDataPayloadTest08", SCTPDecodeDataPayloadTest08);
|
|
UtRegisterTest("SCTPDecodeNoDataPayloadTest09", SCTPDecodeNoDataPayloadTest09);
|
|
UtRegisterTest("SCTPDecodeDataOffsetTest10", SCTPDecodeDataOffsetTest10);
|
|
UtRegisterTest("SCTPDecodeMultiDataTest11", SCTPDecodeMultiDataTest11);
|
|
UtRegisterTest("SCTPDecodeSmallDataChunkTest12", SCTPDecodeSmallDataChunkTest12);
|
|
#endif
|
|
}
|
|
/**
|
|
* @}
|
|
*/
|