detect/sctp: add sctp.chunk_data sticky buffer

Add a sctp.data sticky multi-buffer that allows content matching on the bytes
inside any of the SCTP DATA chunks extracted.

Ticket #4251
pull/15601/head
Giuseppe Longo 4 months ago committed by Victor Julien
parent 1413b082d3
commit dc44447ce6

@ -231,6 +231,7 @@ noinst_HEADERS = \
detect-sctphdr.h \
detect-sctp-chunk-cnt.h \
detect-sctp-chunk-type.h \
detect-sctp-chunk-data.h \
detect-sctp-vtag.h \
detect-ipaddr.h \
detect-ipopts.h \
@ -811,6 +812,7 @@ libsuricata_c_a_SOURCES = \
detect-sctphdr.c \
detect-sctp-chunk-cnt.c \
detect-sctp-chunk-type.c \
detect-sctp-chunk-data.c \
detect-sctp-vtag.c \
detect-ipaddr.c \
detect-ipopts.c \

@ -159,6 +159,7 @@
#include "detect-sctp-chunk-type.h"
#include "detect-sctp-chunk-cnt.h"
#include "detect-sctp-vtag.h"
#include "detect-sctp-chunk-data.h"
#include "detect-urilen.h"
#include "detect-bsize.h"
#include "detect-detection-filter.h"
@ -684,6 +685,7 @@ void SigTableSetup(void)
DetectSCTPChunkTypeRegister();
DetectSCTPChunkCntRegister();
DetectSCTPVtagRegister();
DetectSCTPChunkDataRegister();
DetectTlsRegister();
DetectTlsValidityRegister();
DetectTlsVersionRegister();

