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.
pull/8489/head
Jason Ish 3 years ago
parent 6344501dba
commit e21ae88e05

@ -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");
}
}
}

@ -17,3 +17,4 @@
pub mod hashing;
pub mod base64;
pub mod strings;

@ -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()
));
};
}
}

@ -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)]

Loading…
Cancel
Save