decoder/sctp: extend decoder

Extend the SCTP decoder to parse chunk headers after the 12-byte common
header. Each chunk is validated for minimum header size and length
consistency per RFC 4960 sec 3.2.

Add SCTPChunkHdr and SCTPVars structs to track per-packet chunk metadata

Add five new decoder events for protocol violations:
- SCTP_CHUNK_TOO_SMALL: insufficient data for a chunk header
- SCTP_CHUNK_LEN_INVALID: chunk length < 4 or exceeds packet
- SCTP_INIT_CHUNK_NOT_ALONE: INIT/INIT_ACK bundled (RFC 4960 sec 6.10)
- SCTP_INIT_WITH_NON_ZERO_VTAG: INIT with vtag != 0 (RFC 4960 sec 8.5.1)
- SCTP_DATA_WITH_ZERO_VTAG: DATA chunk with vtag == 0

Ticket #4251
pull/15601/head
Giuseppe Longo 5 months ago committed by Victor Julien
parent 2ce2ebcdba
commit 21d35f6b37

@ -7459,8 +7459,37 @@
"type": "object",
"additionalProperties": false,
"properties": {
"chunk_len_invalid": {
"type": "integer",
"description": "SCTP chunk length < 4 or exceeds remaining packet"
},
"chunk_too_small": {
"type": "integer",
"description": "Remaining data too small for SCTP chunk header"
},
"data_with_zero_vtag": {
"type": "integer",
"description": "SCTP DATA chunk with verification tag == 0"
},
"init_chunk_bundled": {
"type": "integer",
"description": "RFC 4960 sec 6.10 violation: INIT/INIT_ACK bundled with other chunks"
},
"init_with_non_zero_vtag": {
"type": "integer",
"description": "SCTP INIT with verification tag != 0"
},
"pkt_too_small": {
"type": "integer"
"type": "integer",
"description": "SCTP packet smaller than minimum size"
},
"too_many_chunks": {
"type": "integer",
"description": "More chunks than SCTP_MAX_TRACKED_CHUNKS"
},
"too_many_data_chunks": {
"type": "integer",
"description": "More DATA chunks than SCTP_MAX_DATA_CHUNKS"
}
}
},
@ -8547,6 +8576,33 @@
}
}
},
"sctp": {
"type": "object",
"description": "Statistics on SCTP chunk types",
"additionalProperties": false,
"properties": {
"abort": {
"type": "integer",
"description": "Number of SCTP packets with ABORT chunk"
},
"data": {
"type": "integer",
"description": "Number of SCTP packets with DATA chunk"
},
"init": {
"type": "integer",
"description": "Number of SCTP packets with INIT chunk"
},
"init_ack": {
"type": "integer",
"description": "Number of SCTP packets with INIT_ACK chunk"
},
"shutdown": {
"type": "integer",
"description": "Number of SCTP packets with SHUTDOWN chunk"
}
}
},
"stream": {
"type": "object",
"description": "Observational statistics on TCP stream events",

@ -24,6 +24,7 @@ pgsql-events.rules \
pop3-events.rules \
quic-events.rules \
rfb-events.rules \
sctp-events.rules \
smb-events.rules \
smtp-events.rules \
snmp-events.rules \

@ -34,6 +34,7 @@ signature IDs.
| POP3 | 2236000 | 2236999 |
| LDAP | 2237000 | 2237999 |
| SNMP | 2238000 | 2238999 |
| SCTP | 2239000 | 2239999 |
| DNS | 2240000 | 2240999 |
| PGSQL | 2241000 | 2241999 |
| mDNS | 2242000 | 2242999 |

@ -0,0 +1,13 @@
# SCTP decoder event rules.
# SID's fall in the 2239000+ range. See rules/README.md
alert sctp any any -> any any (msg:"SURICATA SCTP packet too small"; decode-event:sctp.pkt_too_small; classtype:protocol-command-decode; sid:2239001; rev:1;)
alert sctp any any -> any any (msg:"SURICATA SCTP chunk too small"; decode-event:sctp.chunk_too_small; classtype:protocol-command-decode; sid:2239002; rev:1;)
alert sctp any any -> any any (msg:"SURICATA SCTP chunk length invalid"; decode-event:sctp.chunk_len_invalid; classtype:protocol-command-decode; sid:2239003; rev:1;)
alert sctp any any -> any any (msg:"SURICATA SCTP INIT chunk bundled"; decode-event:sctp.init_chunk_bundled; classtype:protocol-command-decode; sid:2239004; rev:1;)
alert sctp any any -> any any (msg:"SURICATA SCTP INIT with non-zero vtag"; decode-event:sctp.init_with_non_zero_vtag; classtype:protocol-command-decode; sid:2239005; rev:1;)
alert sctp any any -> any any (msg:"SURICATA SCTP DATA with zero vtag"; decode-event:sctp.data_with_zero_vtag; classtype:protocol-command-decode; sid:2239006; rev:1;)
alert sctp any any -> any any (msg:"SURICATA SCTP too many chunks"; decode-event:sctp.too_many_chunks; classtype:protocol-command-decode; sid:2239007; rev:1;)
alert sctp any any -> any any (msg:"SURICATA SCTP too many data chunks"; decode-event:sctp.too_many_data_chunks; classtype:protocol-command-decode; sid:2239008; rev:1;)
#next sid is 2239009

@ -467,6 +467,34 @@ const struct DecodeEvents_ DEvents[] = {
"decoder.sctp.pkt_too_small",
SCTP_PKT_TOO_SMALL,
},
{
"decoder.sctp.chunk_too_small",
SCTP_CHUNK_TOO_SMALL,
},
{
"decoder.sctp.chunk_len_invalid",
SCTP_CHUNK_LEN_INVALID,
},
{
"decoder.sctp.init_chunk_bundled",
SCTP_INIT_CHUNK_BUNDLED,
},
{
"decoder.sctp.init_with_non_zero_vtag",
SCTP_INIT_WITH_NON_ZERO_VTAG,
},
{
"decoder.sctp.data_with_zero_vtag",
SCTP_DATA_WITH_ZERO_VTAG,
},
{
"decoder.sctp.too_many_chunks",
SCTP_TOO_MANY_CHUNKS,
},
{
"decoder.sctp.too_many_data_chunks",
SCTP_TOO_MANY_DATA_CHUNKS,
},
/* ESP EVENTS */
{

@ -173,6 +173,13 @@ enum {
/* SCTP EVENTS */
SCTP_PKT_TOO_SMALL, /**< sctp packet smaller than minimum size */
SCTP_CHUNK_TOO_SMALL, /**< remaining data too small for chunk header */
SCTP_CHUNK_LEN_INVALID, /**< chunk length < 4 or exceeds remaining packet */
SCTP_INIT_CHUNK_BUNDLED, /**< RFC 4960 sec 6.10: INIT/INIT_ACK bundled with other chunks */
SCTP_INIT_WITH_NON_ZERO_VTAG, /**< INIT with vtag != 0 */
SCTP_DATA_WITH_ZERO_VTAG, /**< DATA chunk with vtag == 0 */
SCTP_TOO_MANY_CHUNKS, /**< more chunks than SCTP_MAX_TRACKED_CHUNKS */
SCTP_TOO_MANY_DATA_CHUNKS, /**< more DATA chunks than SCTP_MAX_DATA_CHUNKS */
/* ESP EVENTS */
ESP_PKT_TOO_SMALL, /**< esp packet smaller than minimum size */

@ -21,7 +21,6 @@
* @{
*/
/**
* \file
*
@ -41,6 +40,117 @@
#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;
uint8_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;
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:
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.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);
@ -50,31 +160,323 @@ static int DecodeSCTPPacket(ThreadVars *tv, Packet *p, const uint8_t *pkt, uint1
return -1;
}
SCTPHdr *sctph = PacketSetSCTP(p, pkt);
p->sp = SCNtohs(sctph->sh_sport);
p->dp = SCNtohs(sctph->sh_dport);
p->payload = (uint8_t *)pkt + sizeof(SCTPHdr);
p->payload_len = len - sizeof(SCTPHdr);
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;
}
} 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)
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)) {
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;
}
#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);
#endif
}
/**
* @}
*/

@ -27,14 +27,64 @@
/** size of the packet header without any chunk headers */
#define SCTP_HEADER_LEN 12
typedef struct SCTPHdr_
{
/** size of a chunk header (type + flags + length) */
#define SCTP_CHUNK_HDR_LEN 4
/** max number of chunks tracked per packet for detection/logging */
/** value chosen to keep per-packet overhead low while still allowing
* some room to track chunks.
* SCTP has no hard limit on the number of chunks per packet.
* A packet can carry as many chunks as fit within the MTU,
* though in practice most packets contain only a few chunks. */
#define SCTP_MAX_TRACKED_CHUNKS 16
/* SCTP chunk types (RFC 4960 sec 3.2) */
#define SCTP_CHUNK_TYPE_DATA 0x00
#define SCTP_CHUNK_TYPE_INIT 0x01
#define SCTP_CHUNK_TYPE_INIT_ACK 0x02
#define SCTP_CHUNK_TYPE_SACK 0x03
#define SCTP_CHUNK_TYPE_HEARTBEAT 0x04
#define SCTP_CHUNK_TYPE_HB_ACK 0x05
#define SCTP_CHUNK_TYPE_ABORT 0x06
#define SCTP_CHUNK_TYPE_SHUTDOWN 0x07
#define SCTP_CHUNK_TYPE_SHUTDOWN_ACK 0x08
#define SCTP_CHUNK_TYPE_ERROR 0x09
#define SCTP_CHUNK_TYPE_COOKIE_ECHO 0x0A
#define SCTP_CHUNK_TYPE_COOKIE_ACK 0x0B
#define SCTP_CHUNK_TYPE_ECNE 0x0C
#define SCTP_CHUNK_TYPE_CWR 0x0D
#define SCTP_CHUNK_TYPE_SHUTDOWN_COMPLETE 0x0E
#define SCTP_CHUNK_TYPE_FORWARD_TSN 0xC0
typedef struct SCTPHdr_ {
uint16_t sh_sport; /* source port */
uint16_t sh_dport; /* destination port */
uint32_t sh_vtag; /* verification tag, defined per flow */
uint32_t sh_sum; /* checksum, computed via crc32 */
} __attribute__((__packed__)) SCTPHdr;
typedef struct SCTPChunkHdr_ {
uint8_t type;
uint8_t flags;
uint16_t length;
} __attribute__((__packed__)) SCTPChunkHdr;
typedef struct SCTPVars_ {
uint16_t hlen; /**< total header length (common header + chunks) */
uint8_t chunk_cnt; /**< number of chunks parsed */
uint8_t tracked_chunk_cnt; /**< number of chunks tracked (capped at SCTP_MAX_TRACKED_CHUNKS) */
uint8_t chunk_types[SCTP_MAX_TRACKED_CHUNKS]; /**< types of first N chunks */
bool has_init : 1;
bool has_init_ack : 1;
bool has_data : 1;
bool has_abort : 1;
} SCTPVars;
#define SCTP_GET_RAW_SRC_PORT(sctph) SCNtohs((sctph)->sh_sport)
#define SCTP_GET_RAW_DST_PORT(sctph) SCNtohs((sctph)->sh_dport)
#define SCTP_GET_RAW_VTAG(sctph) SCNtohl((sctph)->sh_vtag)
#define SCTP_GET_RAW_SUM(sctph) SCNtohl((sctph)->sh_sum)
void DecodeSCTPRegisterTests(void);
#endif /* SURICATA_DECODE_SCTP_H */

@ -656,6 +656,11 @@ void DecodeRegisterPerfCounters(DecodeThreadVars *dtv, ThreadVars *tv)
dtv->counter_udp = StatsRegisterCounter("decoder.udp", &tv->stats);
dtv->counter_sctp = StatsRegisterCounter("decoder.sctp", &tv->stats);
dtv->counter_sctp_init = StatsRegisterCounter("sctp.init", &tv->stats);
dtv->counter_sctp_init_ack = StatsRegisterCounter("sctp.init_ack", &tv->stats);
dtv->counter_sctp_data = StatsRegisterCounter("sctp.data", &tv->stats);
dtv->counter_sctp_abort = StatsRegisterCounter("sctp.abort", &tv->stats);
dtv->counter_sctp_shutdown = StatsRegisterCounter("sctp.shutdown", &tv->stats);
dtv->counter_esp = StatsRegisterCounter("decoder.esp", &tv->stats);
dtv->counter_icmpv4 = StatsRegisterCounter("decoder.icmpv4", &tv->stats);
dtv->counter_icmpv6 = StatsRegisterCounter("decoder.icmpv6", &tv->stats);

@ -491,6 +491,7 @@ struct PacketL4 {
ICMPV4Vars icmpv4;
ICMPV6Vars icmpv6;
IGMPVars igmp;
SCTPVars sctp;
} vars;
};
@ -1025,6 +1026,11 @@ typedef struct DecodeThreadVars_
StatsCounterId counter_raw;
StatsCounterId counter_null;
StatsCounterId counter_sctp;
StatsCounterId counter_sctp_init;
StatsCounterId counter_sctp_init_ack;
StatsCounterId counter_sctp_data;
StatsCounterId counter_sctp_abort;
StatsCounterId counter_sctp_shutdown;
StatsCounterId counter_esp;
StatsCounterId counter_ppp;
StatsCounterId counter_geneve;

@ -167,6 +167,7 @@ static void RegisterUnittests(void)
DecodeTCPRegisterTests();
DecodeUDPV4RegisterTests();
DecodeGRERegisterTests();
DecodeSCTPRegisterTests();
DecodeESPRegisterTests();
DecodeMPLSRegisterTests();
DecodeNSHRegisterTests();

Loading…
Cancel
Save