From 88f17d7b20fe138d203b5a67c1f576972b3e4ff7 Mon Sep 17 00:00:00 2001 From: Giuseppe Longo Date: Tue, 24 Mar 2026 10:17:44 +0100 Subject: [PATCH] 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 #4251 --- rust/src/lib.rs | 1 + rust/src/sctp/detect.rs | 146 +++++++++++++++++++++++++++++++++++ rust/src/sctp/mod.rs | 20 +++++ src/Makefile.am | 2 + src/detect-engine-register.c | 2 + src/detect-engine-register.h | 1 + src/detect-sctp-chunk-type.c | 140 +++++++++++++++++++++++++++++++++ src/detect-sctp-chunk-type.h | 27 +++++++ 8 files changed, 339 insertions(+) create mode 100644 rust/src/sctp/detect.rs create mode 100644 rust/src/sctp/mod.rs create mode 100644 src/detect-sctp-chunk-type.c create mode 100644 src/detect-sctp-chunk-type.h diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 45f1688d91..10c65e66a2 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -136,6 +136,7 @@ pub mod lzma; pub mod util; pub mod ffi; pub mod feature; +pub mod sctp; pub mod sdp; pub mod ldap; pub mod flow; diff --git a/rust/src/sctp/detect.rs b/rust/src/sctp/detect.rs new file mode 100644 index 0000000000..c1067c5eb7 --- /dev/null +++ b/rust/src/sctp/detect.rs @@ -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 + +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 { + let ft_name: &CStr = CStr::from_ptr(ustr); + if let Ok(s) = ft_name.to_str() { + if let Some(ctx) = detect_parse_uint_enum::(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::("0").unwrap(); + assert_eq!(ctx.arg1, 0); + let ctx = detect_parse_uint_enum::("1").unwrap(); + assert_eq!(ctx.arg1, 1); + let ctx = detect_parse_uint_enum::("192").unwrap(); + assert_eq!(ctx.arg1, 0xC0); + } + + #[test] + fn parse_named() { + let ctx = detect_parse_uint_enum::("data").unwrap(); + assert_eq!(ctx.arg1, 0); + let ctx = detect_parse_uint_enum::("init").unwrap(); + assert_eq!(ctx.arg1, 1); + let ctx = detect_parse_uint_enum::("init_ack").unwrap(); + assert_eq!(ctx.arg1, 2); + let ctx = detect_parse_uint_enum::("sack").unwrap(); + assert_eq!(ctx.arg1, 3); + let ctx = detect_parse_uint_enum::("heartbeat").unwrap(); + assert_eq!(ctx.arg1, 4); + let ctx = detect_parse_uint_enum::("hb_ack").unwrap(); + assert_eq!(ctx.arg1, 5); + let ctx = detect_parse_uint_enum::("abort").unwrap(); + assert_eq!(ctx.arg1, 6); + let ctx = detect_parse_uint_enum::("shutdown").unwrap(); + assert_eq!(ctx.arg1, 7); + let ctx = detect_parse_uint_enum::("cookie_echo").unwrap(); + assert_eq!(ctx.arg1, 0x0A); + let ctx = detect_parse_uint_enum::("forward_tsn").unwrap(); + assert_eq!(ctx.arg1, 0xC0); + } + + #[test] + fn parse_case_insensitive() { + let ctx = detect_parse_uint_enum::("INIT").unwrap(); + assert_eq!(ctx.arg1, 1); + let ctx = detect_parse_uint_enum::("Init").unwrap(); + assert_eq!(ctx.arg1, 1); + let ctx = detect_parse_uint_enum::("INIT_ACK").unwrap(); + assert_eq!(ctx.arg1, 2); + } + + #[test] + fn parse_negation() { + let ctx = detect_parse_uint_enum::("!init").unwrap(); + assert_eq!(ctx.arg1, 1); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeNe); + } + + #[test] + fn parse_invalid() { + assert!(detect_parse_uint_enum::("foo").is_none()); + assert!(detect_parse_uint_enum::("").is_none()); + } +} diff --git a/rust/src/sctp/mod.rs b/rust/src/sctp/mod.rs new file mode 100644 index 0000000000..b772cdd0eb --- /dev/null +++ b/rust/src/sctp/mod.rs @@ -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 + +pub mod detect; diff --git a/src/Makefile.am b/src/Makefile.am index d5bbb60c1c..35d5ce145f 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -229,6 +229,7 @@ noinst_HEADERS = \ detect-igmphdr.h \ detect-igmp-type.h \ detect-sctphdr.h \ + detect-sctp-chunk-type.h \ detect-ipaddr.h \ detect-ipopts.h \ detect-ipproto.h \ @@ -806,6 +807,7 @@ libsuricata_c_a_SOURCES = \ detect-igmphdr.c \ detect-igmp-type.c \ detect-sctphdr.c \ + detect-sctp-chunk-type.c \ detect-ipaddr.c \ detect-ipopts.c \ detect-ipproto.c \ diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index cf8de45b35..6bc14dfe0b 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -156,6 +156,7 @@ #include "detect-igmphdr.h" #include "detect-igmp-type.h" #include "detect-sctphdr.h" +#include "detect-sctp-chunk-type.h" #include "detect-urilen.h" #include "detect-bsize.h" #include "detect-detection-filter.h" @@ -678,6 +679,7 @@ void SigTableSetup(void) DetectIGMPHdrRegister(); DetectIGMPTypeRegister(); DetectSCTPHdrRegister(); + DetectSCTPChunkTypeRegister(); DetectTlsRegister(); DetectTlsValidityRegister(); DetectTlsVersionRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 92a57c8900..ca77a229c8 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -52,6 +52,7 @@ enum DetectKeywordId { DETECT_IGMPHDR, DETECT_IGMP_TYPE, DETECT_SCTPHDR, + DETECT_SCTP_CHUNK_TYPE, DETECT_DSIZE, DETECT_FLOW, diff --git a/src/detect-sctp-chunk-type.c b/src/detect-sctp-chunk-type.c new file mode 100644 index 0000000000..dda9d91cca --- /dev/null +++ b/src/detect-sctp-chunk-type.c @@ -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 + */ + +#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); +} diff --git a/src/detect-sctp-chunk-type.h b/src/detect-sctp-chunk-type.h new file mode 100644 index 0000000000..b3d2a874fb --- /dev/null +++ b/src/detect-sctp-chunk-type.h @@ -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 */