/* 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 * * Decode SCTP */ #include "suricata-common.h" #include "decode.h" #include "decode-sctp.h" #include "decode-events.h" #include "util-validate.h" #include "util-debug.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 } /** * @} */