]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
rust: utility function to copy Rust strings to C strings
authorJason Ish <jason.ish@oisf.net>
Fri, 27 Jan 2023 06:58:58 +0000 (00:58 -0600)
committerJason Ish <jason.ish@oisf.net>
Sun, 29 Jan 2023 21:38:20 +0000 (15:38 -0600)
As there are a few places where a Rust string is copied into a provided
C string buffer, create a utility function to take care of these
details.

rust/src/ffi/hashing.rs
rust/src/ffi/mod.rs
rust/src/ffi/strings.rs [new file with mode: 0644]
rust/src/x509/time.rs

index 89205cb4547510eee89446d537a062945fc9129b..d5c247ddc300d1d74c639e08f716a5b01f44fb0d 100644 (file)
@@ -24,8 +24,9 @@ use std::os::raw::c_char;
 pub const SC_SHA1_LEN: usize = 20;
 pub const SC_SHA256_LEN: usize = 32;
 
-// Length of a MD5 hex string, not including a trailing NUL.
+// Length of hex digests without trailing NUL.
 pub const SC_MD5_HEX_LEN: usize = 32;
+pub const SC_SHA256_HEX_LEN: usize = 64;
 
 // Wrap the Rust Sha256 in a new type named SCSha256 to give this type
 // the "SC" prefix. The one drawback is we must access the actual context
