]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
ssh: limit length for banner logs
authorPhilippe Antoine <pantoine@oisf.net>
Mon, 19 Feb 2024 15:57:55 +0000 (16:57 +0100)
committerVictor Julien <vjulien@oisf.net>
Tue, 19 Mar 2024 09:40:16 +0000 (10:40 +0100)
Ticket: 6770
(cherry picked from commit c4b8fb7aca482d1a1555e27072ca26896b52a480)

rust/src/jsonbuilder.rs
rust/src/ssh/logger.rs
rust/src/ssh/ssh.rs

index 7f8569224641a0acb9e716582adcf8208c270df9..cf238d3f828bdf77bdd351b85b06b4f96476b6d1 100644 (file)
@@ -17,6 +17,7 @@
 
 #![allow(clippy::missing_safety_doc)]
 
+use std::collections::TryReserveError;
 use std::ffi::CStr;
 use std::os::raw::c_char;
 use std::str::Utf8Error;
@@ -27,6 +28,7 @@ const INIT_SIZE: usize = 4096;
 pub enum JsonError {
     InvalidState,
     Utf8Error(Utf8Error),
+    Memory,
 }
 
 impl std::error::Error for JsonError {}
@@ -36,10 +38,17 @@ impl std::fmt::Display for JsonError {
         match self {
             JsonError::InvalidState => write!(f, "invalid state"),
             JsonError::Utf8Error(ref e) => e.fmt(f),
+            JsonError::Memory => write!(f, "memory error"),
         }
     }
 }
 
+impl From<TryReserveError> for JsonError {
+    fn from(_: TryReserveError) -> Self {
+        JsonError::Memory
+    }
+}
+
 impl From<Utf8Error> for JsonError {
     fn from(e: Utf8Error) -> Self {
         JsonError::Utf8Error(e)
@@ -427,6 +436,22 @@ impl JsonBuilder {
         }
     }
 
+    /// Set a key and a string value (from bytes) on an object, with a limited size
+    pub fn set_string_from_bytes_limited(&mut self, key: &str, val: &[u8], limit: usize) -> Result<&mut Self, JsonError> {
+        let mut valtrunc = Vec::new();
+        let val = if val.len() > limit {
+            valtrunc.extend_from_slice(&val[..limit]);
+            valtrunc.extend_from_slice(b"[truncated]");
+            &valtrunc
+        } else {
+            val
+        };
+        match std::str::from_utf8(val) {
+            Ok(s) => self.set_string(key, s),
+            Err(_) => self.set_string(key, &try_string_from_bytes(val)?),
+        }
+    }
+
     /// Set a key and an unsigned integer type on an object.
     pub fn set_uint(&mut self, key: &str, val: u64) -> Result<&mut Self, JsonError> {
         match self.current_state() {
@@ -570,6 +595,26 @@ fn string_from_bytes(input: &[u8]) -> String {
     return out;
 }
 
+/// A Suricata specific function to create a string from bytes when UTF-8 decoding fails.
+///
+/// For bytes over 0x0f, we encode as hex like "\xf2".
+fn try_string_from_bytes(input: &[u8]) -> Result<String, JsonError> {
+    let mut out = String::new();
+
+    // Allocate enough data to handle the worst case scenario of every
+    // byte needing to be presented as a byte.
+    out.try_reserve(input.len() * 4)?;
+
+    for b in input.iter() {
+        if *b < 128 {
+            out.push(*b as char);
+        } else {
+            out.push_str(&format!("\\x{:02x}", *b));
+        }
+    }
+    return Ok(out);
+}
+
 #[no_mangle]
 pub extern "C" fn jb_new_object() -> *mut JsonBuilder {
     let boxed = Box::new(JsonBuilder::new_object());
index 7f5965146373eb375e554eb6a914ab6664fb9658..7af0ed80cc8f9fc7cbcd67c838520442cc3ffb04 100644 (file)
@@ -15,7 +15,7 @@
  * 02110-1301, USA.
  */
 
-use super::ssh::SSHTransaction;
+use super::ssh::{SSHTransaction, SSH_MAX_BANNER_LEN};
 use crate::jsonbuilder::{JsonBuilder, JsonError};
 
 fn log_ssh(tx: &SSHTransaction, js: &mut JsonBuilder) -> Result<bool, JsonError> {
@@ -24,9 +24,9 @@ fn log_ssh(tx: &SSHTransaction, js: &mut JsonBuilder) -> Result<bool, JsonError>
     }
     if tx.cli_hdr.protover.len() > 0 {
         js.open_object("client")?;
-        js.set_string_from_bytes("proto_version", &tx.cli_hdr.protover)?;
-        if tx.cli_hdr.swver.len() > 0 {
-            js.set_string_from_bytes("software_version", &tx.cli_hdr.swver)?;
+        js.set_string_from_bytes_limited("proto_version", &tx.cli_hdr.protover, SSH_MAX_BANNER_LEN)?;
+        if !tx.cli_hdr.swver.is_empty() {
+            js.set_string_from_bytes_limited("software_version", &tx.cli_hdr.swver, SSH_MAX_BANNER_LEN)?;
         }
         if tx.cli_hdr.hassh.len() > 0 || tx.cli_hdr.hassh_string.len() > 0 {
             js.open_object("hassh")?;
@@ -42,9 +42,9 @@ fn log_ssh(tx: &SSHTransaction, js: &mut JsonBuilder) -> Result<bool, JsonError>
     }
     if tx.srv_hdr.protover.len() > 0 {
         js.open_object("server")?;
-        js.set_string_from_bytes("proto_version", &tx.srv_hdr.protover)?;
-        if tx.srv_hdr.swver.len() > 0 {
-            js.set_string_from_bytes("software_version", &tx.srv_hdr.swver)?;
+        js.set_string_from_bytes_limited("proto_version", &tx.srv_hdr.protover, SSH_MAX_BANNER_LEN)?;
+        if !tx.srv_hdr.swver.is_empty() {
+            js.set_string_from_bytes_limited("software_version", &tx.srv_hdr.swver, SSH_MAX_BANNER_LEN)?;
         }
         if tx.srv_hdr.hassh.len() > 0 || tx.srv_hdr.hassh_string.len() > 0 {
             js.open_object("hassh")?;
index ec8368d7ce5d8a45212de30da005d9c647c356fd..b57fbf3fab55fd6c55d5dfb335d4157e7e6c4577 100644 (file)
@@ -59,7 +59,7 @@ pub enum SSHConnectionState {
     SshStateFinished = 3,
 }
 
-const SSH_MAX_BANNER_LEN: usize = 256;
+pub const SSH_MAX_BANNER_LEN: usize = 256;
 const SSH_RECORD_HEADER_LEN: usize = 6;
 const SSH_MAX_REASSEMBLED_RECORD_LEN: usize = 65535;