mirror of https://github.com/OISF/suricata
detect/sctp: add sctp.chunk_type keyword
Add a U8 numeric keyword to match the first SCTP chunk type in a packet with prefilter support. Ticket #4251pull/15601/head
parent
bbd0ecb616
commit
88f17d7b20
@ -0,0 +1,146 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
// Author: Giuseppe Longo <glongo@oisf.net>
|
||||
|
||||
use crate::detect::uint::{detect_parse_uint_enum, DetectUintData};
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
/// SCTP chunk types (RFC 4960 sec 3.2)
|
||||
#[repr(u8)]
|
||||
#[derive(EnumStringU8)]
|
||||
pub enum SctpChunkType {
|
||||
Data = 0x00,
|
||||
Init = 0x01,
|
||||
InitAck = 0x02,
|
||||
Sack = 0x03,
|
||||
Heartbeat = 0x04,
|
||||
HbAck = 0x05,
|
||||
Abort = 0x06,
|
||||
Shutdown = 0x07,
|
||||
ShutdownAck = 0x08,
|
||||
Error = 0x09,
|
||||
CookieEcho = 0x0A,
|
||||
CookieAck = 0x0B,
|
||||
Ecne = 0x0C,
|
||||
Cwr = 0x0D,
|
||||
ShutdownComplete = 0x0E,
|
||||
ForwardTsn = 0xC0,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn SCSctpDetectChunkTypeParse(
|
||||
ustr: *const std::os::raw::c_char,
|
||||
) -> *mut DetectUintData<u8> {
|
||||
let ft_name: &CStr = CStr::from_ptr(ustr);
|
||||
if let Ok(s) = ft_name.to_str() {
|
||||
if let Some(ctx) = detect_parse_uint_enum::<u8, SctpChunkType>(s) {
|
||||
let boxed = Box::new(ctx);
|
||||
return Box::into_raw(boxed) as *mut _;
|
||||
}
|
||||
}
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
/// Returns the string name for a chunk type value, or NULL for unknown types.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn SCSctpChunkTypeToString(val: u8) -> *const std::os::raw::c_char {
|
||||
let s: &[u8] = match val {
|
||||
0x00 => b"data\0",
|
||||
0x01 => b"init\0",
|
||||
0x02 => b"init_ack\0",
|
||||
0x03 => b"sack\0",
|
||||
0x04 => b"heartbeat\0",
|
||||
0x05 => b"hb_ack\0",
|
||||
0x06 => b"abort\0",
|
||||
0x07 => b"shutdown\0",
|
||||
0x08 => b"shutdown_ack\0",
|
||||
0x09 => b"error\0",
|
||||
0x0A => b"cookie_echo\0",
|
||||
0x0B => b"cookie_ack\0",
|
||||
0x0C => b"ecne\0",
|
||||
0x0D => b"cwr\0",
|
||||
0x0E => b"shutdown_complete\0",
|
||||
0xC0 => b"forward_tsn\0",
|
||||
_ => return std::ptr::null(),
|
||||
};
|
||||
s.as_ptr() as *const std::os::raw::c_char
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::detect::uint::DetectUintMode;
|
||||
|
||||
#[test]
|
||||
fn parse_numeric() {
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("0").unwrap();
|
||||
assert_eq!(ctx.arg1, 0);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("1").unwrap();
|
||||
assert_eq!(ctx.arg1, 1);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("192").unwrap();
|
||||
assert_eq!(ctx.arg1, 0xC0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_named() {
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("data").unwrap();
|
||||
assert_eq!(ctx.arg1, 0);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("init").unwrap();
|
||||
assert_eq!(ctx.arg1, 1);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("init_ack").unwrap();
|
||||
assert_eq!(ctx.arg1, 2);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("sack").unwrap();
|
||||
assert_eq!(ctx.arg1, 3);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("heartbeat").unwrap();
|
||||
assert_eq!(ctx.arg1, 4);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("hb_ack").unwrap();
|
||||
assert_eq!(ctx.arg1, 5);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("abort").unwrap();
|
||||
assert_eq!(ctx.arg1, 6);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("shutdown").unwrap();
|
||||
assert_eq!(ctx.arg1, 7);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("cookie_echo").unwrap();
|
||||
assert_eq!(ctx.arg1, 0x0A);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("forward_tsn").unwrap();
|
||||
assert_eq!(ctx.arg1, 0xC0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_case_insensitive() {
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("INIT").unwrap();
|
||||
assert_eq!(ctx.arg1, 1);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("Init").unwrap();
|
||||
assert_eq!(ctx.arg1, 1);
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("INIT_ACK").unwrap();
|
||||
assert_eq!(ctx.arg1, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_negation() {
|
||||
let ctx = detect_parse_uint_enum::<u8, SctpChunkType>("!init").unwrap();
|
||||
assert_eq!(ctx.arg1, 1);
|
||||
assert_eq!(ctx.mode, DetectUintMode::DetectUintModeNe);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_invalid() {
|
||||
assert!(detect_parse_uint_enum::<u8, SctpChunkType>("foo").is_none());
|
||||
assert!(detect_parse_uint_enum::<u8, SctpChunkType>("").is_none());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
// Author: Giuseppe Longo <glongo@oisf.net>
|
||||
|
||||
pub mod detect;
|
||||
@ -0,0 +1,140 @@
|
||||
/* 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_type keyword support
|
||||
*
|
||||
* Author: Giuseppe Longo <glongo@oisf.net>
|
||||
*/
|
||||
|
||||
#include "suricata-common.h"
|
||||
#include "decode.h"
|
||||
|
||||
#include "detect.h"
|
||||
#include "detect-parse.h"
|
||||
#include "detect-engine-prefilter-common.h"
|
||||
#include "detect-engine-build.h"
|
||||
|
||||
#include "detect-sctp-chunk-type.h"
|
||||
#include "detect-engine-uint.h"
|
||||
|
||||
#include "util-byte.h"
|
||||
#include "util-debug.h"
|
||||
|
||||
static int DetectSCTPChunkTypeMatch(
|
||||
DetectEngineThreadCtx *, Packet *, const Signature *, const SigMatchCtx *);
|
||||
static int DetectSCTPChunkTypeSetup(DetectEngineCtx *, Signature *, const char *);
|
||||
void DetectSCTPChunkTypeFree(DetectEngineCtx *, void *);
|
||||
|
||||
static int PrefilterSetupSCTPChunkType(DetectEngineCtx *de_ctx, SigGroupHead *sgh);
|
||||
static bool PrefilterSCTPChunkTypeIsPrefilterable(const Signature *s);
|
||||
|
||||
void DetectSCTPChunkTypeRegister(void)
|
||||
{
|
||||
sigmatch_table[DETECT_SCTP_CHUNK_TYPE].name = "sctp.chunk_type";
|
||||
sigmatch_table[DETECT_SCTP_CHUNK_TYPE].desc = "match on any SCTP chunk type in the packet";
|
||||
sigmatch_table[DETECT_SCTP_CHUNK_TYPE].url = "/rules/sctp-keywords.html#sctp-chunk-type";
|
||||
sigmatch_table[DETECT_SCTP_CHUNK_TYPE].Match = DetectSCTPChunkTypeMatch;
|
||||
sigmatch_table[DETECT_SCTP_CHUNK_TYPE].Setup = DetectSCTPChunkTypeSetup;
|
||||
sigmatch_table[DETECT_SCTP_CHUNK_TYPE].Free = DetectSCTPChunkTypeFree;
|
||||
sigmatch_table[DETECT_SCTP_CHUNK_TYPE].flags =
|
||||
SIGMATCH_INFO_UINT8 | SIGMATCH_INFO_MULTI_UINT | SIGMATCH_INFO_ENUM_UINT;
|
||||
sigmatch_table[DETECT_SCTP_CHUNK_TYPE].SupportsPrefilter =
|
||||
PrefilterSCTPChunkTypeIsPrefilterable;
|
||||
sigmatch_table[DETECT_SCTP_CHUNK_TYPE].SetupPrefilter = PrefilterSetupSCTPChunkType;
|
||||
}
|
||||
|
||||
static int DetectSCTPChunkTypeMatch(
|
||||
DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const SigMatchCtx *ctx)
|
||||
{
|
||||
DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p));
|
||||
|
||||
if (!PacketIsSCTP(p)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const DetectU8Data *data = (const DetectU8Data *)ctx;
|
||||
const uint8_t cnt = p->l4.vars.sctp.tracked_chunk_cnt;
|
||||
for (uint8_t i = 0; i < cnt; i++) {
|
||||
if (DetectU8Match(p->l4.vars.sctp.chunk_types[i], data)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DetectSCTPChunkTypeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
|
||||
{
|
||||
DetectU8Data *data = SCSctpDetectChunkTypeParse(str);
|
||||
if (data == NULL)
|
||||
return -1;
|
||||
|
||||
if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_SCTP_CHUNK_TYPE, (SigMatchCtx *)data,
|
||||
DETECT_SM_LIST_MATCH) == NULL) {
|
||||
DetectSCTPChunkTypeFree(de_ctx, data);
|
||||
return -1;
|
||||
}
|
||||
s->flags |= SIG_FLAG_REQUIRE_PACKET;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DetectSCTPChunkTypeFree(DetectEngineCtx *de_ctx, void *ptr)
|
||||
{
|
||||
DetectU8Data *data = (DetectU8Data *)ptr;
|
||||
SCDetectU8Free(data);
|
||||
}
|
||||
|
||||
static void PrefilterPacketSCTPChunkTypeMatch(
|
||||
DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx)
|
||||
{
|
||||
DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p));
|
||||
|
||||
if (!PacketIsSCTP(p)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PrefilterPacketU8HashCtx *h = pectx;
|
||||
const uint8_t cnt = p->l4.vars.sctp.tracked_chunk_cnt;
|
||||
/* bitmap to dedup repeated chunk types within a single packet */
|
||||
uint32_t seen[8] = { 0 };
|
||||
for (uint8_t i = 0; i < cnt; i++) {
|
||||
const uint8_t val = p->l4.vars.sctp.chunk_types[i];
|
||||
if (seen[val >> 5] & (1U << (val & 0x1F))) {
|
||||
continue;
|
||||
}
|
||||
seen[val >> 5] |= 1U << (val & 0x1F);
|
||||
const SigsArray *sa = h->array[val];
|
||||
if (sa) {
|
||||
PrefilterAddSids(&det_ctx->pmq, sa->sigs, sa->cnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int PrefilterSetupSCTPChunkType(DetectEngineCtx *de_ctx, SigGroupHead *sgh)
|
||||
{
|
||||
return PrefilterSetupPacketHeaderU8Hash(de_ctx, sgh, DETECT_SCTP_CHUNK_TYPE,
|
||||
SIG_MASK_REQUIRE_REAL_PKT, PrefilterPacketU8Set, PrefilterPacketU8Compare,
|
||||
PrefilterPacketSCTPChunkTypeMatch);
|
||||
}
|
||||
|
||||
static bool PrefilterSCTPChunkTypeIsPrefilterable(const Signature *s)
|
||||
{
|
||||
return PrefilterIsPrefilterableById(s, DETECT_SCTP_CHUNK_TYPE);
|
||||
}
|
||||
@ -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_TYPE_H
|
||||
#define SURICATA_DETECT_SCTP_CHUNK_TYPE_H
|
||||
|
||||
void DetectSCTPChunkTypeRegister(void);
|
||||
|
||||
#endif /* SURICATA_DETECT_SCTP_CHUNK_TYPE_H */
|
||||
Loading…
Reference in New Issue