mirror of https://github.com/OISF/suricata
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 #4251pull/15601/head
parent
1413b082d3
commit
dc44447ce6
@ -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…
Reference in New Issue