mirror of
https://github.com/OISF/suricata.git
synced 2026-10-12 02:07:10 +00:00
detect/dnp3: make dnp3.ind a generic uint16 bitflags keyword
Ticket: 6724 Allows operations such as negation
This commit is contained in:
@@ -681,6 +681,11 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"suricata": {
|
||||
"keywords": [
|
||||
"dnp3.iin"
|
||||
]
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
@@ -908,6 +913,11 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"suricata": {
|
||||
"keywords": [
|
||||
"dnp3.iin"
|
||||
]
|
||||
}
|
||||
},
|
||||
"src": {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/* Copyright (C) 2025 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.
|
||||
*/
|
||||
|
||||
use crate::detect::uint::{detect_parse_uint_bitflags, DetectUintData};
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(EnumStringU16)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum Dnp3IndFlag {
|
||||
Device_restart = 0x8000,
|
||||
Device_trouble = 0x4000,
|
||||
Local_control = 0x2000,
|
||||
Need_time = 0x1000,
|
||||
Class_3_events = 0x0800,
|
||||
Class_2_events = 0x0400,
|
||||
Class_1_events = 0x0200,
|
||||
All_stations = 0x0100,
|
||||
Reserved_1 = 0x0080,
|
||||
Reserved_2 = 0x0040,
|
||||
Config_corrupt = 0x0020,
|
||||
Already_executing = 0x0010,
|
||||
Event_buffer_overflow = 0x0008,
|
||||
Parameter_error = 0x0004,
|
||||
Object_unknown = 0x0002,
|
||||
No_func_code_support = 0x0001,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn SCDnp3DetectIndParse(
|
||||
ustr: *const std::os::raw::c_char,
|
||||
) -> *mut DetectUintData<u16> {
|
||||
let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
|
||||
if let Ok(s) = ft_name.to_str() {
|
||||
if let Some(ctx) = detect_parse_uint_bitflags::<u16, Dnp3IndFlag>(s) {
|
||||
let boxed = Box::new(ctx);
|
||||
return Box::into_raw(boxed) as *mut _;
|
||||
}
|
||||
}
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn dnp3_ind_parse() {
|
||||
let ctx = detect_parse_uint_bitflags::<u16, Dnp3IndFlag>("0").unwrap();
|
||||
assert_eq!(ctx.arg1, 0);
|
||||
let ctx = detect_parse_uint_bitflags::<u16, Dnp3IndFlag>("1").unwrap();
|
||||
assert_eq!(ctx.arg1, 1);
|
||||
let ctx = detect_parse_uint_bitflags::<u16, Dnp3IndFlag>("0x0").unwrap();
|
||||
assert_eq!(ctx.arg1, 0);
|
||||
let ctx = detect_parse_uint_bitflags::<u16, Dnp3IndFlag>("0x0000").unwrap();
|
||||
assert_eq!(ctx.arg1, 0);
|
||||
let ctx = detect_parse_uint_bitflags::<u16, Dnp3IndFlag>("0x0001").unwrap();
|
||||
assert_eq!(ctx.arg1, 1);
|
||||
let ctx = detect_parse_uint_bitflags::<u16, Dnp3IndFlag>("0x8421").unwrap();
|
||||
assert_eq!(ctx.arg1, 0x8421);
|
||||
assert!(detect_parse_uint_bitflags::<u16, Dnp3IndFlag>("a").is_none());
|
||||
let ctx = detect_parse_uint_bitflags::<u16, Dnp3IndFlag>("all_stations").unwrap();
|
||||
assert_eq!(ctx.arg1, 0x0100);
|
||||
let ctx = detect_parse_uint_bitflags::<u16, Dnp3IndFlag>("class_1_events , class_2_events")
|
||||
.unwrap();
|
||||
assert_eq!(ctx.arg1, 0x600);
|
||||
assert!(detect_parse_uint_bitflags::<u16, Dnp3IndFlag>("something").is_none());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* Copyright (C) 2025 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.
|
||||
*/
|
||||
|
||||
use crate::detect::EnumString;
|
||||
use crate::jsonbuilder::{JsonBuilder, JsonError};
|
||||
|
||||
use super::detect::Dnp3IndFlag;
|
||||
|
||||
fn log_iin(js: &mut JsonBuilder, iin: u16) -> Result<(), JsonError> {
|
||||
js.open_array("indicators")?;
|
||||
|
||||
for i in 0..16 {
|
||||
if (iin & (1 << i)) != 0 {
|
||||
let ind = Dnp3IndFlag::from_u(1 << i).unwrap();
|
||||
js.append_string(ind.to_str())?;
|
||||
}
|
||||
}
|
||||
js.close()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn SCJsonDNP3LogIin(js: &mut JsonBuilder, iin: u16) -> bool {
|
||||
if iin == 0 {
|
||||
return false;
|
||||
}
|
||||
log_iin(js, iin).is_ok()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* Copyright (C) 2025 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.
|
||||
*/
|
||||
|
||||
//! Application layer dnp3 detection.
|
||||
|
||||
pub mod detect;
|
||||
pub mod log;
|
||||
@@ -96,6 +96,7 @@ pub mod handshake;
|
||||
|
||||
pub mod lua;
|
||||
|
||||
pub mod dnp3;
|
||||
pub mod dns;
|
||||
pub mod mdns;
|
||||
pub mod nfs;
|
||||
|
||||
+15
-161
@@ -27,12 +27,14 @@
|
||||
#include "detect-engine-mpm.h"
|
||||
#include "detect-engine-prefilter.h"
|
||||
#include "detect-engine-content-inspection.h"
|
||||
#include "detect-engine-uint.h"
|
||||
|
||||
#include "app-layer-dnp3.h"
|
||||
#include "util-byte.h"
|
||||
|
||||
static int g_dnp3_match_buffer_id = 0;
|
||||
static int g_dnp3_data_buffer_id = 0;
|
||||
static int g_dnp3_ind_buffer_id = 0;
|
||||
|
||||
/**
|
||||
* The detection struct.
|
||||
@@ -43,10 +45,6 @@ typedef struct DetectDNP3_ {
|
||||
/* Function code for function code detection. */
|
||||
uint8_t function_code;
|
||||
};
|
||||
struct {
|
||||
/* Internal indicator flags for IIN detection. */
|
||||
uint16_t ind_flags;
|
||||
};
|
||||
struct {
|
||||
/* Object info for object detection. */
|
||||
uint8_t obj_group;
|
||||
@@ -55,31 +53,6 @@ typedef struct DetectDNP3_ {
|
||||
};
|
||||
} DetectDNP3;
|
||||
|
||||
/**
|
||||
* Indicator names to value mappings (Snort compatible).
|
||||
*/
|
||||
DNP3Mapping DNP3IndicatorsMap[] = {
|
||||
{"device_restart", 0x8000},
|
||||
{"device_trouble", 0x4000},
|
||||
{"local_control", 0x2000},
|
||||
{"need_time", 0x1000},
|
||||
{"class_3_events", 0x0800},
|
||||
{"class_2_events", 0x0400},
|
||||
{"class_1_events", 0x0200},
|
||||
{"all_stations", 0x0100},
|
||||
|
||||
{"reserved_1", 0x0080},
|
||||
{"reserved_2", 0x0040},
|
||||
{"config_corrupt", 0x0020},
|
||||
{"already_executing", 0x0010},
|
||||
{"event_buffer_overflow", 0x0008},
|
||||
{"parameter_error", 0x0004},
|
||||
{"object_unknown", 0x0002},
|
||||
{"no_func_code_support", 0x0001},
|
||||
|
||||
{NULL, 0},
|
||||
};
|
||||
|
||||
/**
|
||||
* Application function code name to code mappings (Snort compatible).
|
||||
*/
|
||||
@@ -125,27 +98,9 @@ DNP3Mapping DNP3FunctionNameMap[] = {
|
||||
|
||||
#ifdef UNITTESTS
|
||||
static void DetectDNP3FuncRegisterTests(void);
|
||||
static void DetectDNP3IndRegisterTests(void);
|
||||
static void DetectDNP3ObjRegisterTests(void);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Utility function to trim leading and trailing whitespace
|
||||
* from a string.
|
||||
*/
|
||||
static char *TrimString(char *str)
|
||||
{
|
||||
char *end = str + strlen(str) - 1;
|
||||
while (isspace(*str)) {
|
||||
str++;
|
||||
}
|
||||
while (end > str && isspace(*end)) {
|
||||
end--;
|
||||
}
|
||||
*(end + 1) = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
static InspectionBuffer *GetDNP3Data(DetectEngineThreadCtx *det_ctx,
|
||||
const DetectEngineTransforms *transforms,
|
||||
Flow *_f, const uint8_t flow_flags,
|
||||
@@ -235,80 +190,33 @@ error:
|
||||
SCReturnInt(-1);
|
||||
}
|
||||
|
||||
static int DetectDNP3IndParseByName(const char *str, uint16_t *flags)
|
||||
static void DetectDNP3IndFree(DetectEngineCtx *de_ctx, void *ptr)
|
||||
{
|
||||
char tmp[strlen(str) + 1];
|
||||
char *p, *last = NULL;
|
||||
|
||||
strlcpy(tmp, str, sizeof(tmp));
|
||||
|
||||
for ((p = strtok_r(tmp, ",", &last)); p; (p = strtok_r(NULL, ",", &last))) {
|
||||
p = TrimString(p);
|
||||
int found = 0;
|
||||
int i = 0;
|
||||
while (DNP3IndicatorsMap[i].name != NULL) {
|
||||
if (strcasecmp(p, DNP3IndicatorsMap[i].name) == 0) {
|
||||
*flags |= DNP3IndicatorsMap[i].value;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
SCLogError("Bad argument \"%s\" supplied to dnp3.ind keyword.", p);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int DetectDNP3IndParse(const char *str, uint16_t *flags)
|
||||
{
|
||||
*flags = 0;
|
||||
|
||||
if (StringParseUint16(flags, 0, (uint16_t)strlen(str), str) > 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Parse by name - will log a more specific error message on error. */
|
||||
if (DetectDNP3IndParseByName(str, flags)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
SCDetectU16Free(ptr);
|
||||
}
|
||||
|
||||
static int DetectDNP3IndSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
|
||||
{
|
||||
SCEnter();
|
||||
DetectDNP3 *detect = NULL;
|
||||
uint16_t flags;
|
||||
|
||||
if (SCDetectSignatureSetAppProto(s, ALPROTO_DNP3) != 0)
|
||||
return -1;
|
||||
|
||||
if (!DetectDNP3IndParse(str, &flags)) {
|
||||
DetectU16Data *detect = SCDnp3DetectIndParse(str);
|
||||
if (detect == NULL) {
|
||||
SCLogError("Invalid argument \"%s\" supplied to dnp3.ind keyword.", str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
detect = SCCalloc(1, sizeof(DetectDNP3));
|
||||
if (unlikely(detect == NULL)) {
|
||||
goto error;
|
||||
}
|
||||
detect->ind_flags = flags;
|
||||
|
||||
if (SCSigMatchAppendSMToList(
|
||||
de_ctx, s, DETECT_DNP3IND, (SigMatchCtx *)detect, g_dnp3_match_buffer_id) == NULL) {
|
||||
de_ctx, s, DETECT_DNP3IND, (SigMatchCtx *)detect, g_dnp3_ind_buffer_id) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
SCReturnInt(0);
|
||||
error:
|
||||
if (detect != NULL) {
|
||||
SCFree(detect);
|
||||
DetectDNP3IndFree(NULL, detect);
|
||||
}
|
||||
SCReturnInt(-1);
|
||||
}
|
||||
@@ -438,16 +346,9 @@ static int DetectDNP3IndMatch(DetectEngineThreadCtx *det_ctx,
|
||||
const SigMatchCtx *ctx)
|
||||
{
|
||||
DNP3Transaction *tx = (DNP3Transaction *)txv;
|
||||
DetectDNP3 *detect = (DetectDNP3 *)ctx;
|
||||
DetectU16Data *detect = (DetectU16Data *)ctx;
|
||||
|
||||
if (flags & STREAM_TOCLIENT) {
|
||||
if ((tx->iin.iin1 & (detect->ind_flags >> 8)) ||
|
||||
(tx->iin.iin2 & (detect->ind_flags & 0xf))) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return DetectU16Match((uint16_t)((tx->iin.iin1 << 8) | tx->iin.iin2), detect);
|
||||
}
|
||||
|
||||
static void DetectDNP3FuncRegister(void)
|
||||
@@ -481,10 +382,8 @@ static void DetectDNP3IndRegister(void)
|
||||
sigmatch_table[DETECT_DNP3IND].Match = NULL;
|
||||
sigmatch_table[DETECT_DNP3IND].AppLayerTxMatch = DetectDNP3IndMatch;
|
||||
sigmatch_table[DETECT_DNP3IND].Setup = DetectDNP3IndSetup;
|
||||
sigmatch_table[DETECT_DNP3IND].Free = DetectDNP3Free;
|
||||
#ifdef UNITTESTS
|
||||
sigmatch_table[DETECT_DNP3IND].RegisterTests = DetectDNP3IndRegisterTests;
|
||||
#endif
|
||||
sigmatch_table[DETECT_DNP3IND].Free = DetectDNP3IndFree;
|
||||
sigmatch_table[DETECT_DNP3IND].flags = SIGMATCH_INFO_UINT16 | SIGMATCH_INFO_BITFLAGS_UINT;
|
||||
SCReturn;
|
||||
}
|
||||
|
||||
@@ -560,6 +459,9 @@ void DetectDNP3Register(void)
|
||||
|
||||
g_dnp3_match_buffer_id = DetectBufferTypeRegister("dnp3");
|
||||
|
||||
DetectAppLayerInspectEngineRegister(
|
||||
"dnp3_ind", ALPROTO_DNP3, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectGenericList, NULL);
|
||||
g_dnp3_ind_buffer_id = DetectBufferTypeRegister("dnp3_ind");
|
||||
}
|
||||
|
||||
#ifdef UNITTESTS
|
||||
@@ -624,46 +526,6 @@ static int DetectDNP3FuncTest01(void)
|
||||
PASS;
|
||||
}
|
||||
|
||||
static int DetectDNP3IndTestParseAsInteger(void)
|
||||
{
|
||||
uint16_t flags = 0;
|
||||
|
||||
FAIL_IF(!DetectDNP3IndParse("0", &flags));
|
||||
FAIL_IF(flags != 0);
|
||||
FAIL_IF(!DetectDNP3IndParse("1", &flags));
|
||||
FAIL_IF(flags != 0x0001);
|
||||
|
||||
FAIL_IF(!DetectDNP3IndParse("0x0", &flags));
|
||||
FAIL_IF(flags != 0);
|
||||
FAIL_IF(!DetectDNP3IndParse("0x0000", &flags));
|
||||
FAIL_IF(flags != 0);
|
||||
FAIL_IF(!DetectDNP3IndParse("0x0001", &flags));
|
||||
FAIL_IF(flags != 0x0001);
|
||||
|
||||
FAIL_IF(!DetectDNP3IndParse("0x8421", &flags));
|
||||
FAIL_IF(flags != 0x8421);
|
||||
|
||||
FAIL_IF(DetectDNP3IndParse("a", &flags));
|
||||
|
||||
PASS;
|
||||
}
|
||||
|
||||
static int DetectDNP3IndTestParseByName(void)
|
||||
{
|
||||
uint16_t flags = 0;
|
||||
|
||||
FAIL_IF(!DetectDNP3IndParse("all_stations", &flags));
|
||||
FAIL_IF(!(flags & 0x0100));
|
||||
FAIL_IF(!DetectDNP3IndParse("class_1_events , class_2_events", &flags));
|
||||
FAIL_IF(!(flags & 0x0200));
|
||||
FAIL_IF(!(flags & 0x0400));
|
||||
FAIL_IF((flags & 0xf9ff));
|
||||
|
||||
FAIL_IF(DetectDNP3IndParse("something", &flags));
|
||||
|
||||
PASS;
|
||||
}
|
||||
|
||||
static int DetectDNP3ObjSetupTest(void)
|
||||
{
|
||||
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
|
||||
@@ -711,14 +573,6 @@ static void DetectDNP3FuncRegisterTests(void)
|
||||
UtRegisterTest("DetectDNP3FuncTest01", DetectDNP3FuncTest01);
|
||||
}
|
||||
|
||||
static void DetectDNP3IndRegisterTests(void)
|
||||
{
|
||||
UtRegisterTest("DetectDNP3IndTestParseAsInteger",
|
||||
DetectDNP3IndTestParseAsInteger);
|
||||
UtRegisterTest("DetectDNP3IndTestParseByName",
|
||||
DetectDNP3IndTestParseByName);
|
||||
}
|
||||
|
||||
static void DetectDNP3ObjRegisterTests(void)
|
||||
{
|
||||
UtRegisterTest("DetectDNP3ObjParseTest", DetectDNP3ObjParseTest);
|
||||
|
||||
@@ -26,9 +26,6 @@ typedef struct DNP3Mapping_ {
|
||||
uint16_t value;
|
||||
} DNP3Mapping;
|
||||
|
||||
/* Map of internal indicators to value for external use. */
|
||||
extern DNP3Mapping DNP3IndicatorsMap[];
|
||||
|
||||
void DetectDNP3Register(void);
|
||||
|
||||
#endif /* SURICATA_DETECT_DNP3_H */
|
||||
|
||||
+1
-17
@@ -61,22 +61,6 @@ static void JsonDNP3LogLinkControl(SCJsonBuilder *js, uint8_t lc)
|
||||
SCJbSetUint(js, "function_code", DNP3_LINK_FC(lc));
|
||||
}
|
||||
|
||||
static void JsonDNP3LogIin(SCJsonBuilder *js, uint16_t iin)
|
||||
{
|
||||
if (iin) {
|
||||
SCJbOpenArray(js, "indicators");
|
||||
|
||||
int mapping = 0;
|
||||
do {
|
||||
if (iin & DNP3IndicatorsMap[mapping].value) {
|
||||
SCJbAppendString(js, DNP3IndicatorsMap[mapping].name);
|
||||
}
|
||||
mapping++;
|
||||
} while (DNP3IndicatorsMap[mapping].name != NULL);
|
||||
SCJbClose(js);
|
||||
}
|
||||
}
|
||||
|
||||
static void JsonDNP3LogApplicationControl(SCJsonBuilder *js, uint8_t ac)
|
||||
{
|
||||
SCJbSetBool(js, "fir", DNP3_APP_FIR(ac));
|
||||
@@ -206,7 +190,7 @@ static void JsonDNP3LogResponse(SCJsonBuilder *js, DNP3Transaction *dnp3tx)
|
||||
SCJbClose(js);
|
||||
|
||||
SCJbOpenObject(js, "iin");
|
||||
JsonDNP3LogIin(js, (uint16_t)(dnp3tx->iin.iin1 << 8 | dnp3tx->iin.iin2));
|
||||
SCJsonDNP3LogIin(js, (uint16_t)(dnp3tx->iin.iin1 << 8 | dnp3tx->iin.iin2));
|
||||
SCJbClose(js);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user