]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect: dnp3.func is now a generic integer
authorPhilippe Antoine <pantoine@oisf.net>
Thu, 16 Oct 2025 07:42:26 +0000 (09:42 +0200)
committerVictor Julien <vjulien@oisf.net>
Fri, 7 Nov 2025 00:42:35 +0000 (00:42 +0000)
Ticket: 7889

doc/userguide/rules/dnp3-keywords.rst
etc/schema.json
rust/src/dnp3/detect.rs
src/detect-dnp3.c

index 4070313910ad1641e2f4d2596f129904b6cc559b..b16263c4cbff6a8fddc72ab6a3936f8167cb09b2 100644 (file)
@@ -12,6 +12,8 @@ This keyword will match on the application function code found in DNP3
 request and responses.  It can be specified as the integer value or
 the symbolic name of the function code.
 
+dnp3_func uses an :ref:`unsigned 8-bits integer <rules-integer-keywords>`.
+
 Syntax
 ~~~~~~
 
@@ -68,7 +70,7 @@ dnp3_ind
 This keyword matches on the DNP3 internal indicator flags in the
 response application header.
 
-dnp3_ind uses :ref:`unsigned 16-bit integer <rules-integer-keywords>` with bitmasks.
+dnp3_ind uses an :ref:`unsigned 16-bits integer <rules-integer-keywords>` with bitmasks.
 
 Syntax
 ~~~~~~
