From a458a94dca27e33dd9b4a629ef0a39b6ea41fb39 Mon Sep 17 00:00:00 2001 From: Simon Dugas Date: Thu, 11 Feb 2021 15:58:43 -0500 Subject: [PATCH] modbus: move from C to rust Adds a new rust modbus app layer parser and detection module. Moves the C module to rust but leaves the test cases in place to regression test the new rust module. --- rust/Cargo.toml.in | 4 + rust/cbindgen.toml | 1 + rust/src/lib.rs | 1 + rust/src/modbus/detect.rs | 539 ++++++++++++ rust/src/modbus/mod.rs | 19 + rust/src/modbus/modbus.rs | 1587 +++++++++++++++++++++++++++++++++ src/app-layer-modbus.c | 1706 +++--------------------------------- src/app-layer-modbus.h | 70 -- src/detect-engine-modbus.c | 255 +----- src/detect-modbus.c | 609 ++++--------- src/suricata.c | 1 - 11 files changed, 2446 insertions(+), 2346 deletions(-) create mode 100644 rust/src/modbus/detect.rs create mode 100644 rust/src/modbus/mod.rs create mode 100644 rust/src/modbus/modbus.rs diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index 89681f478c..234d5dc026 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -33,6 +33,8 @@ widestring = "~0.4.3" flate2 = "~1.0.19" brotli = "~3.3.0" +sawp-modbus = "~0.4.0" +sawp = "~0.4.0" der-parser = "~4.0.2" kerberos-parser = "~0.5.0" ntp-parser = "~0.4.0" @@ -45,6 +47,8 @@ sha2 = "~0.9.2" digest = "~0.9.0" sha-1 = "~0.9.2" md-5 = "~0.9.1" +regex = "~1.4.2" +lazy_static = "~1.4.0" [dev-dependencies] test-case = "~1.1.0" diff --git a/rust/cbindgen.toml b/rust/cbindgen.toml index 71609d4dc1..d3b33312e3 100644 --- a/rust/cbindgen.toml +++ b/rust/cbindgen.toml @@ -75,6 +75,7 @@ include = [ "AppLayerGetTxIterTuple", "RdpState", "SIPState", + "ModbusState", "CMark", ] diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 10ed302d47..e563d8c9cd 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -66,6 +66,7 @@ pub mod ftp; pub mod smb; pub mod krb; pub mod dcerpc; +pub mod modbus; pub mod ike; pub mod snmp; diff --git a/rust/src/modbus/detect.rs b/rust/src/modbus/detect.rs new file mode 100644 index 0000000000..af402f1828 --- /dev/null +++ b/rust/src/modbus/detect.rs @@ -0,0 +1,539 @@ +/* Copyright (C) 2021 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 super::modbus::ModbusTransaction; +use lazy_static::lazy_static; +use regex::Regex; +use sawp_modbus::{AccessType, CodeCategory, Data, Flags, FunctionCode, Message}; +use std::ffi::CStr; +use std::ops::{Range, RangeInclusive}; +use std::os::raw::{c_char, c_void}; +use std::str::FromStr; + +lazy_static! { + static ref ACCESS_RE: Regex = Regex::new( + "^\\s*\"?\\s*access\\s*(read|write)\ + \\s*(discretes|coils|input|holding)?\ + (?:,\\s*address\\s+([<>]?\\d+)(?:<>(\\d+))?\ + (?:,\\s*value\\s+([<>]?\\d+)(?:<>(\\d+))?)?)?\ + \\s*\"?\\s*$" + ) + .unwrap(); + static ref FUNC_RE: Regex = Regex::new( + "^\\s*\"?\\s*function\\s*(!?[A-z0-9]+)\ + (?:,\\s*subfunction\\s+(\\d+))?\\s*\"?\\s*$" + ) + .unwrap(); + static ref UNIT_RE: Regex = Regex::new( + "^\\s*\"?\\s*unit\\s+([<>]?\\d+)\ + (?:<>(\\d+))?(?:,\\s*(.*))?\\s*\"?\\s*$" + ) + .unwrap(); +} + +#[derive(Debug, PartialEq)] +pub struct DetectModbusRust { + category: Option>, + function: Option, + subfunction: Option, + access_type: Option>, + unit_id: Option>, + address: Option>, + value: Option>, +} + +/// TODO: remove these after regression testing commit +#[no_mangle] +pub extern "C" fn rs_modbus_get_category(modbus: *const DetectModbusRust) -> u8 { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.category.map(|val| val.bits()).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_get_function(modbus: *const DetectModbusRust) -> u8 { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.function.map(|val| val as u8).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_get_subfunction(modbus: *const DetectModbusRust) -> u16 { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.subfunction.unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_get_has_subfunction(modbus: *const DetectModbusRust) -> bool { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.subfunction.is_some() +} + +#[no_mangle] +pub extern "C" fn rs_modbus_get_access_type(modbus: *const DetectModbusRust) -> u8 { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.access_type.map(|val| val.bits()).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_get_unit_id_min(modbus: *const DetectModbusRust) -> u16 { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.unit_id.as_ref().map(|val| val.start).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_get_unit_id_max(modbus: *const DetectModbusRust) -> u16 { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.unit_id.as_ref().map(|val| val.end).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_get_address_min(modbus: *const DetectModbusRust) -> u16 { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.address.as_ref().map(|val| val.start).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_get_address_max(modbus: *const DetectModbusRust) -> u16 { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.address.as_ref().map(|val| val.end).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_get_data_min(modbus: *const DetectModbusRust) -> u16 { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.value.as_ref().map(|val| val.start).unwrap_or(0) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_get_data_max(modbus: *const DetectModbusRust) -> u16 { + let modbus = unsafe { modbus.as_ref() }.unwrap(); + modbus.value.as_ref().map(|val| val.end).unwrap_or(0) +} + +impl Default for DetectModbusRust { + fn default() -> Self { + DetectModbusRust { + category: None, + function: None, + subfunction: None, + access_type: None, + unit_id: None, + address: None, + value: None, + } + } +} + +/// Compares a range from the alert signature to the transaction's unit_id/address/value +/// range. If the signature's range intersects with the transaction, it is a match and true is +/// returned. +fn check_match_range(sig_range: &Range, trans_range: RangeInclusive) -> bool { + if sig_range.start == sig_range.end { + sig_range.start >= *trans_range.start() && sig_range.start <= *trans_range.end() + } else if sig_range.start == std::u16::MIN { + sig_range.end > *trans_range.start() + } else if sig_range.end == std::u16::MAX { + sig_range.start < *trans_range.end() + } else { + sig_range.start < *trans_range.end() && *trans_range.start() < sig_range.end + } +} + +/// Compares a range from the alert signature to the transaction's unit_id/address/value. +/// If the signature's range intersects with the transaction, it is a match and true is +/// returned. +fn check_match(sig_range: &Range, value: u16) -> bool { + if sig_range.start == sig_range.end { + sig_range.start == value + } else if sig_range.start == std::u16::MIN { + sig_range.end > value + } else if sig_range.end == std::u16::MAX { + sig_range.start < value + } else { + sig_range.start < value && value < sig_range.end + } +} + +/// Gets the min/max range of an alert signature from the respective capture groups. +/// In the case where the max is not given, it is set based on the first char of the min str +/// which indicates what range we are looking for: +/// '<' = std::u16::MIN..min +/// '>' = min..std::u16::MAX +/// _ = min..min +/// If the max is given, the range returned is min..max +fn parse_range(min_str: &str, max_str: &str) -> Result, ()> { + if max_str.is_empty() { + if let Some(sign) = min_str.chars().next() { + match min_str[!sign.is_ascii_digit() as usize..].parse::() { + Ok(num) => match sign { + '>' => Ok(num..std::u16::MAX), + '<' => Ok(std::u16::MIN..num), + _ => Ok(num..num), + }, + Err(_) => { + SCLogError!("Invalid min number: {}", min_str); + Err(()) + } + } + } else { + Err(()) + } + } else { + let min = match min_str.parse::() { + Ok(num) => num, + Err(_) => { + SCLogError!("Invalid min number: {}", min_str); + return Err(()); + } + }; + + let max = match max_str.parse::() { + Ok(num) => num, + Err(_) => { + SCLogError!("Invalid max number: {}", max_str); + return Err(()); + } + }; + + Ok(min..max) + } +} + +/// Intermediary function between the C code and the parsing functions. +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_parse(c_arg: *const c_char) -> *mut c_void { + if c_arg.is_null() { + return std::ptr::null_mut(); + } + if let Ok(arg) = CStr::from_ptr(c_arg).to_str() { + match parse_unit_id(&arg) + .or_else(|_| parse_function(&arg)) + .or_else(|_| parse_access(&arg)) + { + Ok(detect) => return Box::into_raw(Box::new(detect)) as *mut c_void, + Err(()) => return std::ptr::null_mut(), + } + } + std::ptr::null_mut() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_free(ptr: *mut c_void) { + if !ptr.is_null() { + let _ = Box::from_raw(ptr as *mut DetectModbusRust); + } +} + +/// Compares a transaction to a signature to determine whether the transaction +/// matches the signature. If it does, 1 is returned; otherwise 0 is returned. +#[no_mangle] +pub extern "C" fn rs_modbus_inspect(tx: &ModbusTransaction, modbus: &DetectModbusRust) -> u8 { + // All necessary information can be found in the request (value inspection currently + // only supports write functions, which hold the value in the request). + // Only inspect the response in the case where there is no request. + let msg = match &tx.request { + Some(r) => r, + None => match &tx.response { + Some(r) => r, + None => return 0, + }, + }; + + if let Some(unit_id) = &modbus.unit_id { + if !check_match(unit_id, msg.unit_id.into()) { + return 0; + } + } + + if let Some(access_type) = &modbus.access_type { + let rd_wr_access = *access_type & (AccessType::READ | AccessType::WRITE); + let access_func = *access_type & AccessType::FUNC_MASK; + + if rd_wr_access.is_empty() + || !msg.access_type.intersects(rd_wr_access) + || (!access_func.is_empty() && !msg.access_type.intersects(access_func)) + { + return 0; + } + + return inspect_data(msg, modbus) as u8; + } + + if let Some(category) = modbus.category { + return u8::from(msg.category.intersects(category)); + } + + match &modbus.function { + Some(func) if func == &msg.function.code => match modbus.subfunction { + Some(subfunc) => { + if let Data::Diagnostic { func, data: _ } = &msg.data { + u8::from(subfunc == func.raw) + } else { + 0 + } + } + None => 1, + }, + None => 1, + _ => 0, + } +} + +/// Compares the transaction's data with the signature to determine whether or +/// not it is a match +fn inspect_data(msg: &Message, modbus: &DetectModbusRust) -> bool { + let sig_address = if let Some(sig_addr) = &modbus.address { + // Compare the transaction's address with the signature to determine whether or + // not it is a match + if let Some(req_addr) = msg.get_address_range() { + if !check_match_range(sig_addr, req_addr) { + return false; + } + } else { + return false; + } + + sig_addr.start + } else { + return true; + }; + + let sig_value = if let Some(value) = &modbus.value { + value + } else { + return true; + }; + + if let Some(value) = msg.get_write_value_at_address(&sig_address) { + check_match(sig_value, value) + } else { + false + } +} + +/// Parses the access type for the signature +fn parse_access(access_str: &str) -> Result { + let re = if let Some(re) = ACCESS_RE.captures(access_str) { + re + } else { + return Err(()); + }; + + // 1: Read | Write + let mut access_type: Flags = match re.get(1) { + Some(access) => match AccessType::from_str(access.as_str()) { + Ok(access_type) => access_type.into(), + Err(_) => { + SCLogError!("Unknown access keyword {}", access.as_str()); + return Err(()); + } + }, + None => { + SCLogError!("No access keyword found"); + return Err(()); + } + }; + + // 2: Discretes | Coils | Input | Holding + access_type = match re.get(2) { + Some(x) if x.as_str() == "coils" => access_type | AccessType::COILS, + Some(x) if x.as_str() == "holding" => access_type | AccessType::HOLDING, + Some(x) if x.as_str() == "discretes" => { + if access_type == AccessType::WRITE { + SCLogError!("Discrete access is only read access"); + return Err(()); + } + access_type | AccessType::DISCRETES + } + Some(x) if x.as_str() == "input" => { + if access_type == AccessType::WRITE { + SCLogError!("Input access is only read access"); + return Err(()); + } + access_type | AccessType::INPUT + } + Some(unknown) => { + SCLogError!("Unknown access keyword {}", unknown.as_str()); + return Err(()); + } + None => access_type, + }; + + // 3: Address min + let address = if let Some(min) = re.get(3) { + // 4: Address max + let max_str = if let Some(max) = re.get(4) { + max.as_str() + } else { + "" + }; + parse_range(min.as_str(), max_str)? + } else { + return Ok(DetectModbusRust { + access_type: Some(access_type), + ..Default::default() + }); + }; + + // 5: Value min + let value = if let Some(min) = re.get(5) { + if address.start != address.end { + SCLogError!("rule contains conflicting keywords (address range and value)."); + return Err(()); + } + + if access_type == AccessType::READ { + SCLogError!("Value keyword only works in write access"); + return Err(()); + } + + // 6: Value max + let max_str = if let Some(max) = re.get(6) { + max.as_str() + } else { + "" + }; + + parse_range(min.as_str(), max_str)? + } else { + return Ok(DetectModbusRust { + access_type: Some(access_type), + address: Some(address), + ..Default::default() + }); + }; + + Ok(DetectModbusRust { + access_type: Some(access_type), + address: Some(address), + value: Some(value), + ..Default::default() + }) +} + +fn parse_function(func_str: &str) -> Result { + let re = if let Some(re) = FUNC_RE.captures(func_str) { + re + } else { + return Err(()); + }; + + let mut modbus: DetectModbusRust = Default::default(); + + // 1: Function + if let Some(x) = re.get(1) { + let word = x.as_str(); + + // Digit + if let Ok(num) = word.parse::() { + if num == 0 { + SCLogError!("Invalid modbus function value"); + return Err(()); + } + + modbus.function = Some(FunctionCode::from_raw(num)); + + // 2: Subfunction (optional) + match re.get(2) { + Some(x) => { + let subfunc = x.as_str(); + match subfunc.parse::() { + Ok(num) => { + modbus.subfunction = Some(num); + } + Err(_) => { + SCLogError!("Invalid subfunction value: {}", subfunc); + return Err(()); + } + } + } + None => return Ok(modbus), + } + } + // Non-digit + else { + let neg = word.starts_with('!'); + + let category = match &word[neg as usize..] { + "assigned" => CodeCategory::PUBLIC_ASSIGNED.into(), + "unassigned" => CodeCategory::PUBLIC_UNASSIGNED.into(), + "public" => CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED, + "user" => CodeCategory::USER_DEFINED.into(), + "reserved" => CodeCategory::RESERVED.into(), + "all" => { + CodeCategory::PUBLIC_ASSIGNED + | CodeCategory::PUBLIC_UNASSIGNED + | CodeCategory::USER_DEFINED + | CodeCategory::RESERVED + } + _ => { + SCLogError!("Keyword unknown: {}", word); + return Err(()); + } + }; + + if neg { + modbus.category = Some(!category); + } else { + modbus.category = Some(category); + } + } + } else { + return Err(()); + } + + Ok(modbus) +} + +fn parse_unit_id(unit_str: &str) -> Result { + let re = if let Some(re) = UNIT_RE.captures(unit_str) { + re + } else { + return Err(()); + }; + + // 3: Either function or access string + let mut modbus = if let Some(x) = re.get(3) { + let extra = x.as_str(); + if let Ok(mbus) = parse_function(extra) { + mbus + } else if let Ok(mbus) = parse_access(extra) { + mbus + } else { + SCLogError!("Invalid modbus option: {}", extra); + return Err(()); + } + } else { + Default::default() + }; + + // 1: Unit ID min + if let Some(min) = re.get(1) { + // 2: Unit ID max + let max_str = if let Some(max) = re.get(2) { + max.as_str() + } else { + "" + }; + + modbus.unit_id = Some(parse_range(min.as_str(), max_str)?); + } else { + SCLogError!("Min modbus unit ID not found"); + return Err(()); + } + + Ok(modbus) +} diff --git a/rust/src/modbus/mod.rs b/rust/src/modbus/mod.rs new file mode 100644 index 0000000000..58b9951fe6 --- /dev/null +++ b/rust/src/modbus/mod.rs @@ -0,0 +1,19 @@ +/* Copyright (C) 2021 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. + */ + +pub mod detect; +pub mod modbus; diff --git a/rust/src/modbus/modbus.rs b/rust/src/modbus/modbus.rs new file mode 100644 index 0000000000..f88f5cce0f --- /dev/null +++ b/rust/src/modbus/modbus.rs @@ -0,0 +1,1587 @@ +/* Copyright (C) 2021 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::applayer::*; +use crate::core::{self, AppProto, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP}; + +use std::ffi::CString; +use std::str::FromStr; + +use sawp::error::Error as SawpError; +use sawp::error::ErrorKind as SawpErrorKind; +use sawp::parser::{Direction, Parse}; +use sawp::probe::{Probe, Status}; +use sawp_modbus::{self, AccessType, ErrorFlags, Flags, Message}; + +pub const REQUEST_FLOOD: usize = 500; // Default unreplied Modbus requests are considered a flood +pub const MODBUS_PARSER: sawp_modbus::Modbus = sawp_modbus::Modbus {}; + +static mut ALPROTO_MODBUS: AppProto = ALPROTO_UNKNOWN; + +enum ModbusEvent { + UnsolicitedResponse = 0, + InvalidFunctionCode, + InvalidLength, + InvalidValue, + InvalidExceptionCode, + ValueMismatch, + Flooded, + InvalidProtocolId, +} + +impl FromStr for ModbusEvent { + type Err = (); + fn from_str(name: &str) -> Result { + match name { + "unsolicited_response" => Ok(ModbusEvent::UnsolicitedResponse), + "invalid_function_code" => Ok(ModbusEvent::InvalidFunctionCode), + "invalid_length" => Ok(ModbusEvent::InvalidLength), + "invalid_value" => Ok(ModbusEvent::InvalidValue), + "invalid_exception_code" => Ok(ModbusEvent::InvalidExceptionCode), + "value_mismatch" => Ok(ModbusEvent::ValueMismatch), + "flooded" => Ok(ModbusEvent::Flooded), + "invalid_protocol_id" => Ok(ModbusEvent::InvalidProtocolId), + _ => Err(()), + } + } +} + +impl ModbusEvent { + pub fn to_str(&self) -> &str { + match *self { + ModbusEvent::UnsolicitedResponse => "unsolicited_response", + ModbusEvent::InvalidFunctionCode => "invalid_function_code", + ModbusEvent::InvalidLength => "invalid_length", + ModbusEvent::InvalidValue => "invalid_value", + ModbusEvent::InvalidExceptionCode => "invalid_exception_code", + ModbusEvent::ValueMismatch => "value_mismatch", + ModbusEvent::Flooded => "flooded", + ModbusEvent::InvalidProtocolId => "invalid_protocol_id", + } + } + + pub fn from_id(id: u32) -> Option { + match id { + 0 => Some(ModbusEvent::UnsolicitedResponse), + 1 => Some(ModbusEvent::InvalidFunctionCode), + 2 => Some(ModbusEvent::InvalidLength), + 3 => Some(ModbusEvent::InvalidValue), + 4 => Some(ModbusEvent::InvalidExceptionCode), + 5 => Some(ModbusEvent::ValueMismatch), + 6 => Some(ModbusEvent::Flooded), + 7 => Some(ModbusEvent::InvalidProtocolId), + _ => None, + } + } +} + +pub struct ModbusTransaction { + pub id: u64, + + pub request: Option, + pub response: Option, + + pub events: *mut core::AppLayerDecoderEvents, + pub de_state: Option<*mut core::DetectEngineState>, + pub tx_data: AppLayerTxData, +} + +impl ModbusTransaction { + pub fn new(id: u64) -> Self { + Self { + id, + request: None, + response: None, + events: std::ptr::null_mut(), + de_state: None, + tx_data: AppLayerTxData::new(), + } + } + + fn set_event(&mut self, event: ModbusEvent) { + core::sc_app_layer_decoder_events_set_event_raw(&mut self.events, event as u8); + } + + fn set_events_from_flags(&mut self, flags: &Flags) { + if flags.intersects(ErrorFlags::FUNC_CODE) { + self.set_event(ModbusEvent::InvalidFunctionCode); + } + if flags.intersects(ErrorFlags::DATA_VALUE) { + self.set_event(ModbusEvent::InvalidValue); + } + if flags.intersects(ErrorFlags::DATA_LENGTH) { + self.set_event(ModbusEvent::InvalidLength); + } + if flags.intersects(ErrorFlags::EXC_CODE) { + self.set_event(ModbusEvent::InvalidExceptionCode); + } + if flags.intersects(ErrorFlags::PROTO_ID) { + self.set_event(ModbusEvent::InvalidProtocolId); + } + } +} + +impl Drop for ModbusTransaction { + fn drop(&mut self) { + if !self.events.is_null() { + core::sc_app_layer_decoder_events_free_events(&mut self.events); + } + + if let Some(state) = self.de_state { + core::sc_detect_engine_state_free(state); + } + } +} + +pub struct ModbusState { + pub transactions: Vec, + tx_id: u64, + givenup: bool, // Indicates flood +} + +impl ModbusState { + pub fn new() -> Self { + Self { + transactions: Vec::new(), + tx_id: 0, + givenup: false, + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut ModbusTransaction> { + for tx in &mut self.transactions { + if tx.id == tx_id + 1 { + return Some(tx); + } + } + None + } + + /// Searches the requests in order to find one matching the given response. Returns the matching + /// transaction, if it exists + pub fn find_request_and_validate( + &mut self, resp: &mut Message, + ) -> Option<&mut ModbusTransaction> { + for tx in &mut self.transactions { + if let Some(req) = &tx.request { + if tx.response.is_none() && resp.matches(req) { + return Some(tx); + } + } + } + None + } + + /// Searches the responses in order to find one matching the given request. Returns the matching + /// transaction, if it exists + pub fn find_response_and_validate( + &mut self, req: &mut Message, + ) -> Option<&mut ModbusTransaction> { + for tx in &mut self.transactions { + if let Some(resp) = &tx.response { + if tx.request.is_none() && req.matches(resp) { + return Some(tx); + } + } + } + None + } + + pub fn new_tx(&mut self) -> Option { + // Check flood limit + if self.givenup { + return None; + } + + self.tx_id += 1; + let mut tx = ModbusTransaction::new(self.tx_id); + + if REQUEST_FLOOD != 0 && self.transactions.len() >= REQUEST_FLOOD { + tx.set_event(ModbusEvent::Flooded); + self.givenup = true; + } + + Some(tx) + } + + pub fn free_tx(&mut self, tx_id: u64) { + if let Some(index) = self.transactions.iter().position(|tx| tx.id == tx_id + 1) { + self.transactions.remove(index); + + // Check flood limit + if self.givenup && REQUEST_FLOOD != 0 && self.transactions.len() < REQUEST_FLOOD { + self.givenup = false; + } + } + } + + pub fn parse(&mut self, input: &[u8], direction: Direction) -> AppLayerResult { + let mut rest = input; + while rest.len() > 0 { + match MODBUS_PARSER.parse(rest, direction.clone()) { + Ok((inner_rest, Some(mut msg))) => { + match direction { + Direction::ToServer | Direction::Unknown => { + match self.find_response_and_validate(&mut msg) { + Some(tx) => { + tx.set_events_from_flags(&msg.error_flags); + tx.request = Some(msg); + } + None => { + let mut tx = match self.new_tx() { + Some(tx) => tx, + None => return AppLayerResult::ok(), + }; + tx.set_events_from_flags(&msg.error_flags); + tx.request = Some(msg); + self.transactions.push(tx); + } + } + } + Direction::ToClient => match self.find_request_and_validate(&mut msg) { + Some(tx) => { + if msg + .access_type + .intersects(AccessType::READ | AccessType::WRITE) + && msg.error_flags.intersects( + ErrorFlags::DATA_LENGTH | ErrorFlags::DATA_VALUE, + ) + { + tx.set_event(ModbusEvent::ValueMismatch); + } else { + tx.set_events_from_flags(&msg.error_flags); + } + tx.response = Some(msg); + } + None => { + let mut tx = match self.new_tx() { + Some(tx) => tx, + None => return AppLayerResult::ok(), + }; + if msg + .access_type + .intersects(AccessType::READ | AccessType::WRITE) + && msg.error_flags.intersects( + ErrorFlags::DATA_LENGTH | ErrorFlags::DATA_VALUE, + ) + { + tx.set_event(ModbusEvent::ValueMismatch); + } else { + tx.set_events_from_flags(&msg.error_flags); + } + tx.response = Some(msg); + tx.set_event(ModbusEvent::UnsolicitedResponse); + self.transactions.push(tx); + } + }, + } + + if inner_rest.len() >= rest.len() { + return AppLayerResult::err(); + } + rest = inner_rest; + } + Ok((inner_rest, None)) => { + return AppLayerResult::incomplete( + (input.len() - inner_rest.len()) as u32, + inner_rest.len() as u32 + 1, + ); + } + Err(SawpError { + kind: SawpErrorKind::Incomplete(sawp::error::Needed::Size(needed)), + }) => { + return AppLayerResult::incomplete( + (input.len() - rest.len()) as u32, + (rest.len() + needed.get()) as u32, + ); + } + Err(SawpError { + kind: SawpErrorKind::Incomplete(sawp::error::Needed::Unknown), + }) => { + return AppLayerResult::incomplete( + (input.len() - rest.len()) as u32, + rest.len() as u32 + 1, + ); + } + Err(_) => return AppLayerResult::err(), + } + } + AppLayerResult::ok() + } +} + +/// Probe input to see if it looks like Modbus. +#[no_mangle] +pub extern "C" fn rs_modbus_probe( + _flow: *const core::Flow, _direction: u8, input: *const u8, len: u32, _rdir: *mut u8, +) -> AppProto { + let slice: &[u8] = unsafe { std::slice::from_raw_parts(input as *mut u8, len as usize) }; + match MODBUS_PARSER.probe(slice, Direction::Unknown) { + Status::Recognized => unsafe { ALPROTO_MODBUS }, + Status::Incomplete => ALPROTO_UNKNOWN, + Status::Unrecognized => unsafe { ALPROTO_FAILED }, + } +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + Box::into_raw(Box::new(ModbusState::new())) as *mut std::os::raw::c_void +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_free(state: *mut std::os::raw::c_void) { + let _state: Box = unsafe { Box::from_raw(state as *mut ModbusState) }; +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, ModbusState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_modbus_parse_request( + _flow: *const core::Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, + input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, +) -> AppLayerResult { + if input_len == 0 { + if unsafe { AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) } > 0 { + return AppLayerResult::ok(); + } else { + return AppLayerResult::err(); + } + } + + let state = cast_pointer!(state, ModbusState); + let buf = unsafe { std::slice::from_raw_parts(input, input_len as usize) }; + + state.parse(buf, Direction::ToServer) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_parse_response( + _flow: *const core::Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, + input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, +) -> AppLayerResult { + if input_len == 0 { + unsafe { + if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0 { + return AppLayerResult::ok(); + } else { + return AppLayerResult::err(); + } + } + } + + let state = cast_pointer!(state, ModbusState); + let buf = unsafe { std::slice::from_raw_parts(input, input_len as usize) }; + + state.parse(buf, Direction::ToClient) +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, ModbusState); + state.tx_id +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, ModbusState); + match state.get_tx(tx_id) { + Some(tx) => (tx as *mut ModbusTransaction) as *mut std::os::raw::c_void, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" fn rs_modbus_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, ModbusTransaction); + tx.response.is_some() as std::os::raw::c_int +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_get_events( + tx: *mut std::os::raw::c_void, +) -> *mut core::AppLayerDecoderEvents { + let tx = cast_pointer!(tx, ModbusTransaction); + tx.events +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_get_event_info( + event_name: *const std::os::raw::c_char, event_id: *mut std::os::raw::c_int, + event_type: *mut core::AppLayerEventType, +) -> std::os::raw::c_int { + if event_name.is_null() { + return -1; + } + + let event_name = unsafe { std::ffi::CStr::from_ptr(event_name) }; + if let Ok(event_name) = event_name.to_str() { + match ModbusEvent::from_str(event_name) { + Ok(event) => unsafe { + *event_id = event as std::os::raw::c_int; + *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION; + 0 + }, + Err(_) => { + SCLogError!( + "event {} not present in modbus's enum map table.", + event_name + ); + -1 + } + } + } else { + -1 + } +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_get_event_info_by_id( + event_id: std::os::raw::c_int, event_name: *mut *const std::os::raw::c_char, + event_type: *mut core::AppLayerEventType, +) -> i8 { + if let Some(e) = ModbusEvent::from_id(event_id as u32) { + unsafe { + *event_name = e.to_str().as_ptr() as *const std::os::raw::c_char; + *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION; + } + 0 + } else { + SCLogError!("event {} not present in modbus's enum map table.", event_id); + -1 + } +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_get_tx_detect_state( + tx: *mut std::os::raw::c_void, +) -> *mut core::DetectEngineState { + let tx = cast_pointer!(tx, ModbusTransaction); + match tx.de_state { + Some(ds) => ds, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_set_tx_detect_state( + tx: *mut std::os::raw::c_void, de_state: &mut core::DetectEngineState, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, ModbusTransaction); + tx.de_state = Some(de_state); + 0 +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_get_tx_data( + tx: *mut std::os::raw::c_void, +) -> *mut AppLayerTxData { + let tx = cast_pointer!(tx, ModbusTransaction); + &mut tx.tx_data +} + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_register_parser() { + let default_port = std::ffi::CString::new("[502]").unwrap(); + let parser = RustParser { + name: b"modbus\0".as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: Some(rs_modbus_probe), + probe_tc: Some(rs_modbus_probe), + min_depth: 0, + max_depth: 16, + state_new: rs_modbus_state_new, + state_free: rs_modbus_state_free, + tx_free: rs_modbus_state_tx_free, + parse_ts: rs_modbus_parse_request, + parse_tc: rs_modbus_parse_response, + get_tx_count: rs_modbus_state_get_tx_count, + get_tx: rs_modbus_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_modbus_tx_get_alstate_progress, + get_events: Some(rs_modbus_state_get_events), + get_eventinfo: Some(rs_modbus_state_get_event_info), + get_eventinfo_byid: Some(rs_modbus_state_get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_files: None, + get_tx_iterator: None, + get_de_state: rs_modbus_state_get_tx_detect_state, + set_de_state: rs_modbus_state_set_tx_detect_state, + get_tx_data: rs_modbus_state_get_tx_data, + apply_tx_config: None, + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: None, + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_MODBUS = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } +} + +// This struct and accessor functions are used for app-layer-modbus.c tests. +pub mod test { + use super::ModbusState; + use sawp_modbus::{Data, Message, Read, Write}; + use std::ffi::c_void; + #[repr(C)] + pub struct ModbusMessage(*const c_void); + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_function(msg: *const ModbusMessage) -> u8 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + msg.function.raw + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_subfunction(msg: *const ModbusMessage) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Diagnostic { func, data: _ } = &msg.data { + func.raw + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_read_request_address( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Read(Read::Request { + address, + quantity: _, + }) = &msg.data + { + *address + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_read_request_quantity( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Read(Read::Request { + address: _, + quantity, + }) = &msg.data + { + *quantity + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_read_address( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ReadWrite { + read: + Read::Request { + address, + quantity: _, + }, + write: _, + } = &msg.data + { + *address + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_read_quantity( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ReadWrite { + read: + Read::Request { + address: _, + quantity, + }, + write: _, + } = &msg.data + { + *quantity + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_address( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ReadWrite { + read: _, + write: + Write::MultReq { + address, + quantity: _, + data: _, + }, + } = &msg.data + { + *address + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_quantity( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ReadWrite { + read: _, + write: + Write::MultReq { + address: _, + quantity, + data: _, + }, + } = &msg.data + { + *quantity + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_data( + msg: *const ModbusMessage, data_len: *mut usize, + ) -> *const u8 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ReadWrite { + read: _, + write: + Write::MultReq { + address: _, + quantity: _, + data, + }, + } = &msg.data + { + *data_len = data.len(); + data.as_slice().as_ptr() + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_address( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::MultReq { + address, + quantity: _, + data: _, + }) = &msg.data + { + *address + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_quantity( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::MultReq { + address: _, + quantity, + data: _, + }) = &msg.data + { + *quantity + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_data( + msg: *const ModbusMessage, data_len: *mut usize, + ) -> *const u8 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::MultReq { + address: _, + quantity: _, + data, + }) = &msg.data + { + *data_len = data.len(); + data.as_slice().as_ptr() + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_and_mask(msg: *const ModbusMessage) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::Mask { + address: _, + and_mask, + or_mask: _, + }) = &msg.data + { + *and_mask + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_or_mask(msg: *const ModbusMessage) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::Mask { + address: _, + and_mask: _, + or_mask, + }) = &msg.data + { + *or_mask + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_write_address(msg: *const ModbusMessage) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::Other { address, data: _ }) = &msg.data { + *address + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_write_data(msg: *const ModbusMessage) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::Other { address: _, data }) = &msg.data { + *data + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_bytevec_data( + msg: *const ModbusMessage, data_len: *mut usize, + ) -> *const u8 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ByteVec(data) = &msg.data { + *data_len = data.len(); + data.as_slice().as_ptr() + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub extern "C" fn rs_modbus_state_get_tx_request( + state: *mut std::os::raw::c_void, tx_id: u64, + ) -> ModbusMessage { + let state = cast_pointer!(state, ModbusState); + if let Some(tx) = state.get_tx(tx_id) { + if let Some(request) = &tx.request { + ModbusMessage((request as *const Message) as *const c_void) + } else { + ModbusMessage(std::ptr::null()) + } + } else { + ModbusMessage(std::ptr::null()) + } + } + + #[no_mangle] + pub extern "C" fn rs_modbus_state_get_tx_response( + state: *mut std::os::raw::c_void, tx_id: u64, + ) -> ModbusMessage { + let state = cast_pointer!(state, ModbusState); + if let Some(tx) = state.get_tx(tx_id) { + if let Some(response) = &tx.response { + ModbusMessage((response as *const Message) as *const c_void) + } else { + ModbusMessage(std::ptr::null()) + } + } else { + ModbusMessage(std::ptr::null()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sawp_modbus::{ + Data, Diagnostic, DiagnosticSubfunction, Exception, ExceptionCode, FunctionCode, Read, + Write, + }; + + const INVALID_FUNC_CODE: &[u8] = &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x02, // Length + 0x00, // Unit ID + 0x00, // Function code + ]; + + const RD_COILS_REQ: &[u8] = &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x01, // Function code + 0x78, 0x90, // Starting Address + 0x00, 0x13, // Quantity of coils + ]; + + const RD_COILS_RESP: &[u8] = &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x01, // Function code + 0x03, // Byte count + 0xCD, 0x6B, 0x05, // Coil Status + ]; + + const RD_COILS_ERR_RESP: &[u8] = &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x03, // Length + 0x00, // Unit ID + 0x81, // Function code + 0xFF, // Exception code + ]; + + const WR_SINGLE_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x06, // Function code + 0x00, 0x01, // Register Address + 0x00, 0x03, // Register Value + ]; + + const INVALID_WR_SINGLE_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x04, // Length + 0x00, // Unit ID + 0x06, // Function code + 0x00, 0x01, // Register Address + ]; + + const WR_SINGLE_REG_RESP: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x06, // Function code + 0x00, 0x01, // Register Address + 0x00, 0x03, // Register Value + ]; + + const WR_MULT_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x0B, // Length + 0x00, // Unit ID + 0x10, // Function code + 0x00, 0x01, // Starting Address + 0x00, 0x02, // Quantity of Registers + 0x04, // Byte count + 0x00, 0x0A, // Registers Value + 0x01, 0x02, + ]; + + const INVALID_PDU_WR_MULT_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x02, // Length + 0x00, // Unit ID + 0x10, // Function code + ]; + + const WR_MULT_REG_RESP: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x10, // Function code + 0x00, 0x01, // Starting Address + 0x00, 0x02, // Quantity of Registers + ]; + + const MASK_WR_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x08, // Length + 0x00, // Unit ID + 0x16, // Function code + 0x00, 0x04, // Reference Address + 0x00, 0xF2, // And_Mask + 0x00, 0x25, // Or_Mask + ]; + + const INVALID_MASK_WR_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x16, // Function code + 0x00, 0x04, // Reference Address + 0x00, 0xF2, // And_Mask + ]; + + const MASK_WR_REG_RESP: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x08, // Length + 0x00, // Unit ID + 0x16, // Function code + 0x00, 0x04, // Reference Address + 0x00, 0xF2, // And_Mask + 0x00, 0x25, // Or_Mask + ]; + + const RD_WR_MULT_REG_REQ: &[u8] = &[ + 0x12, 0x34, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x11, // Length + 0x00, // Unit ID + 0x17, // Function code + 0x00, 0x03, // Read Starting Address + 0x00, 0x06, // Quantity to Read + 0x00, 0x0E, // Write Starting Address + 0x00, 0x03, // Quantity to Write + 0x06, // Write Byte count + 0x12, 0x34, // Write Registers Value + 0x56, 0x78, 0x9A, 0xBC, + ]; + + // Mismatch value in Byte count 0x0B instead of 0x0C + const RD_WR_MULT_REG_RESP: &[u8] = &[ + 0x12, 0x34, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x0E, // Length + 0x00, // Unit ID + 0x17, // Function code + 0x0B, // Byte count + 0x00, 0xFE, // Read Registers Value + 0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00, + ]; + + const FORCE_LISTEN_ONLY_MODE: &[u8] = &[ + 0x0A, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x08, // Function code + 0x00, 0x04, // Sub-function code + 0x00, 0x00, // Data + ]; + + const INVALID_PROTO_REQ: &[u8] = &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x01, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x01, // Function code + 0x78, 0x90, // Starting Address + 0x00, 0x13, // Quantity of coils + ]; + + const INVALID_LEN_WR_MULT_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x09, // Length + 0x00, // Unit ID + 0x10, // Function code + 0x00, 0x01, // Starting Address + 0x00, 0x02, // Quantity of Registers + 0x04, // Byte count + 0x00, 0x0A, // Registers Value + 0x01, 0x02, + ]; + + const EXCEEDED_LEN_WR_MULT_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0xff, 0xfa, // Length + 0x00, // Unit ID + 0x10, // Function code + 0x00, 0x01, // Starting Address + 0x7f, 0xf9, // Quantity of Registers + 0xff, // Byte count + ]; + + #[test] + fn read_coils() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&RD_COILS_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!( + msg.data, + Data::Read(Read::Request { + address: 0x7890, + quantity: 0x0013 + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(&RD_COILS_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05]))); + } + + #[test] + fn write_multiple_registers() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&WR_MULT_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!( + msg.data, + Data::Write(Write::MultReq { + address: 0x0001, + quantity: 0x0002, + data: vec![0x00, 0x0a, 0x01, 0x02], + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(&WR_MULT_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!( + msg.data, + Data::Write(Write::Other { + address: 0x0001, + data: 0x0002 + }) + ); + } + + #[test] + fn read_write_multiple_registers() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&RD_WR_MULT_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdWrMultRegs); + assert_eq!( + msg.data, + Data::ReadWrite { + read: Read::Request { + address: 0x0003, + quantity: 0x0006, + }, + write: Write::MultReq { + address: 0x000e, + quantity: 0x0003, + data: vec![0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc] + } + } + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(&RD_WR_MULT_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdWrMultRegs); + assert_eq!( + msg.data, + Data::Read(Read::Response(vec![ + 0x00, 0xFE, 0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00, + ])) + ); + } + + #[test] + fn force_listen_only_mode() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&FORCE_LISTEN_ONLY_MODE, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::Diagnostic); + assert_eq!( + msg.data, + Data::Diagnostic { + func: Diagnostic { + raw: 4, + code: DiagnosticSubfunction::ForceListenOnlyMode + }, + data: vec![0x00, 0x00] + } + ); + } + + #[test] + fn invalid_protocol_version() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&INVALID_PROTO_REQ, Direction::ToServer) + ); + + assert_eq!(state.transactions.len(), 1); + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.error_flags, ErrorFlags::PROTO_ID); + } + + #[test] + fn unsolicited_response() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&RD_COILS_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05]))); + } + + #[test] + fn invalid_length_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::incomplete(15, 4), + state.parse(&INVALID_LEN_WR_MULT_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!( + msg.data, + Data::Write(Write::MultReq { + address: 0x0001, + quantity: 0x0002, + data: vec![0x00, 0x0a] + }) + ); + assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); + } + + #[test] + fn exception_code_invalid() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&RD_COILS_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!( + msg.data, + Data::Read(Read::Request { + address: 0x7890, + quantity: 0x0013 + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(&RD_COILS_ERR_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!( + msg.data, + Data::Exception(Exception { + raw: 255, + code: ExceptionCode::Unknown + }) + ); + assert_eq!(msg.error_flags, ErrorFlags::EXC_CODE); + } + + #[test] + fn fragmentation_1_adu_in_2_tcp_packets() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::incomplete(0, 15), + state.parse( + &RD_COILS_REQ[0..(RD_COILS_REQ.len() - 3)], + Direction::ToServer + ) + ); + assert_eq!(state.transactions.len(), 0); + assert_eq!( + AppLayerResult::ok(), + state.parse(&RD_COILS_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + assert!(&tx.request.is_some()); + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!( + msg.data, + Data::Read(Read::Request { + address: 0x7890, + quantity: 0x0013 + }) + ); + } + + #[test] + fn fragmentation_2_adu_in_1_tcp_packet() { + let req = [RD_COILS_REQ, WR_MULT_REG_REQ].concat(); + let resp = [RD_COILS_RESP, WR_MULT_REG_RESP].concat(); + + let mut state = ModbusState::new(); + assert_eq!(AppLayerResult::ok(), state.parse(&req, Direction::ToServer)); + assert_eq!(state.transactions.len(), 2); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!( + msg.data, + Data::Read(Read::Request { + address: 0x7890, + quantity: 0x0013 + }) + ); + + let tx = &state.transactions[1]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!( + msg.data, + Data::Write(Write::MultReq { + address: 0x0001, + quantity: 0x0002, + data: vec![0x00, 0x0a, 0x01, 0x02] + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(&resp, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 2); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05]))); + + let tx = &state.transactions[1]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!( + msg.data, + Data::Write(Write::Other { + address: 0x0001, + data: 0x0002 + }) + ); + } + + #[test] + fn exceeded_length_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&EXCEEDED_LEN_WR_MULT_REG_REQ, Direction::ToServer) + ); + + assert_eq!(state.transactions.len(), 1); + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); + } + + #[test] + fn invalid_pdu_len_req() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&INVALID_PDU_WR_MULT_REG_REQ, Direction::ToServer) + ); + + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!(msg.data, Data::ByteVec(vec![])); + } + + #[test] + fn mask_write_register_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&MASK_WR_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::MaskWrReg); + assert_eq!( + msg.data, + Data::Write(Write::Mask { + address: 0x0004, + and_mask: 0x00f2, + or_mask: 0x0025 + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(&MASK_WR_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::MaskWrReg); + assert_eq!( + msg.data, + Data::Write(Write::Mask { + address: 0x0004, + and_mask: 0x00f2, + or_mask: 0x0025 + }) + ); + } + + #[test] + fn write_single_register_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&WR_SINGLE_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrSingleReg); + assert_eq!( + msg.data, + Data::Write(Write::Other { + address: 0x0001, + data: 0x0003 + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(&WR_SINGLE_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrSingleReg); + assert_eq!( + msg.data, + Data::Write(Write::Other { + address: 0x0001, + data: 0x0003 + }) + ); + } + + #[test] + fn invalid_mask_write_register_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&INVALID_MASK_WR_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::MaskWrReg); + assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); + assert_eq!(msg.data, Data::ByteVec(vec![0x00, 0x04, 0x00, 0xF2])); + + assert_eq!( + AppLayerResult::ok(), + state.parse(&MASK_WR_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::MaskWrReg); + assert_eq!( + msg.data, + Data::Write(Write::Mask { + address: 0x0004, + and_mask: 0x00f2, + or_mask: 0x0025 + }) + ); + } + + #[test] + fn invalid_write_single_register_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&INVALID_WR_SINGLE_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrSingleReg); + assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); + assert_eq!(msg.data, Data::ByteVec(vec![0x00, 0x01])); + + assert_eq!( + AppLayerResult::ok(), + state.parse(&WR_SINGLE_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrSingleReg); + assert_eq!( + msg.data, + Data::Write(Write::Other { + address: 0x0001, + data: 0x0003 + }) + ); + } + + #[test] + fn invalid_function_code() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(&INVALID_FUNC_CODE, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::Unknown); + assert_eq!(msg.data, Data::ByteVec(vec![])); + } +} diff --git a/src/app-layer-modbus.c b/src/app-layer-modbus.c index 43da199a46..415e950e38 100644 --- a/src/app-layer-modbus.c +++ b/src/app-layer-modbus.c @@ -37,1504 +37,18 @@ #include "suricata-common.h" #include "util-debug.h" -#include "util-byte.h" -#include "util-enum.h" -#include "util-mem.h" -#include "util-misc.h" -#include "stream.h" -#include "stream-tcp.h" - -#include "app-layer-protos.h" #include "app-layer-parser.h" #include "app-layer-modbus.h" -#include "app-layer-detect-proto.h" - -#include "conf.h" -#include "conf-yaml-loader.h" -#include "decode.h" - -SCEnumCharMap modbus_decoder_event_table[ ] = { - /* Modbus Application Data Unit messages - ADU Modbus */ - { "INVALID_PROTOCOL_ID", MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID }, - { "UNSOLICITED_RESPONSE", MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE }, - { "INVALID_LENGTH", MODBUS_DECODER_EVENT_INVALID_LENGTH }, - { "INVALID_UNIT_IDENTIFIER", MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER}, - - /* Modbus Protocol Data Unit messages - PDU Modbus */ - { "INVALID_FUNCTION_CODE", MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE }, - { "INVALID_VALUE", MODBUS_DECODER_EVENT_INVALID_VALUE }, - { "INVALID_EXCEPTION_CODE", MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE }, - { "VALUE_MISMATCH", MODBUS_DECODER_EVENT_VALUE_MISMATCH }, - - /* Modbus Decoder event */ - { "FLOODED", MODBUS_DECODER_EVENT_FLOODED}, - { NULL, -1 }, -}; - -/* Modbus Application Data Unit (ADU) length range. */ -#define MODBUS_MIN_ADU_LEN 2 -#define MODBUS_MAX_ADU_LEN 254 - -/* Modbus Protocol version. */ -#define MODBUS_PROTOCOL_VER 0 - -/* Modbus Unit Identifier range. */ -#define MODBUS_MIN_INVALID_UNIT_ID 247 -#define MODBUS_MAX_INVALID_UNIT_ID 255 - -/* Modbus Quantity range. */ -#define MODBUS_MIN_QUANTITY 0 -#define MODBUS_MAX_QUANTITY_IN_BIT_ACCESS 2000 -#define MODBUS_MAX_QUANTITY_IN_WORD_ACCESS 125 - -/* Modbus Count range. */ -#define MODBUS_MIN_COUNT 1 -#define MODBUS_MAX_COUNT 250 - -/* Modbus Function Code. */ -#define MODBUS_FUNC_READCOILS 0x01 -#define MODBUS_FUNC_READDISCINPUTS 0x02 -#define MODBUS_FUNC_READHOLDREGS 0x03 -#define MODBUS_FUNC_READINPUTREGS 0x04 -#define MODBUS_FUNC_WRITESINGLECOIL 0x05 -#define MODBUS_FUNC_WRITESINGLEREG 0x06 -#define MODBUS_FUNC_READEXCSTATUS 0x07 -#define MODBUS_FUNC_DIAGNOSTIC 0x08 -#define MODBUS_FUNC_GETCOMEVTCOUNTER 0x0b -#define MODBUS_FUNC_GETCOMEVTLOG 0x0c -#define MODBUS_FUNC_WRITEMULTCOILS 0x0f -#define MODBUS_FUNC_WRITEMULTREGS 0x10 -#define MODBUS_FUNC_REPORTSERVERID 0x11 -#define MODBUS_FUNC_READFILERECORD 0x14 -#define MODBUS_FUNC_WRITEFILERECORD 0x15 -#define MODBUS_FUNC_MASKWRITEREG 0x16 -#define MODBUS_FUNC_READWRITEMULTREGS 0x17 -#define MODBUS_FUNC_READFIFOQUEUE 0x18 -#define MODBUS_FUNC_ENCAPINTTRANS 0x2b -#define MODBUS_FUNC_MASK 0x7f -#define MODBUS_FUNC_ERRORMASK 0x80 - -/* Modbus Diagnostic functions: Subfunction Code. */ -#define MODBUS_SUBFUNC_QUERY_DATA 0x00 -#define MODBUS_SUBFUNC_RESTART_COM 0x01 -#define MODBUS_SUBFUNC_DIAG_REGS 0x02 -#define MODBUS_SUBFUNC_CHANGE_DELIMITER 0x03 -#define MODBUS_SUBFUNC_LISTEN_MODE 0x04 -#define MODBUS_SUBFUNC_CLEAR_REGS 0x0a -#define MODBUS_SUBFUNC_BUS_MSG_COUNT 0x0b -#define MODBUS_SUBFUNC_COM_ERR_COUNT 0x0c -#define MODBUS_SUBFUNC_EXCEPT_ERR_COUNT 0x0d -#define MODBUS_SUBFUNC_SERVER_MSG_COUNT 0x0e -#define MODBUS_SUBFUNC_SERVER_NO_RSP_COUNT 0x0f -#define MODBUS_SUBFUNC_SERVER_NAK_COUNT 0x10 -#define MODBUS_SUBFUNC_SERVER_BUSY_COUNT 0x11 -#define MODBUS_SUBFUNC_SERVER_CHAR_COUNT 0x12 -#define MODBUS_SUBFUNC_CLEAR_COUNT 0x14 - -/* Modbus Encapsulated Interface Transport function: MEI type. */ -#define MODBUS_MEI_ENCAPINTTRANS_CAN 0x0d -#define MODBUS_MEI_ENCAPINTTRANS_READ 0x0e - -/* Modbus Exception Codes. */ -#define MODBUS_ERROR_CODE_ILLEGAL_FUNCTION 0x01 -#define MODBUS_ERROR_CODE_ILLEGAL_DATA_ADDRESS 0x02 -#define MODBUS_ERROR_CODE_ILLEGAL_DATA_VALUE 0x03 -#define MODBUS_ERROR_CODE_SERVER_DEVICE_FAILURE 0x04 -#define MODBUS_ERROR_CODE_MEMORY_PARITY_ERROR 0x08 - -/* Modbus Application Protocol (MBAP) header. */ -struct ModbusHeader_ { - uint16_t transactionId; - uint16_t protocolId; - uint16_t length; - uint8_t unitId; -} __attribute__((__packed__)); -typedef struct ModbusHeader_ ModbusHeader; - -/* Modbus Read/Write function and Access Types. */ -#define MODBUS_TYP_WRITE_SINGLE (MODBUS_TYP_WRITE | MODBUS_TYP_SINGLE) -#define MODBUS_TYP_WRITE_MULTIPLE (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE) -#define MODBUS_TYP_READ_WRITE_MULTIPLE (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE) - -/* Macro to convert quantity value (in bit) into count value (in word): count = Ceil(quantity/8) */ -#define CEIL(quantity) (((quantity) + 7)>>3) - -/* Modbus Default unreplied Modbus requests are considered a flood */ -#define MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD 500 - -/* Modbus default stream reassembly depth */ -#define MODBUS_CONFIG_DEFAULT_STREAM_DEPTH 0 - -static uint32_t request_flood = MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD; -static uint32_t stream_depth = MODBUS_CONFIG_DEFAULT_STREAM_DEPTH; - -static int ModbusStateGetEventInfo(const char *event_name, int *event_id, AppLayerEventType *event_type) -{ - *event_id = SCMapEnumNameToValue(event_name, modbus_decoder_event_table); - - if (*event_id == -1) { - SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%s\" not present in " - "modbus's enum map table.", event_name); - /* yes this is fatal */ - return -1; - } - - *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; - - return 0; -} - -static int ModbusStateGetEventInfoById(int event_id, const char **event_name, - AppLayerEventType *event_type) -{ - *event_name = SCMapEnumValueToName(event_id, modbus_decoder_event_table); - - if (*event_name == NULL) { - SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%d\" not present in " - "modbus's enum map table.", event_id); - /* yes this is fatal */ - return -1; - } - - *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; - - return 0; -} - -static void ModbusSetEvent(ModbusState *modbus, uint8_t e) -{ - if (modbus && modbus->curr) { - SCLogDebug("modbus->curr->decoder_events %p", modbus->curr->decoder_events); - AppLayerDecoderEventsSetEventRaw(&modbus->curr->decoder_events, e); - SCLogDebug("modbus->curr->decoder_events %p", modbus->curr->decoder_events); - modbus->events++; - } else - SCLogDebug("couldn't set event %u", e); -} - -static AppLayerDecoderEvents *ModbusGetEvents(void *tx) -{ - return ((ModbusTransaction *)tx)->decoder_events; -} - -static int ModbusGetAlstateProgress(void *modbus_tx, uint8_t direction) -{ - ModbusTransaction *tx = (ModbusTransaction *) modbus_tx; - ModbusState *modbus = tx->modbus; - - if (tx->replied == 1) - return 1; - - /* Check flood limit */ - if ((modbus->givenup == 1) && - ((modbus->transaction_max - tx->tx_num) > request_flood)) - return 1; - - return 0; -} - -static void *ModbusGetTx(void *alstate, uint64_t tx_id) -{ - ModbusState *modbus = (ModbusState *) alstate; - ModbusTransaction *tx = NULL; - - if (modbus->curr && modbus->curr->tx_num == tx_id + 1) - return modbus->curr; - - TAILQ_FOREACH(tx, &modbus->tx_list, next) { - SCLogDebug("tx->tx_num %"PRIu64", tx_id %"PRIu64, tx->tx_num, (tx_id+1)); - if (tx->tx_num != (tx_id+1)) - continue; - - SCLogDebug("returning tx %p", tx); - return tx; - } - - return NULL; -} - -static AppLayerTxData *ModbusGetTxData(void *vtx) -{ - ModbusTransaction *tx = (ModbusTransaction *)vtx; - return &tx->tx_data; -} - -static uint64_t ModbusGetTxCnt(void *alstate) -{ - return ((uint64_t) ((ModbusState *) alstate)->transaction_max); -} - -/** \internal - * \brief Find the Modbus Transaction in the state based on Transaction ID. - * - * \param modbus Pointer to Modbus state structure - * \param transactionId Transaction ID of the transaction - * - * \retval tx or NULL if not found - */ -static ModbusTransaction *ModbusTxFindByTransaction(const ModbusState *modbus, - const uint16_t transactionId) -{ - ModbusTransaction *tx = NULL; - - if (modbus->curr == NULL) - return NULL; - - /* fast path */ - if ((modbus->curr->transactionId == transactionId) && - !(modbus->curr->replied)) { - return modbus->curr; - /* slow path, iterate list */ - } else { - TAILQ_FOREACH(tx, &modbus->tx_list, next) { - if ((tx->transactionId == transactionId) && - !(modbus->curr->replied)) - return tx; - } - } - /* not found */ - return NULL; -} - -/** \internal - * \brief Allocate a Modbus Transaction and - * add it into Transaction list of Modbus State - * - * \param modbus Pointer to Modbus state structure - * - * \retval Pointer to Transaction or NULL pointer - */ -static ModbusTransaction *ModbusTxAlloc(ModbusState *modbus) { - ModbusTransaction *tx; - - /* Check flood limit */ - if ((request_flood != 0) && (modbus->unreplied_cnt >= request_flood)) { - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_FLOODED); - modbus->givenup = 1; - return NULL; - } - - tx = (ModbusTransaction *) SCCalloc(1, sizeof(ModbusTransaction)); - if (unlikely(tx == NULL)) - return NULL; - - modbus->transaction_max++; - modbus->unreplied_cnt++; - - modbus->curr = tx; - - SCLogDebug("modbus->transaction_max updated to %"PRIu64, modbus->transaction_max); - - TAILQ_INSERT_TAIL(&modbus->tx_list, tx, next); - - tx->modbus = modbus; - tx->tx_num = modbus->transaction_max; - - return tx; -} - -/** \internal - * \brief Free a Modbus Transaction - * - * \retval Pointer to Transaction or NULL pointer - */ -static void ModbusTxFree(ModbusTransaction *tx) { - SCEnter(); - if (tx->data != NULL) - SCFree(tx->data); - - AppLayerDecoderEventsFreeEvents(&tx->decoder_events); - - if (tx->de_state != NULL) - DetectEngineStateFree(tx->de_state); - - SCFree(tx); - SCReturn; -} +void ModbusParserRegisterTests(void); /** - * \brief Modbus transaction cleanup callback - */ -static void ModbusStateTxFree(void *state, uint64_t tx_id) -{ - SCEnter(); - ModbusState *modbus = (ModbusState *) state; - ModbusTransaction *tx = NULL, *ttx; - - SCLogDebug("state %p, id %"PRIu64, modbus, tx_id); - - TAILQ_FOREACH_SAFE(tx, &modbus->tx_list, next, ttx) { - SCLogDebug("tx %p tx->tx_num %"PRIu64", tx_id %"PRIu64, tx, tx->tx_num, (tx_id+1)); - - if (tx->tx_num != (tx_id+1)) - continue; - - if (tx == modbus->curr) - modbus->curr = NULL; - - if (tx->decoder_events != NULL) { - if (tx->decoder_events->cnt <= modbus->events) - modbus->events -= tx->decoder_events->cnt; - else - modbus->events = 0; - } - - modbus->unreplied_cnt--; - - /* Check flood limit */ - if ((modbus->givenup == 1) && - (request_flood != 0) && - (modbus->unreplied_cnt < request_flood) ) - modbus->givenup = 0; - - TAILQ_REMOVE(&modbus->tx_list, tx, next); - ModbusTxFree(tx); - break; - } - SCReturn; -} - -/** \internal - * \brief Extract 8bits data from pointer the received input data - * - * \param res Pointer to the result - * \param input Pointer the received input data - * \param input_len Length of the received input data - * \param offset Offset of the received input data pointer - */ -static int ModbusExtractUint8(ModbusState *modbus, - uint8_t *res, - const uint8_t *input, - uint32_t input_len, - uint16_t *offset) { - SCEnter(); - if (input_len < (uint32_t) (*offset + sizeof(uint8_t))) { - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH); - SCReturnInt(-1); - } - - *res = *(input + *offset); - *offset += sizeof(uint8_t); - SCReturnInt(0); -} - -/** \internal - * \brief Extract 16bits data from pointer the received input data - * - * \param res Pointer to the result - * \param input Pointer the received input data - * \param input_len Length of the received input data - * \param offset Offset of the received input data pointer - */ -static int ModbusExtractUint16(ModbusState *modbus, - uint16_t *res, - const uint8_t *input, - uint32_t input_len, - uint16_t *offset) { - SCEnter(); - if (input_len < (uint32_t) (*offset + sizeof(uint16_t))) { - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH); - SCReturnInt(-1); - } - - ByteExtractUint16(res, BYTE_BIG_ENDIAN, sizeof(uint16_t), (const uint8_t *) (input + *offset)); - *offset += sizeof(uint16_t); - SCReturnInt(0); -} - -/** \internal - * \brief Check length field in Modbus header according to code function - * - * \param modbus Pointer to Modbus state structure - * \param length Length field in Modbus Header - * \param len Length according to code functio - */ -static int ModbusCheckHeaderLength(ModbusState *modbus, - uint16_t length, - uint16_t len) { - SCEnter(); - if (length != len) { - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH); - SCReturnInt(-1); - } - SCReturnInt(0); -} - -/** \internal - * \brief Check Modbus header - * - * \param tx Pointer to Modbus Transaction structure - * \param modbus Pointer to Modbus state structure - * \param header Pointer to Modbus header state in which the value to be stored - */ -static void ModbusCheckHeader(ModbusState *modbus, - ModbusHeader *header) -{ - SCEnter(); - /* MODBUS protocol is identified by the value 0. */ - if (header->protocolId != MODBUS_PROTOCOL_VER) - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID); - - /* Check Length field that is a byte count of the following fields */ - if ((header->length < MODBUS_MIN_ADU_LEN) || - (header->length > MODBUS_MAX_ADU_LEN) ) - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH); - - /* Check Unit Identifier field that is not in invalid range */ - if ((header->unitId > MODBUS_MIN_INVALID_UNIT_ID) && - (header->unitId < MODBUS_MAX_INVALID_UNIT_ID) ) - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER); - - SCReturn; -} - -/** \internal - * \brief Parse Exception Response and verify protocol compliance. - * - * \param tx Pointer to Modbus Transaction structure - * \param modbus Pointer to Modbus state structure - * \param input Pointer the received input data - * \param input_len Length of the received input data - * \param offset Offset of the received input data pointer - */ -static void ModbusExceptionResponse(ModbusTransaction *tx, - ModbusState *modbus, - const uint8_t *input, - uint32_t input_len, - uint16_t *offset) -{ - SCEnter(); - uint8_t exception = 0; - - /* Exception code (1 byte) */ - if (ModbusExtractUint8(modbus, &exception, input, input_len, offset)) - SCReturn; - - switch (exception) { - case MODBUS_ERROR_CODE_ILLEGAL_FUNCTION: - case MODBUS_ERROR_CODE_SERVER_DEVICE_FAILURE: - break; - case MODBUS_ERROR_CODE_ILLEGAL_DATA_VALUE: - if (tx->function == MODBUS_FUNC_DIAGNOSTIC) { - break; - } - /* Fallthrough */ - case MODBUS_ERROR_CODE_ILLEGAL_DATA_ADDRESS: - if ( (tx->type & MODBUS_TYP_ACCESS_FUNCTION_MASK) || - (tx->function == MODBUS_FUNC_READFIFOQUEUE) || - (tx->function == MODBUS_FUNC_ENCAPINTTRANS)) { - break; - } - /* Fallthrough */ - case MODBUS_ERROR_CODE_MEMORY_PARITY_ERROR: - if ( (tx->function == MODBUS_FUNC_READFILERECORD) || - (tx->function == MODBUS_FUNC_WRITEFILERECORD) ) { - break; - } - /* Fallthrough */ - default: - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE); - break; - } - - SCReturn; -} - -/** \internal - * \brief Parse Read data Request, complete Transaction structure - * and verify protocol compliance. - * - * \param tx Pointer to Modbus Transaction structure - * \param modbus Pointer to Modbus state structure - * \param input Pointer the received input data - * \param input_len Length of the received input data - * \param offset Offset of the received input data pointer - */ -static void ModbusParseReadRequest(ModbusTransaction *tx, - ModbusState *modbus, - const uint8_t *input, - uint32_t input_len, - uint16_t *offset) -{ - SCEnter(); - uint16_t quantity; - uint8_t type = tx->type; - - /* Starting Address (2 bytes) */ - if (ModbusExtractUint16(modbus, &(tx->read.address), input, input_len, offset)) - goto end; - - /* Quantity (2 bytes) */ - if (ModbusExtractUint16(modbus, &(tx->read.quantity), input, input_len, offset)) - goto end; - quantity = tx->read.quantity; - - /* Check Quantity range */ - if (type & MODBUS_TYP_BIT_ACCESS_MASK) { - if ((quantity == MODBUS_MIN_QUANTITY) || - (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS)) - goto error; - } else { - if ((quantity == MODBUS_MIN_QUANTITY) || - (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS)) - goto error; - } - - if (~type & MODBUS_TYP_WRITE) - /* Except from Read/Write Multiple Registers function (code 23) */ - /* The length of all Read Data function requests is 6 bytes */ - /* Modbus Application Protocol Specification V1.1b3 from 6.1 to 6.4 */ - ModbusCheckHeaderLength(modbus, tx->length, 6); - - goto end; - -error: - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE); -end: - SCReturn; -} - -/** \internal - * \brief Parse Read data Response and verify protocol compliance - * - * \param tx Pointer to Modbus Transaction structure - * \param modbus Pointer to Modbus state structure - * \param input Pointer the received input data - * \param input_len Length of the received input data - * \param offset Offset of the received input data pointer - */ -static void ModbusParseReadResponse(ModbusTransaction *tx, - ModbusState *modbus, - const uint8_t *input, - uint32_t input_len, - uint16_t *offset) -{ - SCEnter(); - uint8_t count = 0; - - /* Count (1 bytes) */ - if (ModbusExtractUint8(modbus, &count, input, input_len, offset)) - goto end; - - /* Check Count range and value according to the request */ - if ((tx->type) & MODBUS_TYP_BIT_ACCESS_MASK) { - if ( (count < MODBUS_MIN_COUNT) || - (count > MODBUS_MAX_COUNT) || - (count != CEIL(tx->read.quantity))) - goto error; - } else { - if ( (count == MODBUS_MIN_COUNT) || - (count > MODBUS_MAX_COUNT) || - (count != (2 * (tx->read.quantity)))) - goto error; - } - - /* Except from Read/Write Multiple Registers function (code 23) */ - /* The length of all Read Data function responses is (3 bytes + count) */ - /* Modbus Application Protocol Specification V1.1b3 from 6.1 to 6.4 */ - ModbusCheckHeaderLength(modbus, tx->length, 3 + count); - goto end; - -error: - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH); -end: - SCReturn; -} - -/** \internal - * \brief Parse Write data Request, complete Transaction structure - * and verify protocol compliance. - * - * \param tx Pointer to Modbus Transaction structure - * \param modbus Pointer to Modbus state structure - * \param input Pointer the received input data - * \param input_len Length of the received input data - * \param offset Offset of the received input data pointer - * - * \retval On success returns 0 or on failure returns -1. - */ -static int ModbusParseWriteRequest(ModbusTransaction *tx, - ModbusState *modbus, - const uint8_t *input, - uint32_t input_len, - uint16_t *offset) -{ - SCEnter(); - uint16_t quantity = 1, word = 0; - uint8_t byte = 0, count = 1, type = tx->type; - - int i = 0; - - /* Starting/Output/Register Address (2 bytes) */ - if (ModbusExtractUint16(modbus, &(tx->write.address), input, input_len, offset)) - goto end; - - if (type & MODBUS_TYP_SINGLE) { - /* The length of Write Single Coil (code 5) and */ - /* Write Single Register (code 6) requests is 6 bytes */ - /* Modbus Application Protocol Specification V1.1b3 6.5 and 6.6 */ - if (ModbusCheckHeaderLength(modbus, tx->length, 6)) - goto end; - } else if (type & MODBUS_TYP_MULTIPLE) { - /* Quantity (2 bytes) */ - if (ModbusExtractUint16(modbus, &quantity, input, input_len, offset)) - goto end; - tx->write.quantity = quantity; - - /* Count (1 bytes) */ - if (ModbusExtractUint8(modbus, &count, input, input_len, offset)) - goto end; - tx->write.count = count; - - if (type & MODBUS_TYP_BIT_ACCESS_MASK) { - /* Check Quantity range and conversion in byte (count) */ - if ((quantity == MODBUS_MIN_QUANTITY) || - (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS) || - (quantity != CEIL(count))) - goto error; - - /* The length of Write Multiple Coils (code 15) request is (7 + count) */ - /* Modbus Application Protocol Specification V1.1b3 6.11 */ - if (ModbusCheckHeaderLength(modbus, tx->length, 7 + count)) - goto end; - } else { - /* Check Quantity range and conversion in byte (count) */ - if ((quantity == MODBUS_MIN_QUANTITY) || - (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS) || - (count != (2 * quantity))) - goto error; - - if (type & MODBUS_TYP_READ) { - /* The length of Read/Write Multiple Registers function (code 23) */ - /* request is (11 bytes + count) */ - /* Modbus Application Protocol Specification V1.1b3 6.17 */ - if (ModbusCheckHeaderLength(modbus, tx->length, 11 + count)) - goto end; - } else { - /* The length of Write Multiple Coils (code 15) and */ - /* Write Multiple Registers (code 16) functions requests is (7 bytes + count) */ - /* Modbus Application Protocol Specification V1.1b3 from 6.11 and 6.12 */ - if (ModbusCheckHeaderLength(modbus, tx->length, 7 + count)) - goto end; - } - } - } else { - /* Mask Write Register function (And_Mask and Or_Mask) */ - quantity = 2; - - /* The length of Mask Write Register (code 22) function request is 8 */ - /* Modbus Application Protocol Specification V1.1b3 6.16 */ - if (ModbusCheckHeaderLength(modbus, tx->length, 8)) - goto end; - } - - if (type & MODBUS_TYP_COILS) { - /* Output value (data block) unit is count */ - tx->data = (uint16_t *) SCCalloc(1, count * sizeof(uint16_t)); - if (unlikely(tx->data == NULL)) - SCReturnInt(-1); - - if (type & MODBUS_TYP_SINGLE) { - /* Outputs value (2 bytes) */ - if (ModbusExtractUint16(modbus, &word, input, input_len, offset)) - goto end; - tx->data[i] = word; - - if ((word != 0x00) && (word != 0xFF00)) - goto error; - } else { - for (i = 0; i < count; i++) { - /* Outputs value (1 byte) */ - if (ModbusExtractUint8(modbus, &byte, input, input_len, offset)) - goto end; - tx->data[i] = (uint16_t) byte; - } - } - } else { - /* Registers value (data block) unit is quantity */ - tx->data = (uint16_t *) SCCalloc(1, quantity * sizeof(uint16_t)); - if (unlikely(tx->data == NULL)) - SCReturnInt(-1); - - for (i = 0; i < quantity; i++) { - /* Outputs/Registers value (2 bytes) */ - if (ModbusExtractUint16(modbus, &word, input, input_len, offset)) - goto end; - tx->data[i] = word; - } - } - goto end; - -error: - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE); -end: - SCReturnInt(0); -} - -/** \internal - * \brief Parse Write data Response and verify protocol compliance - * - * \param tx Pointer to Modbus Transaction structure - * \param modbus Pointer to Modbus state structure - * \param input Pointer the received input data - * \param input_len Length of the received input data - * \param offset Offset of the received input data pointer - */ -static void ModbusParseWriteResponse(ModbusTransaction *tx, - ModbusState *modbus, - const uint8_t *input, - uint32_t input_len, - uint16_t *offset) -{ - SCEnter(); - uint16_t address = 0, quantity = 0, word = 0; - uint8_t type = tx->type; - - /* Starting Address (2 bytes) */ - if (ModbusExtractUint16(modbus, &address, input, input_len, offset)) - goto end; - - if (address != tx->write.address) - goto error; - - if (type & MODBUS_TYP_SINGLE) { - /* Check if Outputs/Registers value has been stored */ - if (tx->data != NULL) - { - /* Outputs/Registers value (2 bytes) */ - if (ModbusExtractUint16(modbus, &word, input, input_len, offset)) - goto end; - - /* Check with Outputs/Registers from request */ - if (word != tx->data[0]) - goto error; - } - } else if (type & MODBUS_TYP_MULTIPLE) { - /* Quantity (2 bytes) */ - if (ModbusExtractUint16(modbus, &quantity, input, input_len, offset)) - goto end; - - /* Check Quantity range */ - if (type & MODBUS_TYP_BIT_ACCESS_MASK) { - if ((quantity == MODBUS_MIN_QUANTITY) || - (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS)) - goto error; - } else { - if ((quantity == MODBUS_MIN_QUANTITY) || - (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS)) - goto error; - } - - /* Check Quantity value according to the request */ - if (quantity != tx->write.quantity) - goto error; - } else { - /* Check if And_Mask and Or_Mask values have been stored */ - if (tx->data != NULL) - { - /* And_Mask value (2 bytes) */ - if (ModbusExtractUint16(modbus, &word, input, input_len, offset)) - goto end; - - /* Check And_Mask value according to the request */ - if (word != tx->data[0]) - goto error; - - /* And_Or_Mask value (2 bytes) */ - if (ModbusExtractUint16(modbus, &word, input, input_len, offset)) - goto end; - - /* Check Or_Mask value according to the request */ - if (word != tx->data[1]) - goto error; - } - - /* The length of Mask Write Register (code 22) function response is 8 */ - /* Modbus Application Protocol Specification V1.1b3 6.16 */ - ModbusCheckHeaderLength(modbus, tx->length, 8); - goto end; - } - - /* Except from Mask Write Register (code 22) */ - /* The length of all Write Data function responses is 6 */ - /* Modbus Application Protocol Specification V1.1b3 6.5, 6.6, 6.11, 6.12 and 6.17 */ - ModbusCheckHeaderLength(modbus, tx->length, 6); - goto end; - -error: - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH); -end: - SCReturn; -} - -/** \internal - * \brief Parse Diagnostic Request, complete Transaction - * structure (Category) and verify protocol compliance. - * - * \param tx Pointer to Modbus Transaction structure - * \param modbus Pointer to Modbus state structure - * \param input Pointer the received input data - * \param input_len Length of the received input data - * \param offset Offset of the received input data pointer - * - * \retval Reserved category function returns 1 otherwise returns 0. - */ -static int ModbusParseDiagnosticRequest(ModbusTransaction *tx, - ModbusState *modbus, - const uint8_t *input, - uint32_t input_len, - uint16_t *offset) -{ - SCEnter(); - uint16_t data = 0; - - /* Sub-function (2 bytes) */ - if (ModbusExtractUint16(modbus, &(tx->subFunction), input, input_len, offset)) - goto end; - - /* Data (2 bytes) */ - if (ModbusExtractUint16(modbus, &data, input, input_len, offset)) - goto end; - - if (tx->subFunction != MODBUS_SUBFUNC_QUERY_DATA) { - switch (tx->subFunction) { - case MODBUS_SUBFUNC_RESTART_COM: - if ((data != 0x00) && (data != 0xFF00)) - goto error; - break; - - case MODBUS_SUBFUNC_CHANGE_DELIMITER: - if ((data & 0xFF) != 0x00) - goto error; - break; - - case MODBUS_SUBFUNC_LISTEN_MODE: - /* No answer is expected then mark tx as completed. */ - tx->replied = 1; - /* Fallthrough */ - case MODBUS_SUBFUNC_DIAG_REGS: - case MODBUS_SUBFUNC_CLEAR_REGS: - case MODBUS_SUBFUNC_BUS_MSG_COUNT: - case MODBUS_SUBFUNC_COM_ERR_COUNT: - case MODBUS_SUBFUNC_EXCEPT_ERR_COUNT: - case MODBUS_SUBFUNC_SERVER_MSG_COUNT: - case MODBUS_SUBFUNC_SERVER_NO_RSP_COUNT: - case MODBUS_SUBFUNC_SERVER_NAK_COUNT: - case MODBUS_SUBFUNC_SERVER_BUSY_COUNT: - case MODBUS_SUBFUNC_SERVER_CHAR_COUNT: - case MODBUS_SUBFUNC_CLEAR_COUNT: - if (data != 0x00) - goto error; - break; - - default: - /* Set function code category */ - tx->category = MODBUS_CAT_RESERVED; - SCReturnInt(1); - } - - /* The length of all Diagnostic Requests is 6 */ - /* Modbus Application Protocol Specification V1.1b3 6.8 */ - ModbusCheckHeaderLength(modbus, tx->length, 6); - } - - goto end; - -error: - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE); -end: - SCReturnInt(0); -} - -/* Modbus Function Code Categories structure. */ -typedef struct ModbusFunctionCodeRange_ { - uint8_t function; - uint8_t category; -} ModbusFunctionCodeRange; - -/* Modbus Function Code Categories table. */ -static ModbusFunctionCodeRange modbusFunctionCodeRanges[] = { - { 0, MODBUS_CAT_PUBLIC_UNASSIGNED}, - { 9, MODBUS_CAT_RESERVED }, - { 15, MODBUS_CAT_PUBLIC_UNASSIGNED}, - { 41, MODBUS_CAT_RESERVED }, - { 43, MODBUS_CAT_PUBLIC_UNASSIGNED}, - { 65, MODBUS_CAT_USER_DEFINED }, - { 73, MODBUS_CAT_PUBLIC_UNASSIGNED}, - { 90, MODBUS_CAT_RESERVED }, - { 92, MODBUS_CAT_PUBLIC_UNASSIGNED}, - { 100, MODBUS_CAT_USER_DEFINED }, - { 111, MODBUS_CAT_PUBLIC_UNASSIGNED}, - { 125, MODBUS_CAT_RESERVED }, - { 128, MODBUS_CAT_NONE } -}; - -/** \internal - * \brief Parse the Modbus Protocol Data Unit (PDU) Request - * - * \param tx Pointer to Modbus Transaction structure - * \param ModbusPdu Pointer the Modbus PDU state in which the value to be stored - * \param input Pointer the received input data - * \param input_len Length of the received input data - */ -static void ModbusParseRequestPDU(ModbusTransaction *tx, - ModbusState *modbus, - const uint8_t *input, - uint32_t input_len) -{ - SCEnter(); - uint16_t offset = (uint16_t) sizeof(ModbusHeader); - uint8_t count = 0; - - int i = 0; - - /* Standard function codes used on MODBUS application layer protocol (1 byte) */ - if (ModbusExtractUint8(modbus, &(tx->function), input, input_len, &offset)) - goto end; - - /* Set default function code category */ - tx->category = MODBUS_CAT_NONE; - - /* Set default function primary table */ - tx->type = MODBUS_TYP_NONE; - - switch (tx->function) { - case MODBUS_FUNC_NONE: - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE); - break; - - case MODBUS_FUNC_READCOILS: - /* Set function type */ - tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_READ); - break; - - case MODBUS_FUNC_READDISCINPUTS: - /* Set function type */ - tx->type = (MODBUS_TYP_DISCRETES | MODBUS_TYP_READ); - break; - - case MODBUS_FUNC_READHOLDREGS: - /* Set function type */ - tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_READ); - break; - - case MODBUS_FUNC_READINPUTREGS: - /* Set function type */ - tx->type = (MODBUS_TYP_INPUT | MODBUS_TYP_READ); - break; - - case MODBUS_FUNC_WRITESINGLECOIL: - /* Set function type */ - tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_WRITE_SINGLE); - break; - - case MODBUS_FUNC_WRITESINGLEREG: - /* Set function type */ - tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE_SINGLE); - break; - - case MODBUS_FUNC_WRITEMULTCOILS: - /* Set function type */ - tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_WRITE_MULTIPLE); - break; - - case MODBUS_FUNC_WRITEMULTREGS: - /* Set function type */ - tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE_MULTIPLE); - break; - - case MODBUS_FUNC_MASKWRITEREG: - /* Set function type */ - tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE); - break; - - case MODBUS_FUNC_READWRITEMULTREGS: - /* Set function type */ - tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_READ_WRITE_MULTIPLE); - break; - - case MODBUS_FUNC_READFILERECORD: - case MODBUS_FUNC_WRITEFILERECORD: - /* Count/length (1 bytes) */ - if (ModbusExtractUint8(modbus, &count, input, input_len, &offset)) - goto end; - - /* Modbus Application Protocol Specification V1.1b3 6.14 and 6.15 */ - ModbusCheckHeaderLength(modbus, tx->length, 2 + count); - break; - - case MODBUS_FUNC_DIAGNOSTIC: - if(ModbusParseDiagnosticRequest(tx, modbus, input, input_len, &offset)) - goto end; - break; - - case MODBUS_FUNC_READEXCSTATUS: - case MODBUS_FUNC_GETCOMEVTCOUNTER: - case MODBUS_FUNC_GETCOMEVTLOG: - case MODBUS_FUNC_REPORTSERVERID: - /* Modbus Application Protocol Specification V1.1b3 6.7, 6.9, 6.10 and 6.13 */ - ModbusCheckHeaderLength(modbus, tx->length, 2); - break; - - case MODBUS_FUNC_READFIFOQUEUE: - /* Modbus Application Protocol Specification V1.1b3 6.18 */ - ModbusCheckHeaderLength(modbus, tx->length, 4); - break; - - case MODBUS_FUNC_ENCAPINTTRANS: - /* MEI type (1 byte) */ - if (ModbusExtractUint8(modbus, &(tx->mei), input, input_len, &offset)) - goto end; - - if (tx->mei == MODBUS_MEI_ENCAPINTTRANS_READ) { - /* Modbus Application Protocol Specification V1.1b3 6.21 */ - ModbusCheckHeaderLength(modbus, tx->length, 5); - } else if (tx->mei != MODBUS_MEI_ENCAPINTTRANS_CAN) { - /* Set function code category */ - tx->category = MODBUS_CAT_RESERVED; - goto end; - } - break; - - default: - /* Check if request is error. */ - if (tx->function & MODBUS_FUNC_ERRORMASK) { - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE); - goto end; - } - - /* Get and store function code category */ - for (i = 0; modbusFunctionCodeRanges[i].category != MODBUS_CAT_NONE; i++) { - if (tx->function <= modbusFunctionCodeRanges[i].function) - break; - tx->category = modbusFunctionCodeRanges[i].category; - } - goto end; - } - - /* Set function code category */ - tx->category = MODBUS_CAT_PUBLIC_ASSIGNED; - - if (tx->type & MODBUS_TYP_READ) - ModbusParseReadRequest(tx, modbus, input, input_len, &offset); - - if (tx->type & MODBUS_TYP_WRITE) - ModbusParseWriteRequest(tx, modbus, input, input_len, &offset); - -end: - SCReturn; -} - -/** \internal - * \brief Parse the Modbus Protocol Data Unit (PDU) Response - * - * \param tx Pointer to Modbus Transaction structure - * \param modbus Pointer the Modbus PDU state in which the value to be stored - * \param input Pointer the received input data - * \param input_len Length of the received input data - * \param offset Offset of the received input data pointer - */ -static void ModbusParseResponsePDU(ModbusTransaction *tx, - ModbusState *modbus, - const uint8_t *input, - uint32_t input_len) -{ - SCEnter(); - uint16_t offset = (uint16_t) sizeof(ModbusHeader); - uint8_t count = 0, function = 0, mei = 0; - uint8_t error = false; - - /* Standard function codes used on MODBUS application layer protocol (1 byte) */ - if (ModbusExtractUint8(modbus, &function, input, input_len, &offset)) - goto end; - - /* Check if response is error */ - if(function & MODBUS_FUNC_ERRORMASK) { - function &= MODBUS_FUNC_MASK; - error = true; - } - - if (tx->category == MODBUS_CAT_PUBLIC_ASSIGNED) { - /* Check if response is error. */ - if (error) { - ModbusExceptionResponse(tx, modbus, input, input_len, &offset); - } else { - switch(function) { - case MODBUS_FUNC_READEXCSTATUS: - /* Modbus Application Protocol Specification V1.1b3 6.7 */ - ModbusCheckHeaderLength(modbus, tx->length, 3); - goto end; - - case MODBUS_FUNC_GETCOMEVTCOUNTER: - /* Modbus Application Protocol Specification V1.1b3 6.9 */ - ModbusCheckHeaderLength(modbus, tx->length, 6); - goto end; - - case MODBUS_FUNC_READFILERECORD: - case MODBUS_FUNC_WRITEFILERECORD: - /* Count/length (1 bytes) */ - if (ModbusExtractUint8(modbus, &count, input, input_len, &offset)) - goto end; - - /* Modbus Application Protocol Specification V1.1b3 6.14 and 6.15 */ - ModbusCheckHeaderLength(modbus, tx->length, 2 + count); - goto end; - - case MODBUS_FUNC_ENCAPINTTRANS: - /* MEI type (1 byte) */ - if (ModbusExtractUint8(modbus, &mei, input, input_len, &offset)) - goto end; - - if (mei != tx->mei) - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH); - goto end; - } - - if (tx->type & MODBUS_TYP_READ) - ModbusParseReadResponse(tx, modbus, input, input_len, &offset); - /* Read/Write response contents none write response part */ - else if (tx->type & MODBUS_TYP_WRITE) - ModbusParseWriteResponse(tx, modbus, input, input_len, &offset); - } - } - -end: - SCReturn; -} - -/** \internal - * \brief Parse the Modbus Application Protocol (MBAP) header - * - * \param header Pointer the Modbus header state in which the value to be stored - * \param input Pointer the received input data - */ -static int ModbusParseHeader(ModbusState *modbus, - ModbusHeader *header, - const uint8_t *input, - uint32_t input_len) -{ - SCEnter(); - uint16_t offset = 0; - - int r = 0; - - /* can't pass the header fields directly due to alignment (Bug 2088) */ - uint16_t transaction_id = 0; - uint16_t protocol_id = 0; - uint16_t length = 0; - uint8_t unit_id = 0; - - /* Transaction Identifier (2 bytes) */ - r = ModbusExtractUint16(modbus, &transaction_id, input, input_len, &offset); - /* Protocol Identifier (2 bytes) */ - r |= ModbusExtractUint16(modbus, &protocol_id, input, input_len, &offset); - /* Length (2 bytes) */ - r |= ModbusExtractUint16(modbus, &length, input, input_len, &offset); - /* Unit Identifier (1 byte) */ - r |= ModbusExtractUint8(modbus, &unit_id, input, input_len, &offset); - - if (r != 0) { - SCReturnInt(-1); - } - header->transactionId = transaction_id; - header->protocolId = protocol_id; - header->length = length; - header->unitId = unit_id; - - SCReturnInt(0); -} - -/** \internal - * - * \brief This function is called to retrieve a Modbus Request - * - * \param state Modbus state structure for the parser - * \param input Input line of the command - * \param input_len Length of the request - * - * \retval AppLayerResult APP_LAYER_OK or APP_LAYER_ERROR - */ -static AppLayerResult ModbusParseRequest(Flow *f, - void *state, - AppLayerParserState *pstate, - const uint8_t *input, - uint32_t input_len, - void *local_data, - const uint8_t flags) -{ - SCEnter(); - ModbusState *modbus = (ModbusState *) state; - ModbusTransaction *tx; - ModbusHeader header; - - if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS)) { - SCReturnStruct(APP_LAYER_OK); - } else if (input == NULL || input_len == 0) { - SCReturnStruct(APP_LAYER_ERROR); - } - - while (input_len > 0) { - uint32_t adu_len = input_len; - const uint8_t *adu = input; - - /* Extract MODBUS Header */ - if (ModbusParseHeader(modbus, &header, adu, adu_len)) - SCReturnStruct(APP_LAYER_OK); - - /* Update ADU length with length in Modbus header. */ - adu_len = (uint32_t) sizeof(ModbusHeader) + (uint32_t) header.length - 1; - if (adu_len > input_len) - SCReturnStruct(APP_LAYER_OK); - - /* Allocate a Transaction Context and add it to Transaction list */ - tx = ModbusTxAlloc(modbus); - if (tx == NULL) - SCReturnStruct(APP_LAYER_OK); - - /* Check MODBUS Header */ - ModbusCheckHeader(modbus, &header); - - /* Store Unit ID, Transaction ID & PDU length */ - tx->unit_id = header.unitId; - tx->transactionId = header.transactionId; - tx->length = header.length; - - /* Extract MODBUS PDU and fill Transaction Context */ - ModbusParseRequestPDU(tx, modbus, adu, adu_len); - - /* Update input line and remaining input length of the command */ - input += adu_len; - input_len -= adu_len; - } - - SCReturnStruct(APP_LAYER_OK); -} - -/** \internal - * \brief This function is called to retrieve a Modbus response - * - * \param state Pointer to Modbus state structure for the parser - * \param input Input line of the command - * \param input_len Length of the request - * - * \retval AppLayerResult APP_LAYER_OK or APP_LAYER_ERROR - */ -static AppLayerResult ModbusParseResponse(Flow *f, - void *state, - AppLayerParserState *pstate, - const uint8_t *input, - uint32_t input_len, - void *local_data, - const uint8_t flags) -{ - SCEnter(); - ModbusHeader header; - ModbusState *modbus = (ModbusState *) state; - ModbusTransaction *tx; - - if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC)) { - SCReturnStruct(APP_LAYER_OK); - } else if (input == NULL || input_len == 0) { - SCReturnStruct(APP_LAYER_ERROR); - } - - while (input_len > 0) { - uint32_t adu_len = input_len; - const uint8_t *adu = input; - - /* Extract MODBUS Header */ - if (ModbusParseHeader(modbus, &header, adu, adu_len)) - SCReturnStruct(APP_LAYER_OK); - - /* Update ADU length with length in Modbus header. */ - adu_len = (uint32_t) sizeof(ModbusHeader) + (uint32_t) header.length - 1; - if (adu_len > input_len) - SCReturnStruct(APP_LAYER_OK); - - /* Find the transaction context thanks to transaction ID (and function code) */ - tx = ModbusTxFindByTransaction(modbus, header.transactionId); - if (tx == NULL) { - /* Allocate a Transaction Context if not previous request */ - /* and add it to Transaction list */ - tx = ModbusTxAlloc(modbus); - if (tx == NULL) - SCReturnStruct(APP_LAYER_OK); - - SCLogDebug("MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE"); - ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE); - } else { - /* Store PDU length */ - tx->length = header.length; - - /* Extract MODBUS PDU and fill Transaction Context */ - ModbusParseResponsePDU(tx, modbus, adu, adu_len); - } - - /* Check and store MODBUS Header */ - ModbusCheckHeader(modbus, &header); - - /* Mark as completed */ - tx->replied = 1; - - /* Update input line and remaining input length of the command */ - input += adu_len; - input_len -= adu_len; - } - - SCReturnStruct(APP_LAYER_OK); -} - -/** \internal - * \brief Function to allocate the Modbus state memory - */ -static void *ModbusStateAlloc(void *orig_state, AppProto proto_orig) -{ - ModbusState *modbus; - - modbus = (ModbusState *) SCCalloc(1, sizeof(ModbusState)); - if (unlikely(modbus == NULL)) - return NULL; - - TAILQ_INIT(&modbus->tx_list); - - return (void *) modbus; -} - -/** \internal - * \brief Function to free the Modbus state memory - */ -static void ModbusStateFree(void *state) -{ - SCEnter(); - ModbusState *modbus = (ModbusState *) state; - ModbusTransaction *tx = NULL, *ttx; - - if (state) { - TAILQ_FOREACH_SAFE(tx, &modbus->tx_list, next, ttx) { - ModbusTxFree(tx); - } - - SCFree(state); - } - SCReturn; -} - -static uint16_t ModbusProbingParser(Flow *f, - uint8_t direction, - const uint8_t *input, - uint32_t input_len, - uint8_t *rdir) -{ - ModbusHeader *header = (ModbusHeader *) input; - - /* Modbus header is 7 bytes long */ - if (input_len < sizeof(ModbusHeader)) - return ALPROTO_UNKNOWN; - - /* MODBUS protocol is identified by the value 0. */ - if (header->protocolId != 0) - return ALPROTO_FAILED; - - return ALPROTO_MODBUS; -} - -static DetectEngineState *ModbusGetTxDetectState(void *vtx) -{ - ModbusTransaction *tx = (ModbusTransaction *)vtx; - return tx->de_state; -} - -static int ModbusSetTxDetectState(void *vtx, DetectEngineState *s) -{ - ModbusTransaction *tx = (ModbusTransaction *)vtx; - tx->de_state = s; - return 0; -} - -/** - * \brief Function to register the Modbus protocol parsers and other functions + * \brief Function to register the Modbus protocol parser */ void RegisterModbusParsers(void) { - SCEnter(); - const char *proto_name = "modbus"; - - /* Modbus application protocol V1.1b3 */ - if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { - AppLayerProtoDetectRegisterProtocol(ALPROTO_MODBUS, proto_name); - - if (RunmodeIsUnittests()) { - AppLayerProtoDetectPPRegister(IPPROTO_TCP, - "502", - ALPROTO_MODBUS, - 0, sizeof(ModbusHeader), - STREAM_TOSERVER, - ModbusProbingParser, ModbusProbingParser); - } else { - /* If there is no app-layer section for Modbus, silently - * leave it disabled. */ - if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP, - proto_name, ALPROTO_MODBUS, - 0, sizeof(ModbusHeader), - ModbusProbingParser, ModbusProbingParser)) { - return; - } - } - - ConfNode *p = NULL; - p = ConfGetNode("app-layer.protocols.modbus.request-flood"); - if (p != NULL) { - uint32_t value; - if (ParseSizeStringU32(p->val, &value) < 0) { - SCLogError(SC_ERR_MODBUS_CONFIG, "invalid value for request-flood %s", p->val); - } else { - request_flood = value; - } - } - SCLogConfig("Modbus request flood protection level: %u", request_flood); - - p = ConfGetNode("app-layer.protocols.modbus.stream-depth"); - if (p != NULL) { - uint32_t value; - if (ParseSizeStringU32(p->val, &value) < 0) { - SCLogError(SC_ERR_MODBUS_CONFIG, "invalid value for stream-depth %s", p->val); - } else { - stream_depth = value; - } - } - SCLogConfig("Modbus stream depth: %u", stream_depth); - } else { - SCLogConfig("Protocol detection and parser disabled for %s protocol.", proto_name); - return; - } - if (AppLayerParserConfParserEnabled("tcp", proto_name)) { - AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER, ModbusParseRequest); - AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOCLIENT, ModbusParseResponse); - AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateAlloc, ModbusStateFree); - - AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetEvents); - AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_MODBUS, - ModbusGetTxDetectState, ModbusSetTxDetectState); - - AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTx); - AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTxData); - AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTxCnt); - AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateTxFree); - - AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetAlstateProgress); - AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_MODBUS, 1, 1); - - AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateGetEventInfo); - AppLayerParserRegisterGetEventInfoById(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateGetEventInfoById); - - AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER); - - AppLayerParserSetStreamDepth(IPPROTO_TCP, ALPROTO_MODBUS, stream_depth); - } else { - SCLogConfig("Parsed disabled for %s protocol. Protocol detection" "still on.", proto_name); - } + rs_modbus_register_parser(); #ifdef UNITTESTS AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_MODBUS, ModbusParserRegisterTests); #endif @@ -1556,6 +70,11 @@ void RegisterModbusParsers(void) #include "stream-tcp.h" #include "stream-tcp-private.h" +#include "rust.h" + +/* Modbus default stream reassembly depth */ +#define MODBUS_CONFIG_DEFAULT_STREAM_DEPTH 0 + /* Modbus Application Protocol Specification V1.1b3 6.1: Read Coils */ static uint8_t invalidFunctionCode[] = { /* Transaction ID */ 0x00, 0x00, @@ -1770,13 +289,14 @@ static int ModbusParserTest01(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); - FAIL_IF_NOT(tx->function == 1); - FAIL_IF_NOT(tx->read.address == 0x7890); - FAIL_IF_NOT(tx->read.quantity == 19); + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0); + FAIL_IF_NULL(request._0); + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1); + FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890); + FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, @@ -1785,7 +305,7 @@ static int ModbusParserTest01(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - FAIL_IF_NOT(modbus_state->transaction_max == 1); + FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); @@ -1818,17 +338,22 @@ static int ModbusParserTest02(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0); + FAIL_IF_NULL(request._0); + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 16); + FAIL_IF_NOT(rs_modbus_message_get_write_multreq_address(&request) == 0x01); + FAIL_IF_NOT(rs_modbus_message_get_write_multreq_quantity(&request) == 2); - FAIL_IF_NOT(tx->function == 16); - FAIL_IF_NOT(tx->write.address == 0x01); - FAIL_IF_NOT(tx->write.quantity == 2); - FAIL_IF_NOT(tx->write.count == 4); - FAIL_IF_NOT(tx->data[0] == 0x000A); - FAIL_IF_NOT(tx->data[1] == 0x0102); + size_t data_len; + const uint8_t *data = rs_modbus_message_get_write_multreq_data(&request, &data_len); + FAIL_IF_NOT(data_len == 4); + FAIL_IF_NOT(data[0] == 0x00); + FAIL_IF_NOT(data[1] == 0x0A); + FAIL_IF_NOT(data[2] == 0x01); + FAIL_IF_NOT(data[3] == 0x02); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, @@ -1837,7 +362,7 @@ static int ModbusParserTest02(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - FAIL_IF_NOT(modbus_state->transaction_max == 1); + FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); @@ -1898,20 +423,27 @@ static int ModbusParserTest03(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); - - FAIL_IF_NOT(tx->function == 23); - FAIL_IF_NOT(tx->read.address == 0x03); - FAIL_IF_NOT(tx->read.quantity == 6); - FAIL_IF_NOT(tx->write.address == 0x0E); - FAIL_IF_NOT(tx->write.quantity == 3); - FAIL_IF_NOT(tx->write.count == 6); - FAIL_IF_NOT(tx->data[0] == 0x1234); - FAIL_IF_NOT(tx->data[1] == 0x5678); - FAIL_IF_NOT(tx->data[2] == 0x9ABC); + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0); + FAIL_IF_NULL(request._0); + + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 23); + FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_read_address(&request) == 0x03); + FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_read_quantity(&request) == 6); + FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_write_address(&request) == 0x0E); + FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_write_quantity(&request) == 3); + + size_t data_len; + uint8_t const *data = rs_modbus_message_get_rw_multreq_write_data(&request, &data_len); + FAIL_IF_NOT(data_len == 6); + FAIL_IF_NOT(data[0] == 0x12); + FAIL_IF_NOT(data[1] == 0x34); + FAIL_IF_NOT(data[2] == 0x56); + FAIL_IF_NOT(data[3] == 0x78); + FAIL_IF_NOT(data[4] == 0x9A); + FAIL_IF_NOT(data[5] == 0xBC); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, @@ -1920,7 +452,7 @@ static int ModbusParserTest03(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - FAIL_IF_NOT(modbus_state->transaction_max == 1); + FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -1965,13 +497,14 @@ static int ModbusParserTest04(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0); + FAIL_IF_NULL(request._0); - FAIL_IF_NOT(tx->function == 8); - FAIL_IF_NOT(tx->subFunction == 4); + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 8); + FAIL_IF_NOT(rs_modbus_message_get_subfunction(&request) == 4); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); @@ -2031,7 +564,7 @@ static int ModbusParserTest05(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); /* do detect */ @@ -2104,7 +637,7 @@ static int ModbusParserTest06(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); /* do detect */ @@ -2175,10 +708,10 @@ static int ModbusParserTest07(void) { STREAM_TOSERVER, invalidLengthWriteMultipleRegistersReq, sizeof(invalidLengthWriteMultipleRegistersReq)); - FAIL_IF_NOT(r == 0); + FAIL_IF_NOT(r == 1); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); /* do detect */ @@ -2251,14 +784,15 @@ static int ModbusParserTest08(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0); + FAIL_IF_NULL(request._0); - FAIL_IF_NOT(tx->function == 1); - FAIL_IF_NOT(tx->read.address == 0x7890); - FAIL_IF_NOT(tx->read.quantity == 19); + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1); + FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890); + FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, @@ -2267,7 +801,7 @@ static int ModbusParserTest08(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - FAIL_IF_NOT(modbus_state->transaction_max == 1); + FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -2311,21 +845,22 @@ static int ModbusParserTest09(void) { FLOWLOCK_WRLOCK(&f); int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, input, input_len - part2_len); - FAIL_IF_NOT(r == 0); + FAIL_IF_NOT(r == 1); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, input, input_len); FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0); + FAIL_IF_NULL(request._0); - FAIL_IF_NOT(tx->function == 1); - FAIL_IF_NOT(tx->read.address == 0x7890); - FAIL_IF_NOT(tx->read.quantity == 19); + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1); + FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890); + FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19); input_len = sizeof(readCoilsRsp); part2_len = 10; @@ -2334,14 +869,14 @@ static int ModbusParserTest09(void) { FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, input, input_len - part2_len); - FAIL_IF_NOT(r == 0); + FAIL_IF_NOT(r == 1); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, input, input_len); FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - FAIL_IF_NOT(modbus_state->transaction_max ==1); + FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); @@ -2382,19 +917,25 @@ static int ModbusParserTest10(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - FAIL_IF_NOT(modbus_state->transaction_max == 2); + FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 2); + + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 1); + FAIL_IF_NULL(request._0); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 1); + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 16); + FAIL_IF_NOT(rs_modbus_message_get_write_multreq_address(&request) == 0x01); + FAIL_IF_NOT(rs_modbus_message_get_write_multreq_quantity(&request) == 2); - FAIL_IF_NOT(tx->function == 16); - FAIL_IF_NOT(tx->write.address == 0x01); - FAIL_IF_NOT(tx->write.quantity == 2); - FAIL_IF_NOT(tx->write.count == 4); - FAIL_IF_NOT(tx->data[0] == 0x000A); - FAIL_IF_NOT(tx->data[1] == 0x0102); + size_t data_len; + uint8_t const *data = rs_modbus_message_get_write_multreq_data(&request, &data_len); + FAIL_IF_NOT(data_len == 4); + FAIL_IF_NOT(data[0] == 0x00); + FAIL_IF_NOT(data[1] == 0x0A); + FAIL_IF_NOT(data[2] == 0x01); + FAIL_IF_NOT(data[3] == 0x02); input_len = sizeof(readCoilsRsp) + sizeof(writeMultipleRegistersRsp); @@ -2476,7 +1017,7 @@ static int ModbusParserTest11(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); /* do detect */ @@ -2550,7 +1091,7 @@ static int ModbusParserTest12(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); /* do detect */ @@ -2596,14 +1137,15 @@ static int ModbusParserTest13(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0); + FAIL_IF_NULL(request._0); - FAIL_IF_NOT(tx->function == 22); - FAIL_IF_NOT(tx->data[0] == 0x00F2); - FAIL_IF_NOT(tx->data[1] == 0x0025); + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 22); + FAIL_IF_NOT(rs_modbus_message_get_and_mask(&request) == 0x00F2); + FAIL_IF_NOT(rs_modbus_message_get_or_mask(&request) == 0x0025); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, @@ -2612,7 +1154,7 @@ static int ModbusParserTest13(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - FAIL_IF_NOT(modbus_state->transaction_max == 1); + FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); @@ -2645,14 +1187,15 @@ static int ModbusParserTest14(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0); + FAIL_IF_NULL(request._0); - FAIL_IF_NOT(tx->function == 6); - FAIL_IF_NOT(tx->write.address == 0x0001); - FAIL_IF_NOT(tx->data[0] == 0x0003); + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 6); + FAIL_IF_NOT(rs_modbus_message_get_write_address(&request) == 0x0001); + FAIL_IF_NOT(rs_modbus_message_get_write_data(&request) == 0x0003); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, @@ -2661,7 +1204,7 @@ static int ModbusParserTest14(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - FAIL_IF_NOT(modbus_state->transaction_max == 1); + FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); @@ -2721,12 +1264,13 @@ static int ModbusParserTest15(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0); + FAIL_IF_NULL(request._0); - FAIL_IF_NOT(tx->function == 22); + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 22); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -2740,7 +1284,11 @@ static int ModbusParserTest15(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - FAIL_IF_NOT(modbus_state->transaction_max == 1); + FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1); + ModbusMessage response = rs_modbus_state_get_tx_response(modbus_state, 0); + FAIL_IF_NULL(response._0); + + FAIL_IF_NOT(rs_modbus_message_get_function(&response) == 22); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); @@ -2808,13 +1356,18 @@ static int ModbusParserTest16(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); - ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0); + FAIL_IF_NULL(request._0); - FAIL_IF_NOT(tx->function == 6); - FAIL_IF_NOT(tx->write.address == 0x0001); + FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 6); + size_t data_len; + const uint8_t *data = rs_modbus_message_get_bytevec_data(&request, &data_len); + FAIL_IF_NOT(data_len == 2); + FAIL_IF_NOT(data[0] == 0x00); + FAIL_IF_NOT(data[1] == 0x01); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -2828,7 +1381,12 @@ static int ModbusParserTest16(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - FAIL_IF_NOT(modbus_state->transaction_max == 1); + FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1); + ModbusMessage response = rs_modbus_state_get_tx_response(modbus_state, 0); + FAIL_IF_NULL(response._0); + + FAIL_IF_NOT(rs_modbus_message_get_function(&response) == 6); + FAIL_IF_NOT(rs_modbus_message_get_write_address(&response) == 0x0001); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); @@ -2908,7 +1466,7 @@ static int ModbusParserTest18(void) { FLOWLOCK_WRLOCK(&f); int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, input, input_len - part2_len); - FAIL_IF(r != 0); + FAIL_IF(r != 1); FLOWLOCK_UNLOCK(&f); FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH); @@ -2930,7 +1488,7 @@ static int ModbusParserTest18(void) { FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, input, input_len - part2_len); - FAIL_IF(r != 0); + FAIL_IF(r != 1); FLOWLOCK_UNLOCK(&f); FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH); @@ -3002,7 +1560,7 @@ static int ModbusParserTest19(void) { FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; + ModbusState *modbus_state = f.alstate; FAIL_IF_NULL(modbus_state); /* do detect */ diff --git a/src/app-layer-modbus.h b/src/app-layer-modbus.h index 467837bd66..5d97fdcede 100644 --- a/src/app-layer-modbus.h +++ b/src/app-layer-modbus.h @@ -34,25 +34,6 @@ #ifndef __APP_LAYER_MODBUS_H__ #define __APP_LAYER_MODBUS_H__ -#include "decode.h" -#include "detect-engine-state.h" -#include "queue.h" -#include "rust.h" - -/* Modbus Application Data Unit (ADU) - * and Protocol Data Unit (PDU) messages */ -enum { - MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID, - MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE, - MODBUS_DECODER_EVENT_INVALID_LENGTH, - MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER, - MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE, - MODBUS_DECODER_EVENT_INVALID_VALUE, - MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE, - MODBUS_DECODER_EVENT_VALUE_MISMATCH, - MODBUS_DECODER_EVENT_FLOODED, -}; - /* Modbus Function Code Categories. */ #define MODBUS_CAT_NONE 0x0 #define MODBUS_CAT_PUBLIC_ASSIGNED (1<<0) @@ -79,57 +60,6 @@ enum { #define MODBUS_TYP_WRITE_MULTIPLE (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE) #define MODBUS_TYP_READ_WRITE_MULTIPLE (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE) -/* Modbus Function Code. */ -#define MODBUS_FUNC_NONE 0x00 - -/* Modbus Transaction Structure, request/response. */ -typedef struct ModbusTransaction_ { - struct ModbusState_ *modbus; - - uint64_t tx_num; /**< internal: id */ - uint16_t transactionId; - uint16_t length; - uint8_t unit_id; - uint8_t function; - uint8_t category; - uint8_t type; - uint8_t replied; /**< bool indicating request is replied to. */ - - union { - uint16_t subFunction; - uint8_t mei; - struct { - struct { - uint16_t address; - uint16_t quantity; - } read; - struct { - uint16_t address; - uint16_t quantity; - uint8_t count; - } write; - }; - }; - uint16_t *data; /**< to store data to write, bit is converted in 16bits. */ - - AppLayerDecoderEvents *decoder_events; /**< per tx events */ - DetectEngineState *de_state; - AppLayerTxData tx_data; - - TAILQ_ENTRY(ModbusTransaction_) next; -} ModbusTransaction; - -/* Modbus State Structure. */ -typedef struct ModbusState_ { - TAILQ_HEAD(, ModbusTransaction_) tx_list; /**< transaction list */ - ModbusTransaction *curr; /**< ptr to current tx */ - uint64_t transaction_max; - uint32_t unreplied_cnt; /**< number of unreplied requests */ - uint16_t events; - uint8_t givenup; /**< bool indicating flood. */ -} ModbusState; - void RegisterModbusParsers(void); -void ModbusParserRegisterTests(void); #endif /* __APP_LAYER_MODBUS_H__ */ diff --git a/src/detect-engine-modbus.c b/src/detect-engine-modbus.c index 6c1551457a..354a73c707 100644 --- a/src/detect-engine-modbus.c +++ b/src/detect-engine-modbus.c @@ -35,7 +35,6 @@ #include "suricata-common.h" #include "app-layer.h" -#include "app-layer-modbus.h" #include "detect.h" #include "detect-modbus.h" @@ -46,224 +45,6 @@ #include "util-debug.h" -/** \internal - * - * \brief Value match detection code - * - * \param value Modbus value context (min, max and mode) - * \param min Minimum value to compare - * \param inter Interval or maximum (min + inter) value to compare - * - * \retval 1 match or 0 no match - */ -static int DetectEngineInspectModbusValueMatch(DetectModbusValue *value, - uint16_t min, - uint16_t inter) -{ - SCEnter(); - uint16_t max = min + inter; - - int ret = 0; - - switch (value->mode) { - case DETECT_MODBUS_EQ: - if ((value->min >= min) && (value->min <= max)) - ret = 1; - break; - - case DETECT_MODBUS_LT: - if (value->min > min) - ret = 1; - break; - - case DETECT_MODBUS_GT: - if (value->min < max) - ret = 1; - break; - - case DETECT_MODBUS_RA: - if ((value->max > min) && (value->min < max)) - ret = 1; - break; - } - - SCReturnInt(ret); -} - -/** \internal - * - * \brief Do data (and address) inspection & validation for a signature - * - * \param tx Pointer to Modbus Transaction - * \param address Address inspection - * \param data Pointer to data signature structure to match - * - * \retval 0 no match or 1 match - */ -static int DetectEngineInspectModbusData(ModbusTransaction *tx, - uint16_t address, - DetectModbusValue *data) -{ - SCEnter(); - uint16_t offset, value = 0, type = tx->type; - - if (type & MODBUS_TYP_SINGLE) { - /* Output/Register(s) Value */ - if (type & MODBUS_TYP_COILS) - value = (tx->data[0])? 1 : 0; - else - value = tx->data[0]; - } else if (type & MODBUS_TYP_MULTIPLE) { - int i, size = (int) sizeof(tx->data); - - offset = address - (tx->write.address + 1); - - /* In case of Coils, offset is in bit (convert in byte) */ - if (type & MODBUS_TYP_COILS) - offset >>= 3; - - for (i=0; i< size; i++) { - /* Select the correct register/coils amongst the output value */ - if (!(offset--)) { - value = tx->data[i]; - break; - } - } - - /* In case of Coils, offset is now in the bit is the rest of previous convert */ - if (type & MODBUS_TYP_COILS) { - offset = (address - (tx->write.address + 1)) & 0x7; - value = (value >> offset) & 0x1; - } - } else { - /* It is not possible to define the value that is writing for Mask */ - /* Write Register function because the current content is not available.*/ - SCReturnInt(0); - } - - SCReturnInt(DetectEngineInspectModbusValueMatch(data, value, 0)); -} - -/** \internal - * - * \brief Do address inspection & validation for a signature - * - * \param tx Pointer to Modbus Transaction - * \param address Pointer to address signature structure to match - * \param access Access mode (READ or WRITE) - * - * \retval 0 no match or 1 match - */ -static int DetectEngineInspectModbusAddress(ModbusTransaction *tx, - DetectModbusValue *address, - uint8_t access) -{ - SCEnter(); - int ret = 0; - - /* Check if read/write address of request is at/in the address range of signature */ - if (access == MODBUS_TYP_READ) { - /* In the PDU Coils are addresses starting at zero */ - /* therefore Coils numbered 1-16 are addressed as 0-15 */ - ret = DetectEngineInspectModbusValueMatch(address, - tx->read.address + 1, - tx->read.quantity - 1); - } else { - /* In the PDU Registers are addresses starting at zero */ - /* therefore Registers numbered 1-16 are addressed as 0-15 */ - if (tx->type & MODBUS_TYP_SINGLE) - ret = DetectEngineInspectModbusValueMatch(address, - tx->write.address + 1, - 0); - else - ret = DetectEngineInspectModbusValueMatch(address, - tx->write.address + 1, - tx->write.quantity - 1); - } - - SCReturnInt(ret); -} - -/** \brief Do the content inspection & validation for a signature - * - * \param de_ctx Detection engine context - * \param det_ctx Detection engine thread context - * \param s Signature to inspect ( and sm: SigMatch to inspect) - * \param f Flow - * \param flags App layer flags - * \param alstate App layer state - * \param txv Pointer to Modbus Transaction structure - * - * \retval 0 no match or 1 match - */ -int DetectEngineInspectModbus(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, - const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, - uint8_t flags, void *alstate, void *txv, uint64_t tx_id) -{ - SCEnter(); - ModbusTransaction *tx = (ModbusTransaction *)txv; - DetectModbus *modbus = (DetectModbus *)engine->smd->ctx; - - int ret = 0; - - if (modbus == NULL) { - SCLogDebug("no modbus state, no match"); - SCReturnInt(0); - } - - if (modbus->unit_id != NULL) { - if (DetectEngineInspectModbusValueMatch(modbus->unit_id, tx->unit_id, 0) == 0) { - SCReturnInt(0); - } else { - ret = 1; - } - } - - if (modbus->type == MODBUS_TYP_NONE) { - if (modbus->category == MODBUS_CAT_NONE) { - if (modbus->function != MODBUS_FUNC_NONE) { - if (modbus->function == tx->function) { - if (modbus->has_subfunction) { - SCLogDebug("looking for Modbus server function %d and subfunction %d", - modbus->function, modbus->subfunction); - ret = (modbus->subfunction == (tx->subFunction))? 1 : 0; - } else { - SCLogDebug("looking for Modbus server function %d", modbus->function); - ret = 1; - } - } else { - ret = 0; - } - } - } else { - SCLogDebug("looking for Modbus category function %d", modbus->category); - ret = (tx->category & modbus->category)? 1 : 0; - } - } else { - uint8_t access = modbus->type & MODBUS_TYP_ACCESS_MASK; - uint8_t function = modbus->type & MODBUS_TYP_ACCESS_FUNCTION_MASK; - - if (access != MODBUS_TYP_NONE) { - if ((access & tx->type) && ((function == MODBUS_TYP_NONE) || (function & tx->type))) { - if (modbus->address != NULL) { - ret = DetectEngineInspectModbusAddress(tx, modbus->address, access); - - if (ret && (modbus->data != NULL)) { - ret = DetectEngineInspectModbusData(tx, modbus->address->min, modbus->data); - } - } else { - SCLogDebug("looking for Modbus access type %d and function type %d", access, function); - ret = 1; - } - } else { - ret = 0; - } - } - } - - SCReturnInt(ret); -} - #ifdef UNITTESTS /* UNITTESTS */ #include "app-layer-parser.h" @@ -396,8 +177,7 @@ static int DetectEngineInspectModbusTest01(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -467,8 +247,7 @@ static int DetectEngineInspectModbusTest02(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -539,8 +318,7 @@ static int DetectEngineInspectModbusTest03(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -610,8 +388,7 @@ static int DetectEngineInspectModbusTest04(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -681,8 +458,7 @@ static int DetectEngineInspectModbusTest05(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -752,8 +528,7 @@ static int DetectEngineInspectModbusTest06(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -823,8 +598,7 @@ static int DetectEngineInspectModbusTest07(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -942,8 +716,7 @@ static int DetectEngineInspectModbusTest08(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -1074,8 +847,7 @@ static int DetectEngineInspectModbusTest09(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -1196,8 +968,7 @@ static int DetectEngineInspectModbusTest10(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -1313,8 +1084,7 @@ static int DetectEngineInspectModbusTest11(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); @@ -1411,8 +1181,7 @@ static int DetectEngineInspectModbusTest12(void) FAIL_IF_NOT(r == 0); FLOWLOCK_UNLOCK(&f); - ModbusState *modbus_state = f.alstate; - FAIL_IF_NULL(modbus_state); + FAIL_IF_NULL(f.alstate); /* do detect */ SigMatchSignatures(&tv, de_ctx, det_ctx, p); diff --git a/src/detect-modbus.c b/src/detect-modbus.c index c4e3a5f083..0fe6d345f8 100644 --- a/src/detect-modbus.c +++ b/src/detect-modbus.c @@ -48,7 +48,6 @@ #include "detect-engine.h" #include "detect-modbus.h" -#include "detect-engine-modbus.h" #include "util-debug.h" #include "util-byte.h" @@ -56,29 +55,12 @@ #include "app-layer-modbus.h" #include "stream-tcp.h" - -/** - * \brief Regex for parsing the Modbus unit id string - */ -#define PARSE_REGEX_UNIT_ID "^\\s*\"?\\s*unit\\s+([<>]?\\d+)(<>\\d+)?(,\\s*(.*))?\\s*\"?\\s*$" -static DetectParseRegex unit_id_parse_regex; - -/** - * \brief Regex for parsing the Modbus function string - */ -#define PARSE_REGEX_FUNCTION "^\\s*\"?\\s*function\\s*(!?[A-z0-9]+)(,\\s*subfunction\\s+(\\d+))?\\s*\"?\\s*$" -static DetectParseRegex function_parse_regex; - -/** - * \brief Regex for parsing the Modbus access string - */ -#define PARSE_REGEX_ACCESS "^\\s*\"?\\s*access\\s*(read|write)\\s*(discretes|coils|input|holding)?(,\\s*address\\s+([<>]?\\d+)(<>\\d+)?(,\\s*value\\s+([<>]?\\d+)(<>\\d+)?)?)?\\s*\"?\\s*$" -static DetectParseRegex access_parse_regex; +#include "rust.h" static int g_modbus_buffer_id = 0; #ifdef UNITTESTS -void DetectModbusRegisterTests(void); +static void DetectModbusRegisterTests(void); #endif /** \internal @@ -89,413 +71,12 @@ void DetectModbusRegisterTests(void); */ static void DetectModbusFree(DetectEngineCtx *de_ctx, void *ptr) { SCEnter(); - DetectModbus *modbus = (DetectModbus *) ptr; - - if(modbus) { - if (modbus->unit_id) - SCFree(modbus->unit_id); - - if (modbus->address) - SCFree(modbus->address); - - if (modbus->data) - SCFree(modbus->data); - - SCFree(modbus); - } -} - -/** \internal - * - * \brief This function is used to parse Modbus parameters in access mode - * - * \param de_ctx Pointer to the detection engine context - * \param str Pointer to the user provided id option - * - * \retval Pointer to DetectModbusData on success or NULL on failure - */ -static DetectModbus *DetectModbusAccessParse(DetectEngineCtx *de_ctx, const char *str) -{ - SCEnter(); - DetectModbus *modbus = NULL; - - char arg[MAX_SUBSTRINGS]; - int ov[MAX_SUBSTRINGS], ret, res; - - ret = DetectParsePcreExec(&access_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS); - if (ret < 1) - goto error; - - res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - /* We have a correct Modbus option */ - modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus)); - if (unlikely(modbus == NULL)) - goto error; - - if (strcmp(arg, "read") == 0) - modbus->type = MODBUS_TYP_READ; - else if (strcmp(arg, "write") == 0) - modbus->type = MODBUS_TYP_WRITE; - else - goto error; - - if (ret > 2) { - res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 2, arg, MAX_SUBSTRINGS); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - if (*arg != '\0') { - if (strcmp(arg, "discretes") == 0) { - if (modbus->type == MODBUS_TYP_WRITE) - /* Discrete access is only read access. */ - goto error; - - modbus->type |= MODBUS_TYP_DISCRETES; - } - else if (strcmp(arg, "coils") == 0) { - modbus->type |= MODBUS_TYP_COILS; - } - else if (strcmp(arg, "input") == 0) { - if (modbus->type == MODBUS_TYP_WRITE) { - /* Input access is only read access. */ - goto error; - } - - modbus->type |= MODBUS_TYP_INPUT; - } - else if (strcmp(arg, "holding") == 0) { - modbus->type |= MODBUS_TYP_HOLDING; - } - else - goto error; - } - - if (ret > 4) { - res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 4, arg, MAX_SUBSTRINGS); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - /* We have a correct address option */ - modbus->address = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue)); - if (unlikely(modbus->address == NULL)) - goto error; - - uint8_t idx; - if (arg[0] == '>') { - idx = 1; - modbus->address->mode = DETECT_MODBUS_GT; - } else if (arg[0] == '<') { - idx = 1; - modbus->address->mode = DETECT_MODBUS_LT; - } else { - idx = 0; - } - if (StringParseUint16(&modbus->address->min, 10, 0, - (const char *) (arg + idx)) < 0) { - SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for min " - "address: %s", (const char *)(arg + idx)); - goto error; - } - SCLogDebug("and min/equal address %d", modbus->address->min); - - if (ret > 5) { - res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 5, arg, MAX_SUBSTRINGS); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - if (*arg != '\0') { - if (StringParseUint16(&modbus->address->max, 10, 0, - (const char*) (arg + 2)) < 0) { - SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for max " - "address: %s", (const char*)(arg + 2)); - goto error; - } - modbus->address->mode = DETECT_MODBUS_RA; - SCLogDebug("and max address %d", modbus->address->max); - } - - if (ret > 7) { - res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 7, arg, MAX_SUBSTRINGS); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - if (modbus->address->mode != DETECT_MODBUS_EQ) { - SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords (address range and value)."); - goto error; - } - - /* We have a correct address option */ - if (modbus->type == MODBUS_TYP_READ) - /* Value access is only possible in write access. */ - goto error; - - modbus->data = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue)); - if (unlikely(modbus->data == NULL)) - goto error; - - - uint8_t idx_mode; - if (arg[0] == '>') { - idx_mode = 1; - modbus->data->mode = DETECT_MODBUS_GT; - } else if (arg[0] == '<') { - idx_mode = 1; - modbus->data->mode = DETECT_MODBUS_LT; - } else { - idx_mode = 0; - } - if (StringParseUint16(&modbus->data->min, 10, 0, - (const char*) (arg + idx_mode)) < 0) { - SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for " - "min data: %s", (const char*)(arg + idx_mode)); - goto error; - } - SCLogDebug("and min/equal value %d", modbus->data->min); - - if (ret > 8) { - res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 8, arg, MAX_SUBSTRINGS); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - if (*arg != '\0') { - if (StringParseUint16(&modbus->data->max, - 10, 0, (const char*) (arg + 2)) < 0) { - SCLogError(SC_ERR_INVALID_VALUE, - "Invalid value for max data: %s", - (const char*)(arg + 2)); - goto error; - } - modbus->data->mode = DETECT_MODBUS_RA; - SCLogDebug("and max value %d", modbus->data->max); - } - } - } - } - } - } - - SCReturnPtr(modbus, "DetectModbusAccess"); - -error: - if (modbus != NULL) - DetectModbusFree(de_ctx, modbus); - - SCReturnPtr(NULL, "DetectModbus"); -} - -/** \internal - * - * \brief This function is used to parse Modbus parameters in function mode - * - * \param str Pointer to the user provided id option - * - * \retval id_d pointer to DetectModbusData on success - * \retval NULL on failure - */ -static DetectModbus *DetectModbusFunctionParse(DetectEngineCtx *de_ctx, const char *str) -{ - SCEnter(); - DetectModbus *modbus = NULL; - - char arg[MAX_SUBSTRINGS], *ptr = arg; - int ov[MAX_SUBSTRINGS], res, ret; - - ret = DetectParsePcreExec(&function_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS); - if (ret < 1) - goto error; - - res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - /* We have a correct Modbus function option */ - modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus)); - if (unlikely(modbus == NULL)) - goto error; - - if (isdigit((unsigned char)ptr[0])) { - if (StringParseUint8(&modbus->function, 10, 0, (const char *)ptr) < 0) { - SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for " - "modbus function: %s", (const char *)ptr); - goto error; - } - /* Function code 0 is managed by decoder_event INVALID_FUNCTION_CODE */ - if (modbus->function == MODBUS_FUNC_NONE) { - SCLogError(SC_ERR_INVALID_SIGNATURE, - "Invalid argument \"%d\" supplied to modbus function keyword.", - modbus->function); - goto error; - } - - SCLogDebug("will look for modbus function %d", modbus->function); - - if (ret > 2) { - res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 3, arg, MAX_SUBSTRINGS); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - if (StringParseUint16(&modbus->subfunction, 10, 0, (const char *)arg) < 0) { - SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for " - "modbus subfunction: %s", (const char*)arg); - goto error; - } - modbus->has_subfunction = true; - - SCLogDebug("and subfunction %d", modbus->subfunction); - } - } else { - uint8_t neg = 0; - - if (ptr[0] == '!') { - neg = 1; - ptr++; - } - - if (strcmp("assigned", ptr) == 0) - modbus->category = MODBUS_CAT_PUBLIC_ASSIGNED; - else if (strcmp("unassigned", ptr) == 0) - modbus->category = MODBUS_CAT_PUBLIC_UNASSIGNED; - else if (strcmp("public", ptr) == 0) - modbus->category = MODBUS_CAT_PUBLIC_ASSIGNED | MODBUS_CAT_PUBLIC_UNASSIGNED; - else if (strcmp("user", ptr) == 0) - modbus->category = MODBUS_CAT_USER_DEFINED; - else if (strcmp("reserved", ptr) == 0) - modbus->category = MODBUS_CAT_RESERVED; - else if (strcmp("all", ptr) == 0) - modbus->category = MODBUS_CAT_ALL; - - if (neg) - modbus->category = ~modbus->category; - SCLogDebug("will look for modbus category function %d", modbus->category); - } - - SCReturnPtr(modbus, "DetectModbusFunction"); - -error: - if (modbus != NULL) - DetectModbusFree(de_ctx, modbus); - - SCReturnPtr(NULL, "DetectModbus"); -} - -/** \internal - * - * \brief This function is used to parse Modbus parameters in unit id mode - * - * \param str Pointer to the user provided id option - * - * \retval Pointer to DetectModbusUnit on success or NULL on failure - */ -static DetectModbus *DetectModbusUnitIdParse(DetectEngineCtx *de_ctx, const char *str) -{ - SCEnter(); - DetectModbus *modbus = NULL; - - char arg[MAX_SUBSTRINGS]; - int ov[MAX_SUBSTRINGS], ret, res; - - ret = DetectParsePcreExec(&unit_id_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS); - if (ret < 1) - goto error; - - res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - if (ret > 3) { - /* We have more Modbus option */ - const char *str_ptr; - - res = pcre_get_substring((char *)str, ov, MAX_SUBSTRINGS, 4, &str_ptr); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - if ((modbus = DetectModbusFunctionParse(de_ctx, str_ptr)) == NULL) { - if ((modbus = DetectModbusAccessParse(de_ctx, str_ptr)) == NULL) { - SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option"); - goto error; - } - } - } else { - /* We have only unit id Modbus option */ - modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus)); - if (unlikely(modbus == NULL)) - goto error; - } - - /* We have a correct unit id option */ - modbus->unit_id = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue)); - if (unlikely(modbus->unit_id == NULL)) - goto error; - - uint8_t idx; - if (arg[0] == '>') { - idx = 1; - modbus->unit_id->mode = DETECT_MODBUS_GT; - } else if (arg[0] == '<') { - idx = 1; - modbus->unit_id->mode = DETECT_MODBUS_LT; - } else { - idx = 0; - } - if (StringParseUint16(&modbus->unit_id->min, 10, 0, (const char *) (arg + idx)) < 0) { - SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for " - "modbus min unit id: %s", (const char*)(arg + idx)); - goto error; - } - SCLogDebug("and min/equal unit id %d", modbus->unit_id->min); - - if (ret > 2) { - res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 2, arg, MAX_SUBSTRINGS); - if (res < 0) { - SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); - goto error; - } - - if (*arg != '\0') { - if (StringParseUint16(&modbus->unit_id->max, 10, 0, (const char *) (arg + 2)) < 0) { - SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for " - "modbus max unit id: %s", (const char*)(arg + 2)); - goto error; - } - modbus->unit_id->mode = DETECT_MODBUS_RA; - SCLogDebug("and max unit id %d", modbus->unit_id->max); - } + if (ptr != NULL) { + rs_modbus_free(ptr); } - - SCReturnPtr(modbus, "DetectModbusUnitId"); - -error: - if (modbus != NULL) - DetectModbusFree(de_ctx, modbus); - - SCReturnPtr(NULL, "DetectModbus"); + SCReturn; } - /** \internal * * \brief this function is used to add the parsed "id" option into the current signature @@ -509,19 +90,15 @@ error: static int DetectModbusSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) { SCEnter(); - DetectModbus *modbus = NULL; + DetectModbusRust *modbus = NULL; SigMatch *sm = NULL; if (DetectSignatureSetAppProto(s, ALPROTO_MODBUS) != 0) return -1; - if ((modbus = DetectModbusUnitIdParse(de_ctx, str)) == NULL) { - if ((modbus = DetectModbusFunctionParse(de_ctx, str)) == NULL) { - if ((modbus = DetectModbusAccessParse(de_ctx, str)) == NULL) { - SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option"); - goto error; - } - } + if ((modbus = rs_modbus_parse(str)) == NULL) { + SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option"); + goto error; } /* Okay so far so good, lets get this into a SigMatch and put it in the Signature. */ @@ -544,24 +121,47 @@ error: SCReturnInt(-1); } +static int DetectModbusMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state, + void *txv, const Signature *s, const SigMatchCtx *ctx) +{ + return rs_modbus_inspect(txv, (void *)ctx); +} + +/** \brief Do the content inspection & validation for a signature + * + * \param de_ctx Detection engine context + * \param det_ctx Detection engine thread context + * \param s Signature to inspect ( and sm: SigMatch to inspect) + * \param f Flow + * \param flags App layer flags + * \param alstate App layer state + * \param txv Pointer to Modbus Transaction structure + * + * \retval 0 no match or 1 match + */ +static int DetectEngineInspectModbus(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, + uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList( + de_ctx, det_ctx, s, engine->smd, f, flags, alstate, txv, tx_id); +} + /** * \brief Registration function for Modbus keyword */ void DetectModbusRegister(void) { - SCEnter(); - sigmatch_table[DETECT_AL_MODBUS].name = "modbus"; - sigmatch_table[DETECT_AL_MODBUS].desc = "match on various properties of Modbus requests"; - sigmatch_table[DETECT_AL_MODBUS].url = "/rules/modbus-keyword.html#modbus-keyword"; - sigmatch_table[DETECT_AL_MODBUS].Match = NULL; - sigmatch_table[DETECT_AL_MODBUS].Setup = DetectModbusSetup; - sigmatch_table[DETECT_AL_MODBUS].Free = DetectModbusFree; + sigmatch_table[DETECT_AL_MODBUS].name = "modbus"; + sigmatch_table[DETECT_AL_MODBUS].desc = "match on various properties of Modbus requests"; + sigmatch_table[DETECT_AL_MODBUS].url = "/rules/modbus-keyword.html#modbus-keyword"; + sigmatch_table[DETECT_AL_MODBUS].Match = NULL; + sigmatch_table[DETECT_AL_MODBUS].Setup = DetectModbusSetup; + sigmatch_table[DETECT_AL_MODBUS].Free = DetectModbusFree; + sigmatch_table[DETECT_AL_MODBUS].AppLayerTxMatch = DetectModbusMatch; #ifdef UNITTESTS sigmatch_table[DETECT_AL_MODBUS].RegisterTests = DetectModbusRegisterTests; #endif - DetectSetupParseRegexes(PARSE_REGEX_UNIT_ID, &unit_id_parse_regex); - DetectSetupParseRegexes(PARSE_REGEX_FUNCTION, &function_parse_regex); - DetectSetupParseRegexes(PARSE_REGEX_ACCESS, &access_parse_regex); DetectAppLayerInspectEngineRegister2( "modbus", ALPROTO_MODBUS, SIG_FLAG_TOSERVER, 0, DetectEngineInspectModbus, NULL); @@ -572,6 +172,72 @@ void DetectModbusRegister(void) #ifdef UNITTESTS /* UNITTESTS */ #include "util-unittest.h" +/** Convert rust structure to C for regression tests. + * + * Note: Newly allocated `DetectModbus` structure must be freed. + * + * TODO: remove this after regression testing commit. + */ +static DetectModbusValue *DetectModbusValueRustToC(uint16_t min, uint16_t max) +{ + DetectModbusValue *value = SCMalloc(sizeof(*value)); + FAIL_IF_NULL(value); + + value->min = min; + value->max = max; + + if (min == max) { + value->mode = DETECT_MODBUS_EQ; + } else if (min == 0) { + value->mode = DETECT_MODBUS_LT; + } else if (max == UINT16_MAX) { + value->mode = DETECT_MODBUS_GT; + } else { + value->mode = DETECT_MODBUS_RA; + } + + return value; +} + +static DetectModbus *DetectModbusRustToC(DetectModbusRust *ctx) +{ + DetectModbus *modbus = SCMalloc(sizeof(*modbus)); + FAIL_IF_NULL(modbus); + + modbus->category = rs_modbus_get_category(ctx); + modbus->function = rs_modbus_get_function(ctx); + modbus->subfunction = rs_modbus_get_subfunction(ctx); + modbus->has_subfunction = rs_modbus_get_has_subfunction(ctx); + modbus->type = rs_modbus_get_access_type(ctx); + + modbus->unit_id = DetectModbusValueRustToC( + rs_modbus_get_unit_id_min(ctx), rs_modbus_get_unit_id_max(ctx)); + + modbus->address = DetectModbusValueRustToC( + rs_modbus_get_address_min(ctx), rs_modbus_get_address_max(ctx)); + + modbus->data = + DetectModbusValueRustToC(rs_modbus_get_data_min(ctx), rs_modbus_get_data_max(ctx)); + + return modbus; +} + +static void DetectModbusCFree(DetectModbus *modbus) +{ + if (modbus) { + if (modbus->unit_id) + SCFree(modbus->unit_id); + + if (modbus->address) + SCFree(modbus->address); + + if (modbus->data) + SCFree(modbus->data); + + SCFree(modbus); + } +} + /** \test Signature containing a function. */ static int DetectModbusTest01(void) { @@ -590,10 +256,12 @@ static int DetectModbusTest01(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT(modbus->function == 1); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -618,11 +286,13 @@ static int DetectModbusTest02(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT(modbus->function == 8); FAIL_IF_NOT(modbus->subfunction == 4); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -647,10 +317,12 @@ static int DetectModbusTest03(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT(modbus->category == MODBUS_CAT_RESERVED); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -663,8 +335,6 @@ static int DetectModbusTest04(void) DetectEngineCtx *de_ctx = NULL; DetectModbus *modbus = NULL; - uint8_t category = ~MODBUS_CAT_PUBLIC_ASSIGNED; - de_ctx = DetectEngineCtxInit(); FAIL_IF_NULL(de_ctx); @@ -677,10 +347,15 @@ static int DetectModbusTest04(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - FAIL_IF_NOT(modbus->category == category); + FAIL_IF(modbus->category & MODBUS_CAT_PUBLIC_ASSIGNED); + FAIL_IF_NOT(modbus->category & MODBUS_CAT_PUBLIC_UNASSIGNED); + FAIL_IF_NOT(modbus->category & MODBUS_CAT_USER_DEFINED); + FAIL_IF_NOT(modbus->category & MODBUS_CAT_RESERVED); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -705,10 +380,12 @@ static int DetectModbusTest05(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT(modbus->type == MODBUS_TYP_READ); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -735,10 +412,12 @@ static int DetectModbusTest06(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT(modbus->type == type); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -766,12 +445,14 @@ static int DetectModbusTest07(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT(modbus->type == type); FAIL_IF_NOT((*modbus->address).mode == mode); FAIL_IF_NOT((*modbus->address).min == 1000); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -799,12 +480,14 @@ static int DetectModbusTest08(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT(modbus->type == type); FAIL_IF_NOT((*modbus->address).mode == mode); FAIL_IF_NOT((*modbus->address).min == 500); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -833,7 +516,8 @@ static int DetectModbusTest09(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT(modbus->type == type); FAIL_IF_NOT((*modbus->address).mode == addressMode); @@ -842,6 +526,7 @@ static int DetectModbusTest09(void) FAIL_IF_NOT((*modbus->data).min == 500); FAIL_IF_NOT((*modbus->data).max == 1000); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -867,11 +552,13 @@ static int DetectModbusTest10(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT((*modbus->unit_id).min == 10); FAIL_IF_NOT((*modbus->unit_id).mode == mode); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -897,13 +584,15 @@ static int DetectModbusTest11(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT((*modbus->unit_id).min == 10); FAIL_IF_NOT((*modbus->unit_id).mode == mode); FAIL_IF_NOT(modbus->function == 8); FAIL_IF_NOT(modbus->subfunction == 4); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -931,7 +620,8 @@ static int DetectModbusTest12(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT((*modbus->unit_id).min == 10); FAIL_IF_NOT((*modbus->unit_id).mode == mode); @@ -939,6 +629,7 @@ static int DetectModbusTest12(void) FAIL_IF_NOT((*modbus->address).mode == mode); FAIL_IF_NOT((*modbus->address).min == 1000); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); @@ -964,12 +655,14 @@ static int DetectModbusTest13(void) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]); FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); - modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; + modbus = DetectModbusRustToC( + (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx); FAIL_IF_NOT((*modbus->unit_id).min == 10); FAIL_IF_NOT((*modbus->unit_id).max == 500); FAIL_IF_NOT((*modbus->unit_id).mode == mode); + DetectModbusCFree(modbus); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineCtxFree(de_ctx); diff --git a/src/suricata.c b/src/suricata.c index 1150b4861d..bcae579af7 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -119,7 +119,6 @@ #include "app-layer-ssh.h" #include "app-layer-ftp.h" #include "app-layer-smtp.h" -#include "app-layer-modbus.h" #include "app-layer-enip.h" #include "app-layer-dnp3.h" #include "app-layer-smb.h" -- 2.47.2