diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index 831c59e558..2bc934213c 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -1721,7 +1721,7 @@ incompatible with ``decode-mime``. If both are enabled, Maximum transactions ~~~~~~~~~~~~~~~~~~~~ -MQTT, FTP, and NFS have each a `max-tx` parameter that can be customized. +MQTT, FTP, PostgreSQL and NFS have each a `max-tx` parameter that can be customized. `max-tx` refers to the maximum number of live transactions for each flow. An app-layer event `protocol.too_many_transactions` is triggered when this value is reached. The point of this parameter is to find a balance between the completeness of analysis diff --git a/rust/src/pgsql/pgsql.rs b/rust/src/pgsql/pgsql.rs index 24dd2e1f67..daf6111742 100644 --- a/rust/src/pgsql/pgsql.rs +++ b/rust/src/pgsql/pgsql.rs @@ -32,12 +32,15 @@ pub const PGSQL_CONFIG_DEFAULT_STREAM_DEPTH: u32 = 0; static mut ALPROTO_PGSQL: AppProto = ALPROTO_UNKNOWN; +static mut PGSQL_MAX_TX: usize = 1024; + #[repr(u8)] #[derive(Copy, Clone, PartialOrd, PartialEq, Debug)] pub enum PgsqlTransactionState { Init = 0, RequestReceived, ResponseDone, + FlushedOut, } #[derive(Debug)] @@ -123,6 +126,7 @@ pub struct PgsqlState { backend_secret_key: u32, backend_pid: u32, state_progress: PgsqlStateProgress, + tx_index_completed: usize, } impl State for PgsqlState { @@ -145,6 +149,7 @@ impl PgsqlState { backend_secret_key: 0, backend_pid: 0, state_progress: PgsqlStateProgress::IdleState, + tx_index_completed: 0, } } @@ -162,6 +167,7 @@ impl PgsqlState { } } if found { + self.tx_index_completed = 0; self.transactions.remove(index); } } @@ -180,6 +186,21 @@ impl PgsqlState { self.tx_id += 1; tx.tx_id = self.tx_id; SCLogDebug!("Creating new transaction. tx_id: {}", tx.tx_id); + if self.transactions.len() > unsafe { PGSQL_MAX_TX } + self.tx_index_completed { + // If there are too many open transactions, + // mark the earliest ones as completed, and take care + // to avoid quadratic complexity + let mut index = self.tx_index_completed; + for tx_old in &mut self.transactions.range_mut(self.tx_index_completed..) { + index = index + 1; + if tx_old.tx_state < PgsqlTransactionState::ResponseDone { + tx_old.tx_state = PgsqlTransactionState::FlushedOut; + //TODO set event + break; + } + } + self.tx_index_completed = index; + } return tx; } @@ -738,6 +759,13 @@ pub unsafe extern "C" fn rs_pgsql_register_parser() { } AppLayerParserSetStreamDepth(IPPROTO_TCP as u8, ALPROTO_PGSQL, stream_depth) } + if let Some(val) = conf_get("app-layer.protocols.pgsql.max-tx") { + if let Ok(v) = val.parse::() { + PGSQL_MAX_TX = v; + } else { + SCLogError!("Invalid value for pgsql.max-tx"); + } + } } else { SCLogDebug!("Protocol detector and parser disabled for PGSQL."); } diff --git a/suricata.yaml.in b/suricata.yaml.in index bb8712cd20..2745fd93af 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -835,6 +835,8 @@ app-layer: enabled: no # Stream reassembly size for PostgreSQL. By default, track it completely. stream-depth: 0 + # Maximum number of live PostgreSQL transactions per flow + # max-tx: 1024 dcerpc: enabled: yes ftp: