]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
pgsql: allow multi-request transactions
authorJuliana Fajardini <jufajardini@oisf.net>
Thu, 1 May 2025 00:48:07 +0000 (21:48 -0300)
committerJuliana Fajardini <jufajardini@oisf.net>
Wed, 4 Jun 2025 18:21:32 +0000 (15:21 -0300)
Important for CopyIn mode/ subprotocol, where the frontend is the one
sending 0 or more messages to the backend as part of a transaction.

Related to
Task #7645

rust/src/pgsql/detect.rs
rust/src/pgsql/logger.rs
rust/src/pgsql/pgsql.rs

index de58f8ae3ea6735e58a4d7d229d39e1ad3f10bf9..530bfeee09e8fad77a68412c1e522863eab8b589 100644 (file)
@@ -46,10 +46,12 @@ unsafe extern "C" fn pgsql_detect_query_get_data(
 ) -> bool {
     let tx = cast_pointer!(tx, PgsqlTransaction);
 
-    if let Some(PgsqlFEMessage::SimpleQuery(ref query)) = &tx.request {
-        *buffer = query.payload.as_ptr();
-        *buffer_len = query.payload.len() as u32;
-        return true;
+    for request in &tx.requests {
+        if let PgsqlFEMessage::SimpleQuery(ref query) = request {
+            *buffer = query.payload.as_ptr();
+            *buffer_len = query.payload.len() as u32;
+            return true;
+        }
     }
 
     *buffer = std::ptr::null();
index c1020ecf1d70396b8256a2fe6df6142d2e00784d..a714603a85dcec1faffaaa1a50e600b09bc9ed6e 100644 (file)
@@ -29,8 +29,15 @@ pub const PGSQL_LOG_PASSWORDS: u32 = BIT_U32!(0);
 fn log_pgsql(tx: &PgsqlTransaction, flags: u32, js: &mut JsonBuilder) -> Result<(), JsonError> {
     js.open_object("pgsql")?;
     js.set_uint("tx_id", tx.tx_id)?;
-    if let Some(request) = &tx.request {
-        js.set_object("request", &log_request(request, flags)?)?;
+    if !tx.requests.is_empty() {
+        // For now, even if 'requests' is an array, we don't need to log it as such, as
+        // there are no duplicated messages
+        js.open_object("request")?;
+        for request in &tx.requests {
+            SCLogNotice!("Suricata requests length: {}", tx.requests.len());
+            log_request(request, flags, js)?;
+        }
+        js.close()?;
     } else if tx.responses.is_empty() {
         SCLogDebug!("Suricata created an empty PGSQL transaction");
         // TODO Log anomaly event?
@@ -48,8 +55,7 @@ fn log_pgsql(tx: &PgsqlTransaction, flags: u32, js: &mut JsonBuilder) -> Result<
     Ok(())
 }
 
-fn log_request(req: &PgsqlFEMessage, flags: u32) -> Result<JsonBuilder, JsonError> {
-    let mut js = JsonBuilder::try_new_object()?;
+fn log_request(req: &PgsqlFEMessage, flags: u32, js: &mut JsonBuilder) -> Result<(), JsonError> {
     match req {
         PgsqlFEMessage::StartupMessage(StartupPacket {
             length: _,
@@ -118,8 +124,7 @@ fn log_request(req: &PgsqlFEMessage, flags: u32) -> Result<JsonBuilder, JsonErro
             // We don't want to log these, for now. Cf redmine: #6576
         }
     }
-    js.close()?;
-    Ok(js)
+    Ok(())
 }
 
 fn log_response_object(tx: &PgsqlTransaction) -> Result<JsonBuilder, JsonError> {
index eba26cbebd548a5c2cda455a2a5d5886005b3f84..d569069350bae94303bc2d9451097e854188b2b3 100644 (file)
@@ -60,7 +60,7 @@ pub struct PgsqlTransaction {
     pub tx_id: u64,
     pub tx_req_state: PgsqlTxProgress,
     pub tx_res_state: PgsqlTxProgress,
-    pub request: Option<PgsqlFEMessage>,
+    pub requests: Vec<PgsqlFEMessage>,
     pub responses: Vec<PgsqlBEMessage>,
 
     pub data_row_cnt: u64,
@@ -87,7 +87,7 @@ impl PgsqlTransaction {
             tx_id: 0,
             tx_req_state: PgsqlTxProgress::TxInit,
             tx_res_state: PgsqlTxProgress::TxInit,
-            request: None,
+            requests: Vec::<PgsqlFEMessage>::new(),
             responses: Vec::<PgsqlBEMessage>::new(),
             data_row_cnt: 0,
             data_size: 0,
@@ -380,7 +380,7 @@ impl PgsqlState {
                     // https://samadhiweb.com/blog/2013.04.28.graphviz.postgresv3.html
                     if let Some(tx) = self.find_or_create_tx() {
                         tx.tx_data.updated_ts = true;
-                        tx.request = Some(request);
+                        tx.requests.push(request);
                         if let Some(state) = new_state {
                             if Self::request_is_complete(state) {
                                 // The request is always complete at this point