@@ -59,17 +60,10 @@ pub unsafe extern "C" fn SCSha256Finalize(hasher: &mut SCSha256, out: *mut u8, l
 /// did in C using NSS.
 #[no_mangle]
 pub unsafe extern "C" fn SCSha256FinalizeToHex(hasher: &mut SCSha256, out: *mut c_char, len: u32) {
-    let out = &mut *(out as *mut u8);
     let hasher: Box<SCSha256> = Box::from_raw(hasher);
     let result = hasher.0.finalize();
     let hex = format!("{:x}", &result);
-    let output = std::slice::from_raw_parts_mut(out, len as usize);
-
-    // This will panic if the sizes differ.
-    output[0..len as usize - 1].copy_from_slice(hex.as_bytes());
-
-    // Terminate the string.
-    output[output.len() - 1] = 0;
+    crate::ffi::strings::copy_to_c_char(hex, out, len as usize);
 }
 
 /// Free an unfinalized Sha256 context.
@@ -164,17 +158,10 @@ pub unsafe extern "C" fn SCMd5Finalize(hasher: &mut SCMd5, out: *mut u8, len: u3
 /// Consumes the hash context and cannot be re-used.
 #[no_mangle]
 pub unsafe extern "C" fn SCMd5FinalizeToHex(hasher: &mut SCMd5, out: *mut c_char, len: u32) {
-    let out = &mut *(out as *mut u8);
     let hasher: Box<SCMd5> = Box::from_raw(hasher);
     let result = hasher.0.finalize();
     let hex = format!("{:x}", &result);
-    let output = std::slice::from_raw_parts_mut(out, len as usize);
-
-    // This will panic if the sizes differ.
-    output[0..len as usize - 1].copy_from_slice(hex.as_bytes());
-
-    // Terminate the string.
-    output[output.len() - 1] = 0;
+    crate::ffi::strings::copy_to_c_char(hex, out, len as usize);
 }
 
 /// Free an unfinalized Sha1 context.
@@ -197,18 +184,10 @@ pub unsafe extern "C" fn SCMd5HashBuffer(buf: *const u8, buf_len: u32, out: *mut
 pub unsafe extern "C" fn SCMd5HashBufferToHex(
     buf: *const u8, buf_len: u32, out: *mut c_char, len: u32,
 ) {
-    let out = &mut *(out as *mut u8);
-    let output = std::slice::from_raw_parts_mut(out, len as usize);
     let data = std::slice::from_raw_parts(buf, buf_len as usize);
-    // let output = std::slice::from_raw_parts_mut(out, len as usize);
     let hash = Md5::new().chain(data).finalize();
     let hex = format!("{:x}", &hash);
-
-    // This will panic if the sizes differ.
-    output[0..len as usize - 1].copy_from_slice(hex.as_bytes());
-
-    // Terminate the string.
-    output[output.len() - 1] = 0;
+    crate::ffi::strings::copy_to_c_char(hex, out, len as usize);
 }
 
 // Functions that are generic over Digest. For the most part the C bindings are
@@ -225,3 +204,49 @@ unsafe fn finalize<D: Digest>(digest: D, out: *mut u8, len: u32) {
     // This will panic if the sizes differ.
     output.copy_from_slice(&result);
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    // A test around SCSha256 primarily to check that the ouput is
+    // correctly copied into a C string.
+    #[test]
+    fn test_sha256() {
+        unsafe {
+            let hasher = SCSha256New();
+            assert!(!hasher.is_null());
+            let hasher = &mut *hasher as &mut SCSha256;
+            let bytes = &[0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41];
+            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
+            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
+            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
+            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
+            let hex = [0_u8; SC_SHA256_HEX_LEN + 1];
+            SCSha256FinalizeToHex(hasher, hex.as_ptr() as *mut c_char, (SC_SHA256_HEX_LEN + 1) as u32);
+            let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char).to_str().unwrap();
+            assert_eq!(string, "22a48051594c1949deed7040850c1f0f8764537f5191be56732d16a54c1d8153");
+        }
+    }
+
+    // A test around SCSha256 primarily to check that the ouput is
+    // correctly copied into a C string.
+    #[test]
+    fn test_md5() {
+        unsafe {
+            let hasher = SCMd5New();
+            assert!(!hasher.is_null());
+            let hasher = &mut *hasher as &mut SCMd5;
+            let bytes = &[0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41];
+            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
+            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
+            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
+            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
+            let hex = [0_u8; SC_MD5_HEX_LEN + 1];
+            SCMd5FinalizeToHex(hasher, hex.as_ptr() as *mut c_char, (SC_MD5_HEX_LEN + 1) as u32);
+            let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char).to_str().unwrap();
+            assert_eq!(string, "5216ddcc58e8dade5256075e77f642da");
+        }
+    }
+
+}
index c6ba7183036a1423ccb2d86c79269deab0b253a7..ff9f7d1642c33fde6108c64790e6edc30a0a9433 100644 (file)
@@ -17,3 +17,4 @@
 
 pub mod hashing;
 pub mod base64;
+pub mod strings;
diff --git a/rust/src/ffi/strings.rs b/rust/src/ffi/strings.rs
new file mode 100644 (file)
index 0000000..1374cb2
--- /dev/null
@@ -0,0 +1,84 @@
+/* Copyright (C) 2023 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 std::ffi::CString;
+use std::os::raw::c_char;
+
+/// FFI utility function to copy a Rust string to a C string buffer.
+///
+/// Return true on success. On error, false will be returned.
+///
+/// An error will be returned if the provided string cannot be
+/// converted to a C string (for example, it contains NULs), or if the
+/// provided buffer is not large enough.
+///
+/// # Safety
+///
+/// Unsafe as this depends on the caller providing valid buf and size
+/// parameters.
+pub unsafe fn copy_to_c_char(src: String, buf: *mut c_char, size: usize) -> bool {
+    if let Ok(src) = CString::new(src) {
+        let src = src.as_bytes_with_nul();
+        if size >= src.len() {
+            let buf = std::slice::from_raw_parts_mut(buf as *mut u8, size);
+            buf[0..src.len()].copy_from_slice(src);
+            return true;
+        }
+    }
+    false
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_copy_to_c_char() {
+        unsafe {
+            const INPUT: &str = "1234567890";
+            let buf = [0_i8; INPUT.len() + 1];
+            assert!(copy_to_c_char(
+                INPUT.to_string(),
+                buf.as_ptr() as *mut c_char,
+                buf.len()
+            ));
+            // Note that while CStr::from_ptr is documented to take a
+            // *const i8, on Arm/Arm64 it actually takes a *const
+            // u8. So cast it c_char which is an alias for the correct
+            // type depending on the arch.
+            let output = std::ffi::CStr::from_ptr(buf.as_ptr() as *const c_char)
+                .to_str()
+                .unwrap();
+            assert_eq!(INPUT, output);
+        };
+    }
+
+    // Test `copy_to_c_char` with too short of an output buffer to
+    // make sure false is returned.
+    #[test]
+    fn test_copy_to_c_char_short_output() {
+        unsafe {
+            const INPUT: &str = "1234567890";
+            let buf = [0_i8; INPUT.len()];
+            assert!(!copy_to_c_char(
+                INPUT.to_string(),
+                buf.as_ptr() as *mut c_char,
+                buf.len()
+            ));
+        };
+    }
+}
index 2e4a28dd9bb1694007248c3a18c78532c484e8f2..507b39c5f8726c80a8e4443bcea0aea2613f5e77 100644 (file)
@@ -15,7 +15,6 @@
  * 02110-1301, USA.
  */
 
-use std::ffi::CString;
 use std::os::raw::c_char;
 use time::macros::format_description;
 
@@ -41,23 +40,11 @@ pub fn format_timestamp(timestamp: i64) -> Result<String, time::error::Error> {
 pub unsafe extern "C" fn sc_x509_format_timestamp(
     timestamp: i64, buf: *mut c_char, size: usize,
 ) -> bool {
-    let ctimestamp = match format_timestamp(timestamp) {
-        Ok(ts) => match CString::new(ts) {
-            Ok(ts) => ts,
-            Err(_) => return false,
-        },
+    let timestamp = match format_timestamp(timestamp) {
+        Ok(ts) => ts,
         Err(_) => return false,
     };
-    let bytes = ctimestamp.as_bytes_with_nul();
-
-    if size < bytes.len() {
-        false
-    } else {
-        // Convert buf into a slice we can copy into.
-        let buf = std::slice::from_raw_parts_mut(buf as *mut u8, size);
-        buf[0..bytes.len()].copy_from_slice(bytes);
-        true
-    }
+    crate::ffi::strings::copy_to_c_char(timestamp, buf, size)
 }
 
 #[cfg(test)]