diff --git a/src/decode-sctp.c b/src/decode-sctp.c index 311fcb9528..48df9e1b4c 100644 --- a/src/decode-sctp.c +++ b/src/decode-sctp.c @@ -64,6 +64,7 @@ static int DecodeSCTPChunks(Packet *p, const uint8_t *pkt, uint16_t len) 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) { @@ -112,6 +113,15 @@ static int DecodeSCTPChunks(Packet *p, const uint8_t *pkt, uint16_t len) 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) { @@ -143,6 +153,7 @@ static int DecodeSCTPChunks(Packet *p, const uint8_t *pkt, uint16_t len) 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; @@ -173,6 +184,16 @@ static int DecodeSCTPPacket(ThreadVars *tv, Packet *p, const uint8_t *pkt, uint1 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; } @@ -463,6 +484,199 @@ static int SCTPDecodeInitNotAloneTest07(void) 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) @@ -475,6 +689,11 @@ void DecodeSCTPRegisterTests(void) 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 } /** diff --git a/src/decode-sctp.h b/src/decode-sctp.h index 97d219dd03..c6e6b45323 100644 --- a/src/decode-sctp.h +++ b/src/decode-sctp.h @@ -38,6 +38,12 @@ * though in practice most packets contain only a few chunks. */ #define SCTP_MAX_TRACKED_CHUNKS 16 +/** max number of DATA chunk payloads tracked per packet */ +#define SCTP_MAX_DATA_CHUNKS 16 + +/** DATA chunk overhead before user data (chunk hdr + TSN + SID + SSN + PPID) */ +#define SCTP_DATA_CHUNK_HDR_LEN 16 + /* SCTP chunk types (RFC 4960 sec 3.2) */ #define SCTP_CHUNK_TYPE_DATA 0x00 #define SCTP_CHUNK_TYPE_INIT 0x01 @@ -74,10 +80,13 @@ typedef struct SCTPVars_ { 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 */ + uint8_t data_chunk_cnt; /**< number of DATA chunk payloads tracked */ bool has_init : 1; bool has_init_ack : 1; bool has_data : 1; bool has_abort : 1; + uint16_t data_offsets[SCTP_MAX_DATA_CHUNKS]; /**< offsets of DATA user data from L4 start */ + uint16_t data_lens[SCTP_MAX_DATA_CHUNKS]; /**< lengths of DATA user data */ } SCTPVars; #define SCTP_GET_RAW_SRC_PORT(sctph) SCNtohs((sctph)->sh_sport)