@ -55,6 +55,7 @@ enum DetectKeywordId {
DETECT_SCTP_CHUNK_TYPE,
DETECT_SCTP_CHUNK_CNT,
DETECT_SCTP_VTAG,
DETECT_SCTP_CHUNK_DATA,
DETECT_DSIZE,
DETECT_FLOW,

@ -0,0 +1,220 @@
/* Copyright (C) 2026 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
*
* Implements sctp.chunk_data multi-buffer sticky buffer.
*
* Each SCTP DATA chunk payload in the packet is inspected as a
* separate buffer instance (not reassembled).
*
* Author: Giuseppe Longo <glongo@oisf.net>
*/
#include "suricata-common.h"
#include "detect.h"
#include "detect-engine.h"
#include "detect-engine-buffer.h"
#include "detect-engine-content-inspection.h"
#include "detect-engine-inspect-buffer.h"
#include "detect-engine-mpm.h"
#include "detect-engine-prefilter.h"
#include "detect-sctp-chunk-data.h"
#include "util-mpm.h"
#include "util-profiling.h"
static int DetectSCTPChunkDataSetup(DetectEngineCtx *, Signature *, const char *);
static int g_buffer_id = 0;
/**
* \brief Get a multi-instance inspection buffer for a specific DATA chunk.
*
* \param det_ctx detection engine thread context
* \param transforms transforms to apply
* \param p packet
* \param list_id buffer list id
* \param local_id multi-instance buffer index and index into SCTPVars data_offsets/data_lens
*
* \retval buffer or NULL
*/
static InspectionBuffer *GetBuffer(DetectEngineThreadCtx *det_ctx,
const DetectEngineTransforms *transforms, Packet *p, const int list_id,
const uint32_t local_id)
{
DEBUG_VALIDATE_BUG_ON(local_id >= SCTP_MAX_DATA_CHUNKS);
InspectionBuffer *buffer = InspectionBufferMultipleForListGet(det_ctx, list_id, local_id);
if (buffer == NULL)
return NULL;
if (buffer->initialized)
return buffer;
const uint16_t offset = p->l4.vars.sctp.data_offsets[(uint8_t)local_id];
const uint16_t len = p->l4.vars.sctp.data_lens[(uint8_t)local_id];
if (len == 0) {
InspectionBufferSetupMultiEmpty(buffer);
return NULL;
}
const uint8_t *data = (const uint8_t *)PacketGetSCTP(p) + offset;
if ((data + (ptrdiff_t)len) > ((uint8_t *)GET_PKT_DATA(p) + (ptrdiff_t)GET_PKT_LEN(p))) {
SCLogDebug("data out of range: %p > %p", (data + (ptrdiff_t)len),
((uint8_t *)GET_PKT_DATA(p) + (ptrdiff_t)GET_PKT_LEN(p)));
InspectionBufferSetupMultiEmpty(buffer);
return NULL;
}
InspectionBufferSetupMulti(det_ctx, buffer, transforms, data, len);
return buffer;
}
/**
* \brief Custom packet inspection callback for sctp.chunk_data.
*
* Loops over all tracked DATA chunks, inspecting each as a separate buffer.
*/
static int DetectEngineInspectSCTPChunkData(DetectEngineThreadCtx *det_ctx,
const DetectEnginePktInspectionEngine *engine, const Signature *s, Packet *p,
uint8_t *_alert_flags)
{
if (!PacketIsSCTP(p))
return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
const uint8_t cnt = p->l4.vars.sctp.data_chunk_cnt;
if (cnt == 0)
return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
const int list_id = engine->sm_list;
const DetectEngineTransforms *transforms = NULL;
if (!engine->mpm) {
transforms = engine->v1.transforms;
}
for (uint8_t i = 0; i < cnt; i++) {
InspectionBuffer *buffer = GetBuffer(det_ctx, transforms, p, list_id, (uint32_t)i);
if (buffer == NULL || buffer->inspect == NULL)
continue;
if (DetectEngineContentInspectionBuffer(det_ctx->de_ctx, det_ctx, s, engine->smd, p,
p->flow, buffer, DETECT_ENGINE_CONTENT_INSPECTION_MODE_HEADER)) {
return DETECT_ENGINE_INSPECT_SIG_MATCH;
}
}
return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
}
typedef struct PrefilterMpmSCTPChunkData {
int list_id;
const MpmCtx *mpm_ctx;
const DetectEngineTransforms *transforms;
} PrefilterMpmSCTPChunkData;
/**
* \brief Prefilter callback: run MPM on each DATA chunk buffer.
*/
static void PrefilterMpmSCTPChunkDataPkt(
DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx)
{
if (!PacketIsSCTP(p))
return;
const uint8_t cnt = p->l4.vars.sctp.data_chunk_cnt;
if (cnt == 0)
return;
const PrefilterMpmSCTPChunkData *ctx = (const PrefilterMpmSCTPChunkData *)pectx;
const MpmCtx *mpm_ctx = ctx->mpm_ctx;
const int list_id = ctx->list_id;
for (uint8_t i = 0; i < cnt; i++) {
InspectionBuffer *buffer = GetBuffer(det_ctx, ctx->transforms, p, list_id, (uint32_t)i);
if (buffer == NULL || buffer->inspect == NULL)
continue;
if (buffer->inspect_len >= mpm_ctx->minlen) {
(void)mpm_table[mpm_ctx->mpm_type].Search(
mpm_ctx, &det_ctx->mtc, &det_ctx->pmq, buffer->inspect, buffer->inspect_len);
PREFILTER_PROFILING_ADD_BYTES(det_ctx, buffer->inspect_len);
}
}
}
static void PrefilterMpmSCTPChunkDataFree(void *ptr)
{
SCFree(ptr);
}
static int PrefilterSCTPChunkDataRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh,
MpmCtx *mpm_ctx, const DetectBufferMpmRegistry *mpm_reg, int list_id)
{
PrefilterMpmSCTPChunkData *pectx = SCCalloc(1, sizeof(*pectx));
if (pectx == NULL)
return -1;
pectx->list_id = list_id;
pectx->mpm_ctx = mpm_ctx;
pectx->transforms = &mpm_reg->transforms;
return PrefilterAppendEngine(de_ctx, sgh, PrefilterMpmSCTPChunkDataPkt, 0,
SIGNATURE_HOOK_PKT_NOT_SET, pectx, PrefilterMpmSCTPChunkDataFree, mpm_reg->pname);
}
void DetectSCTPChunkDataRegister(void)
{
sigmatch_table[DETECT_SCTP_CHUNK_DATA].name = "sctp.chunk_data";
sigmatch_table[DETECT_SCTP_CHUNK_DATA].desc =
"sticky buffer to match on each SCTP DATA chunk payload";
sigmatch_table[DETECT_SCTP_CHUNK_DATA].url = "/rules/sctp-keywords.html#sctp-chunk-data";
sigmatch_table[DETECT_SCTP_CHUNK_DATA].Setup = DetectSCTPChunkDataSetup;
sigmatch_table[DETECT_SCTP_CHUNK_DATA].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER;
g_buffer_id = DetectBufferTypeRegister("sctp.chunk_data");
BUG_ON(g_buffer_id < 0);
DetectBufferTypeSupportsPacket("sctp.chunk_data");
DetectBufferTypeSupportsMultiInstance("sctp.chunk_data");
DetectPktMpmRegister("sctp.chunk_data", 2, PrefilterSCTPChunkDataRegister, NULL);
DetectPktInspectEngineRegister("sctp.chunk_data", NULL, DetectEngineInspectSCTPChunkData);
}
/**
* \brief setup sctp.chunk_data sticky buffer
*
* \param de_ctx pointer to the Detection Engine Context
* \param s pointer to the current Signature
* \param _unused unused
*
* \retval 0 on Success
* \retval -1 on Failure
*/
static int DetectSCTPChunkDataSetup(DetectEngineCtx *de_ctx, Signature *s, const char *_unused)
{
if (!(DetectProtoContainsProto(s->proto, IPPROTO_SCTP)))
return -1;
s->flags |= SIG_FLAG_REQUIRE_PACKET;
if (SCDetectBufferSetActiveList(de_ctx, s, g_buffer_id) < 0)
return -1;
return 0;
}

@ -0,0 +1,27 @@
/* Copyright (C) 2026 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
*/
#ifndef SURICATA_DETECT_SCTP_CHUNK_DATA_H
#define SURICATA_DETECT_SCTP_CHUNK_DATA_H
void DetectSCTPChunkDataRegister(void);
#endif /* SURICATA_DETECT_SCTP_CHUNK_DATA_H */
Loading…
Cancel
Save