index d23edbfb8b7ea94a9a29f1678d7840528a298352..03e8b374024cd163643c93c5df63feb13dedb815 100644 (file)
                             }
                         },
                         "function_code": {
-                            "type": "integer"
+                            "type": "integer",
+                            "suricata": {
+                                "keywords": [
+                                    "dnp3.func"
+                                ]
+                            }
                         },
                         "objects": {
                             "type": "array",
                                     }
                                 },
                                 "function_code": {
-                                    "type": "integer"
+                                    "type": "integer",
+                                    "suricata": {
+                                        "keywords": [
+                                            "dnp3.func"
+                                        ]
+                                    }
                                 },
                                 "objects": {
                                     "type": "array",
                                     }
                                 },
                                 "function_code": {
-                                    "type": "integer"
+                                    "type": "integer",
+                                    "suricata": {
+                                        "keywords": [
+                                            "dnp3.func"
+                                        ]
+                                    }
                                 },
                                 "objects": {
                                     "type": "array",
index 6f079b073238494e8045710a75173dda8f07ef25..00331b9ea4b58355f488ec5f3f4dcf5e7e0bb02a 100644 (file)
@@ -15,7 +15,9 @@
  * 02110-1301, USA.
  */
 
-use crate::detect::uint::{detect_parse_uint_bitflags, DetectBitflagModifier, DetectUintData};
+use crate::detect::uint::{
+    detect_parse_uint_bitflags, detect_parse_uint_enum, DetectBitflagModifier, DetectUintData,
+};
 
 use std::ffi::CStr;
 
@@ -45,6 +47,48 @@ fn dnp3_detect_ind_parse(s: &str) -> Option<DetectUintData<u16>> {
     detect_parse_uint_bitflags::<u16, Dnp3IndFlag>(s, DetectBitflagModifier::Any, false)
 }
 
+#[repr(u8)]
+#[derive(EnumStringU8)]
+pub enum Dnp3Func {
+    Confirm = 0,
+    Read = 1,
+    Write = 2,
+    Select = 3,
+    Operate = 4,
+    DirectOperate = 5,
+    DirectOperateNr = 6,
+    ImmedFreeze = 7,
+    ImmedFreezeNr = 8,
+    FreezeClear = 9,
+    FreezeClearNr = 10,
+    FreezeAtTime = 11,
+    FreezeAtTimeNr = 12,
+    ColdRestart = 13,
+    WarmRestart = 14,
+    InitializeData = 15,
+    InitializeAppl = 16,
+    StartAppl = 17,
+    StopAppl = 18,
+    SaveConfig = 19,
+    EnableUnsolicited = 20,
+    DisableUnsolicited = 21,
+    AssignClass = 22,
+    DelayMeasure = 23,
+    RecordCurrentTime = 24,
+    OpenFile = 25,
+    CloseFile = 26,
+    DeleteFile = 27,
+    GetFileInfo = 28,
+    AuthenticateFile = 29,
+    AbortFile = 30,
+    ActivateConfig = 31,
+    AutenthicateReq = 32,
+    AutenthicateErr = 33,
+    Response = 129,
+    UnsolicitedResponse = 130,
+    AuthenticateResp = 131,
+}
+
 #[no_mangle]
 pub unsafe extern "C" fn SCDnp3DetectIndParse(
     ustr: *const std::os::raw::c_char,
@@ -59,6 +103,20 @@ pub unsafe extern "C" fn SCDnp3DetectIndParse(
     return std::ptr::null_mut();
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn SCDnp3DetectFuncParse(
+    ustr: *const std::os::raw::c_char,
+) -> *mut DetectUintData<u8> {
+    let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
+    if let Ok(s) = ft_name.to_str() {
+        if let Some(ctx) = detect_parse_uint_enum::<u8, Dnp3Func>(s) {
+            let boxed = Box::new(ctx);
+            return Box::into_raw(boxed) as *mut _;
+        }
+    }
+    return std::ptr::null_mut();
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
@@ -84,4 +142,25 @@ mod test {
         assert_eq!(ctx.arg1, 0x600);
         assert!(dnp3_detect_ind_parse("something",).is_none());
     }
+
+    #[test]
+    fn dnp3_func_parse() {
+        let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("0").unwrap();
+        assert_eq!(ctx.arg1, 0);
+        let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("1").unwrap();
+        assert_eq!(ctx.arg1, 1);
+        let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("254").unwrap();
+        assert_eq!(ctx.arg1, 254);
+        let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("255").unwrap();
+        assert_eq!(ctx.arg1, 255);
+        let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("confirm").unwrap();
+        assert_eq!(ctx.arg1, 0);
+        let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("CONFIRM").unwrap();
+        assert_eq!(ctx.arg1, 0);
+        assert!(detect_parse_uint_enum::<u8, Dnp3Func>("").is_none());
+        assert!(detect_parse_uint_enum::<u8, Dnp3Func>("-1").is_none());
+        assert!(detect_parse_uint_enum::<u8, Dnp3Func>("-2").is_none());
+        assert!(detect_parse_uint_enum::<u8, Dnp3Func>("256").is_none());
+        assert!(detect_parse_uint_enum::<u8, Dnp3Func>("unknown_function_code").is_none());
+    }
 }
index 48be7e9d75ea8039d36b2207b6db49ca0a30ce24..1400fc6ed7cdba104bf06445572143bf4eaf52c7 100644 (file)
@@ -40,62 +40,11 @@ static int g_dnp3_ind_buffer_id = 0;
  * The detection struct.
  */
 typedef struct DetectDNP3_ {
-    union {
-        struct {
-            /* Function code for function code detection. */
-            uint8_t  function_code;
-        };
-        struct {
-            /* Object info for object detection. */
-            uint8_t  obj_group;
-            uint8_t  obj_variation;
-        };
-    };
+    /* Object info for object detection. */
+    uint8_t obj_group;
+    uint8_t obj_variation;
 } DetectDNP3;
 
-/**
- * Application function code name to code mappings (Snort compatible).
- */
-DNP3Mapping DNP3FunctionNameMap[] = {
-    {"confirm",              0},
-    {"read",                 1},
-    {"write",                2},
-    {"select",               3},
-    {"operate",              4},
-    {"direct_operate",       5},
-    {"direct_operate_nr",    6},
-    {"immed_freeze",         7},
-    {"immed_freeze_nr",      8},
-    {"freeze_clear",         9},
-    {"freeze_clear_nr",      10},
-    {"freeze_at_time",       11},
-    {"freeze_at_time_nr",    12},
-    {"cold_restart",         13},
-    {"warm_restart",         14},
-    {"initialize_data",      15},
-    {"initialize_appl",      16},
-    {"start_appl",           17},
-    {"stop_appl",            18},
-    {"save_config",          19},
-    {"enable_unsolicited",   20},
-    {"disable_unsolicited",  21},
-    {"assign_class",         22},
-    {"delay_measure",        23},
-    {"record_current_time",  24},
-    {"open_file",            25},
-    {"close_file",           26},
-    {"delete_file",          27},
-    {"get_file_info",        28},
-    {"authenticate_file",    29},
-    {"abort_file",           30},
-    {"activate_config",      31},
-    {"authenticate_req",     32},
-    {"authenticate_err",     33},
-    {"response",             129},
-    {"unsolicited_response", 130},
-    {"authenticate_resp",    131}
-};
-
 #ifdef UNITTESTS
 static void DetectDNP3FuncRegisterTests(void);
 static void DetectDNP3ObjRegisterTests(void);
@@ -128,64 +77,33 @@ static InspectionBuffer *GetDNP3Data(DetectEngineThreadCtx *det_ctx,
     return buffer;
 }
 
-/**
- * \brief Parse the provided function name or code to its integer
- *     value.
- *
- * If the value passed is a number, it will be checked that it falls
- * within the range of valid function codes.  If function name is
- * passed it will be resolved to its function code.
- *
- * \retval The function code as an integer if successful, -1 on
- *     failure.
- */
-static int DetectDNP3FuncParseFunctionCode(const char *str, uint8_t *fc)
+static void DetectDNP3FuncFree(DetectEngineCtx *de_ctx, void *ptr)
 {
-    if (StringParseUint8(fc, 10, (uint16_t)strlen(str), str) > 0) {
-        return 1;
-    }
-
-    /* Lookup by name. */
-    for (size_t i = 0;
-            i < sizeof(DNP3FunctionNameMap) / sizeof(DNP3Mapping); i++) {
-        if (strcasecmp(str, DNP3FunctionNameMap[i].name) == 0) {
-            *fc = (uint8_t)(DNP3FunctionNameMap[i].value);
-            return 1;
-        }
-    }
-
-    return 0;
+    SCDetectU8Free(ptr);
 }
 
 static int DetectDNP3FuncSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
 {
     SCEnter();
-    DetectDNP3 *dnp3 = NULL;
-    uint8_t function_code;
 
     if (SCDetectSignatureSetAppProto(s, ALPROTO_DNP3) != 0)
         return -1;
 
-    if (!DetectDNP3FuncParseFunctionCode(str, &function_code)) {
+    DetectU8Data *detect = SCDnp3DetectFuncParse(str);
+    if (detect == NULL) {
         SCLogError("Invalid argument \"%s\" supplied to dnp3_func keyword.", str);
         return -1;
     }
 
-    dnp3 = SCCalloc(1, sizeof(DetectDNP3));
-    if (unlikely(dnp3 == NULL)) {
-        goto error;
-    }
-    dnp3->function_code = function_code;
-
-    if (SCSigMatchAppendSMToList(
-                de_ctx, s, DETECT_DNP3FUNC, (SigMatchCtx *)dnp3, g_dnp3_match_buffer_id) == NULL) {
+    if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_DNP3FUNC, (SigMatchCtx *)detect,
+                g_dnp3_match_buffer_id) == NULL) {
         goto error;
     }
 
     SCReturnInt(0);
 error:
-    if (dnp3 != NULL) {
-        SCFree(dnp3);
+    if (detect != NULL) {
+        DetectDNP3FuncFree(NULL, detect);
     }
     SCReturnInt(-1);
 }
@@ -302,16 +220,15 @@ static int DetectDNP3FuncMatch(DetectEngineThreadCtx *det_ctx,
     const SigMatchCtx *ctx)
 {
     DNP3Transaction *tx = (DNP3Transaction *)txv;
-    DetectDNP3 *detect = (DetectDNP3 *)ctx;
-    int match = 0;
+    DetectU8Data *detect = (DetectU8Data *)ctx;
 
     if (flags & STREAM_TOSERVER && tx->is_request) {
-        match = detect->function_code == tx->ah.function_code;
+        return DetectU8Match(tx->ah.function_code, detect);
     } else if (flags & STREAM_TOCLIENT && !tx->is_request) {
-        match = detect->function_code == tx->ah.function_code;
+        return DetectU8Match(tx->ah.function_code, detect);
     }
 
-    return match;
+    return 0;
 }
 
 static int DetectDNP3ObjMatch(DetectEngineThreadCtx *det_ctx,
@@ -363,7 +280,8 @@ static void DetectDNP3FuncRegister(void)
     sigmatch_table[DETECT_DNP3FUNC].Match = NULL;
     sigmatch_table[DETECT_DNP3FUNC].AppLayerTxMatch = DetectDNP3FuncMatch;
     sigmatch_table[DETECT_DNP3FUNC].Setup = DetectDNP3FuncSetup;
-    sigmatch_table[DETECT_DNP3FUNC].Free = DetectDNP3Free;
+    sigmatch_table[DETECT_DNP3FUNC].Free = DetectDNP3FuncFree;
+    sigmatch_table[DETECT_DNP3FUNC].flags = SIGMATCH_INFO_UINT8 | SIGMATCH_INFO_ENUM_UINT;
 #ifdef UNITTESTS
     sigmatch_table[DETECT_DNP3FUNC].RegisterTests = DetectDNP3FuncRegisterTests;
 #endif
@@ -472,39 +390,6 @@ void DetectDNP3Register(void)
 #include "flow-util.h"
 #include "stream-tcp.h"
 
-static int DetectDNP3FuncParseFunctionCodeTest(void)
-{
-    uint8_t fc;
-
-    /* Valid. */
-    FAIL_IF_NOT(DetectDNP3FuncParseFunctionCode("0", &fc));
-    FAIL_IF(fc != 0);
-
-    FAIL_IF_NOT(DetectDNP3FuncParseFunctionCode("1", &fc));
-    FAIL_IF(fc != 1);
-
-    FAIL_IF_NOT(DetectDNP3FuncParseFunctionCode("254", &fc));
-    FAIL_IF(fc != 254);
-
-    FAIL_IF_NOT(DetectDNP3FuncParseFunctionCode("255", &fc));
-    FAIL_IF(fc != 255);
-
-    FAIL_IF_NOT(DetectDNP3FuncParseFunctionCode("confirm", &fc));
-    FAIL_IF(fc != 0);
-
-    FAIL_IF_NOT(DetectDNP3FuncParseFunctionCode("CONFIRM", &fc));
-    FAIL_IF(fc != 0);
-
-    /* Invalid. */
-    FAIL_IF(DetectDNP3FuncParseFunctionCode("", &fc));
-    FAIL_IF(DetectDNP3FuncParseFunctionCode("-1", &fc));
-    FAIL_IF(DetectDNP3FuncParseFunctionCode("-2", &fc));
-    FAIL_IF(DetectDNP3FuncParseFunctionCode("256", &fc));
-    FAIL_IF(DetectDNP3FuncParseFunctionCode("unknown_function_code", &fc));
-
-    PASS;
-}
-
 static int DetectDNP3FuncTest01(void)
 {
     DetectEngineCtx *de_ctx = DetectEngineCtxInit();
@@ -519,8 +404,8 @@ static int DetectDNP3FuncTest01(void)
     FAIL_IF_NULL(sm);
     FAIL_IF_NULL(sm->ctx);
 
-    DetectDNP3 *dnp3func = (DetectDNP3 *)sm->ctx;
-    FAIL_IF(dnp3func->function_code != 2);
+    DetectU8Data *dnp3func = (DetectU8Data *)sm->ctx;
+    FAIL_IF(dnp3func->arg1 != 2);
 
     DetectEngineCtxFree(de_ctx);
     PASS;
@@ -568,8 +453,6 @@ static int DetectDNP3ObjParseTest(void)
 
 static void DetectDNP3FuncRegisterTests(void)
 {
-    UtRegisterTest("DetectDNP3FuncParseFunctionCodeTest",
-                   DetectDNP3FuncParseFunctionCodeTest);
     UtRegisterTest("DetectDNP3FuncTest01", DetectDNP3FuncTest01);
 }