From e21ae88e05468262a09fec3fd7ef8e50814f4ff7 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Fri, 27 Jan 2023 00:58:58 -0600 Subject: [PATCH] rust: utility function to copy Rust strings to C strings 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 | 77 ++++++++++++++++++++++++------------- rust/src/ffi/mod.rs | 1 + rust/src/ffi/strings.rs | 84 +++++++++++++++++++++++++++++++++++++++++ rust/src/x509/time.rs | 19 ++-------- 4 files changed, 139 insertions(+), 42 deletions(-) create mode 100644 rust/src/ffi/strings.rs diff --git a/rust/src/ffi/hashing.rs b/rust/src/ffi/hashing.rs index 89205cb454..d5c247ddc3 100644 --- a/rust/src/ffi/hashing.rs +++ b/rust/src/ffi/hashing.rs @@ -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 = 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 = 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(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"); + } + } + +} diff --git a/rust/src/ffi/mod.rs b/rust/src/ffi/mod.rs index c6ba718303..ff9f7d1642 100644 --- a/rust/src/ffi/mod.rs +++ b/rust/src/ffi/mod.rs @@ -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 index 0000000000..1374cb27ba --- /dev/null +++ b/rust/src/ffi/strings.rs @@ -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() + )); + }; + } +} diff --git a/rust/src/x509/time.rs b/rust/src/x509/time.rs index 2e4a28dd9b..507b39c5f8 100644 --- a/rust/src/x509/time.rs +++ b/rust/src/x509/time.rs @@ -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 { 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)]