From 991e7f3b1d4e4d75ca79e573d73bbb2d4177744f Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Wed, 15 Apr 2026 18:08:51 -0600 Subject: [PATCH] ntp: add ntp.mode keyword This keyword also accepts strings for known mode names. Ticket: #8429 --- doc/userguide/rules/ntp-keywords.rst | 37 +++++++- rust/src/ntp/detect.rs | 132 ++++++++++++++++++++++++++- 2 files changed, 165 insertions(+), 4 deletions(-) diff --git a/doc/userguide/rules/ntp-keywords.rst b/doc/userguide/rules/ntp-keywords.rst index df6da60d44..3134b6821c 100644 --- a/doc/userguide/rules/ntp-keywords.rst +++ b/doc/userguide/rules/ntp-keywords.rst @@ -3,6 +3,39 @@ NTP Keywords .. role:: example-rule-options +ntp.mode +******** + +NTP mode. This keyword accepts either an integer or one of the known mode +names. + +``ntp.mode`` uses an :ref:`unsigned 8-bit integer `. + +Syntax:: + + ntp.mode:[op] + ntp.mode:[!]reserved + ntp.mode:[!]symmetric_active + ntp.mode:[!]symmetric_passive + ntp.mode:[!]client + ntp.mode:[!]server + ntp.mode:[!]broadcast + ntp.mode:[!]control + ntp.mode:[!]private + +Examples:: + + ntp.mode:3 + ntp.mode:>3 + ntp.mode:client + ntp.mode:!server + +Signature Example: + +.. container:: example-rule + + alert ntp any any -> any any (msg:"NTP client mode"; :example-rule-options:`ntp.mode:client;` sid:1; rev:1;) + ntp.stratum *********** @@ -24,7 +57,7 @@ Signature Example: .. container:: example-rule - alert ntp any any -> any any (msg:"NTP stratum 2"; :example-rule-options:`ntp.stratum:2;` sid:1; rev:1;) + alert ntp any any -> any any (msg:"NTP stratum 2"; :example-rule-options:`ntp.stratum:2;` sid:2; rev:1;) ntp.version *********** @@ -47,4 +80,4 @@ Signature Example: .. container:: example-rule - alert ntp any any -> any any (msg:"NTP version 4"; :example-rule-options:`ntp.version:4;` sid:2; rev:1;) + alert ntp any any -> any any (msg:"NTP version 4"; :example-rule-options:`ntp.version:4;` sid:3; rev:1;) diff --git a/rust/src/ntp/detect.rs b/rust/src/ntp/detect.rs index c1059ee769..1bbf528558 100644 --- a/rust/src/ntp/detect.rs +++ b/rust/src/ntp/detect.rs @@ -17,8 +17,11 @@ use super::ntp::{NTPTransaction, ALPROTO_NTP}; use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER}; -use crate::detect::uint::{DetectUintData, SCDetectU8Free, SCDetectU8Match, SCDetectU8Parse}; -use crate::detect::SIGMATCH_INFO_UINT8; +use crate::detect::uint::{ + detect_parse_uint_enum, DetectUintData, SCDetectU8Free, SCDetectU8Match, SCDetectU8Parse, +}; +use crate::detect::{SIGMATCH_INFO_ENUM_UINT, SIGMATCH_INFO_UINT8}; +use std::ffi::CStr; use std::os::raw::{c_int, c_void}; use suricata_sys::sys::{ DetectEngineCtx, DetectEngineThreadCtx, Flow, SCDetectHelperBufferProgressRegister, @@ -27,9 +30,23 @@ use suricata_sys::sys::{ }; static mut G_NTP_VERSION_KW_ID: u16 = 0; +static mut G_NTP_MODE_KW_ID: u16 = 0; static mut G_NTP_STRATUM_KW_ID: u16 = 0; static mut G_NTP_GENERIC_BUFFER_ID: c_int = 0; +#[derive(Clone, Debug, PartialEq, EnumStringU8)] +#[repr(u8)] +pub enum NTPMode { + Reserved = 0, + SymmetricActive = 1, + SymmetricPassive = 2, + Client = 3, + Server = 4, + Broadcast = 5, + Control = 6, + Private = 7, +} + unsafe extern "C" fn ntp_detect_version_setup( de: *mut DetectEngineCtx, s: *mut Signature, raw: *const libc::c_char, ) -> c_int { @@ -69,6 +86,51 @@ unsafe extern "C" fn ntp_detect_u8_free(_de: *mut DetectEngineCtx, ctx: *mut c_v SCDetectU8Free(ctx); } +unsafe extern "C" fn ntp_detect_mode_setup( + de: *mut DetectEngineCtx, s: *mut Signature, raw: *const libc::c_char, +) -> c_int { + if SCDetectSignatureSetAppProto(s, ALPROTO_NTP) != 0 { + return -1; + } + let ctx = ntp_parse_mode(raw) as *mut c_void; + if ctx.is_null() { + return -1; + } + if SCSigMatchAppendSMToList( + de, + s, + G_NTP_MODE_KW_ID, + ctx as *mut SigMatchCtx, + G_NTP_GENERIC_BUFFER_ID, + ) + .is_null() + { + ntp_detect_u8_free(std::ptr::null_mut(), ctx); + return -1; + } + return 0; +} + +unsafe extern "C" fn ntp_parse_mode(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); + } + } + return std::ptr::null_mut(); +} + +unsafe extern "C" fn ntp_detect_mode_match( + _de: *mut DetectEngineThreadCtx, _f: *mut Flow, _flags: u8, _state: *mut c_void, + tx: *mut c_void, _sig: *const Signature, ctx: *const SigMatchCtx, +) -> c_int { + let tx = cast_pointer!(tx, NTPTransaction); + let ctx = cast_pointer!(ctx, DetectUintData); + return SCDetectU8Match(tx.mode, ctx); +} + unsafe extern "C" fn ntp_detect_stratum_setup( de: *mut DetectEngineCtx, s: *mut Signature, raw: *const libc::c_char, ) -> c_int { @@ -121,6 +183,17 @@ pub(super) unsafe extern "C" fn detect_ntp_register() { 1, ); + let kw = SCSigTableAppLiteElmt { + name: b"ntp.mode\0".as_ptr() as *const libc::c_char, + desc: b"match NTP mode\0".as_ptr() as *const libc::c_char, + url: b"/rules/ntp-keywords.html#ntp-mode\0".as_ptr() as *const libc::c_char, + AppLayerTxMatch: Some(ntp_detect_mode_match), + Setup: Some(ntp_detect_mode_setup), + Free: Some(ntp_detect_u8_free), + flags: SIGMATCH_INFO_UINT8 | SIGMATCH_INFO_ENUM_UINT, + }; + G_NTP_MODE_KW_ID = SCDetectHelperKeywordRegister(&kw); + let kw = SCSigTableAppLiteElmt { name: b"ntp.stratum\0".as_ptr() as *const libc::c_char, desc: b"match NTP stratum\0".as_ptr() as *const libc::c_char, @@ -132,3 +205,58 @@ pub(super) unsafe extern "C" fn detect_ntp_register() { }; G_NTP_STRATUM_KW_ID = SCDetectHelperKeywordRegister(&kw); } + +#[cfg(test)] +mod test { + use super::*; + use crate::detect::uint::DetectUintMode; + + #[test] + fn test_ntp_parse_known_mode_strings() { + let ctx = detect_parse_uint_enum::("reserved").unwrap(); + assert_eq!(ctx.arg1, 0); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeEqual); + + let ctx = detect_parse_uint_enum::("symmetric_active").unwrap(); + assert_eq!(ctx.arg1, 1); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeEqual); + + let ctx = detect_parse_uint_enum::("symmetric_passive").unwrap(); + assert_eq!(ctx.arg1, 2); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeEqual); + + let ctx = detect_parse_uint_enum::("client").unwrap(); + assert_eq!(ctx.arg1, 3); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeEqual); + + let ctx = detect_parse_uint_enum::("server").unwrap(); + assert_eq!(ctx.arg1, 4); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeEqual); + + let ctx = detect_parse_uint_enum::("broadcast").unwrap(); + assert_eq!(ctx.arg1, 5); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeEqual); + + let ctx = detect_parse_uint_enum::("control").unwrap(); + assert_eq!(ctx.arg1, 6); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeEqual); + + let ctx = detect_parse_uint_enum::("private").unwrap(); + assert_eq!(ctx.arg1, 7); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeEqual); + } + + #[test] + fn test_ntp_parse_mode_integer() { + let ctx = detect_parse_uint_enum::("255").unwrap(); + assert_eq!(ctx.arg1, 255); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeEqual); + } + + #[test] + fn test_ntp_parse_mode_invalid() { + assert!(detect_parse_uint_enum::("invalid_mode").is_none()); + assert!(detect_parse_uint_enum::("symmetric_private").is_none()); + assert!(detect_parse_uint_enum::("256").is_none()); + } +}