From 76354f144aae1a58cb5d7bc393d626091dc25027 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Jul 2024 17:01:24 -0400 Subject: [PATCH] make read() safer and add copy_slice_to_ptr() --- src/lib.rs | 181 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 125 insertions(+), 56 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 40b2904..be7fd5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +1,19 @@ -use std::env; -use std::mem; -use std::ffi::CStr; +#![allow(clippy::not_unsafe_ptr_arg_deref)] + use libc::{ - c_int, - c_uchar, - c_uint, - c_void, - dlsym, - getrandom as libc_getrandom, - RTLD_NEXT, - ssize_t + c_int, c_uchar, c_uint, c_void, dlsym, getrandom as libc_getrandom, ssize_t, RTLD_NEXT, }; +use std::env; +use std::ffi::{CStr, CString}; +use std::mem; +use std::sync::OnceLock; fn fakerand_value() -> Option { - env::var("FAKERAND").ok().and_then(|val| val.parse().ok()) + static FAKERAND: OnceLock> = OnceLock::new(); + *FAKERAND.get_or_init(|| env::var("FAKERAND").ok().and_then(|v| v.parse().ok())) } +/* #[no_mangle] pub extern "C" fn rand() -> c_int { println!("rand() called"); @@ -29,52 +27,113 @@ pub extern "C" fn rand() -> c_int { if !symbol.is_null() { let original_rand: extern "C" fn() -> c_int = std::mem::transmute(symbol); - return original_rand(); + original_rand() } else { eprintln!("Error: original rand() not found"); std::process::exit(1); } } } +*/ -#[no_mangle] -pub extern "C" fn read(fd: c_int, buf: *mut c_void, count: usize) -> ssize_t { - static mut ORIGINAL_READ: Option ssize_t> = None; - - unsafe { - if ORIGINAL_READ.is_none() { - let symbol = dlsym(RTLD_NEXT, "read\0".as_ptr() as *const _); - if symbol.is_null() { - eprintln!("Error: original read() not found"); - std::process::exit(1); - } - ORIGINAL_READ = Some(std::mem::transmute(symbol)); +#[inline(always)] +unsafe fn copy_slice_to_ptr(slice: &[u8], buf: *mut c_void, buf_size: usize) { + for offset in (0..buf_size).step_by(4) { + unsafe { + std::ptr::copy( + slice.as_ptr() as *mut c_void, + buf.byte_add(offset), + std::cmp::min(buf_size - offset, slice.len()), + ) } - - let original_read = ORIGINAL_READ.unwrap(); - - let path = format!("/proc/self/fd/{}", fd); - let mut actualpath = [0u8; 1024]; - - let len = libc::readlink( - path.as_ptr() as *const i8, - actualpath.as_mut_ptr() as *mut i8, - actualpath.len() as _ - ); - - if len != -1 { - let actualpath = CStr::from_ptr(actualpath.as_ptr() as *const i8).to_str().unwrap(); - if (actualpath == "/dev/urandom" || actualpath == "/dev/random") && fakerand_value().is_some() { - let fakerand = fakerand_value().unwrap(); - *(buf as *mut c_int) = fakerand; - return std::mem::size_of::() as ssize_t; - } - } - - original_read(fd, buf, count) } } +/// # Safety +/// +/// This function assumes the `read()` libc function is defined as +/// `read(c_int, *mut c_void, usize) -> ssize_t` and calls it as such. This function also invokes +/// `libc::readlink()` with two paths each of which not exceeding 4096 (PATH_MAX) size in length. +/// +/// The function may panic if the path can't be encoded as a Rust `str` value. +#[no_mangle] +pub extern "C" fn read(fd: c_int, buf: *mut c_void, count: usize) -> ssize_t { + static ORIGINAL_READ: OnceLock ssize_t> = + OnceLock::new(); + + let original_read = ORIGINAL_READ.get_or_init(|| { + let symbol = unsafe { dlsym(RTLD_NEXT, "read\0".as_ptr() as *const _) }; + if symbol.is_null() { + eprintln!("Error: original read() not found"); + std::process::exit(1); + } + unsafe { std::mem::transmute(symbol) } + }); + + let path = format!("/proc/self/fd/{}", fd); + // PATH_MAX is defined as 4096 chars in a path including nul + let mut actualpath = [0u8; 4096]; + + let len = unsafe { + libc::readlink( + path.as_ptr() as *const i8, + actualpath.as_mut_ptr() as *mut i8, + actualpath.len() as _, + ) + }; + + if len != -1 { + let actualpath = unsafe { CStr::from_ptr(actualpath.as_ptr() as *const i8) }; + let urandom = CString::new("/dev/urandom").unwrap(); + let random = CString::new("/dev/random").unwrap(); + if actualpath == urandom.as_c_str() || actualpath == random.as_c_str() { + let random_value = match fakerand_value() { + Some(n) => n, + None => { + eprintln!("fakerand loaded but unable to generate fake rand"); + unsafe { + let errno = libc::__errno_location(); + *errno = libc::EINVAL; + } + return -1; + } + }; + unsafe { + copy_slice_to_ptr(&random_value.to_ne_bytes(), buf, count); + } + /* + let value_ptr: *const i32 = &random_value; + for offset in (0..count).step_by(4) { + unsafe { + std::ptr::copy( + value_ptr as *mut c_void, + buf.byte_add(offset), + std::cmp::min(count - offset, 4), + ) + }; + } + */ + return count.try_into().expect("size larger than SSIZE_MAX"); + } + /* + if (actualpath == "/dev/urandom" || actualpath == "/dev/random") + && fakerand_value().is_some() + { + let fakerand = fakerand_value().unwrap(); + *(buf as *mut c_int) = fakerand; + return std::mem::size_of::() as ssize_t; + } + */ + } + + original_read(fd, buf, count) +} + +/* +/// # Safety +/// +/// Checked by caller. This function may result in undefined behavior if buflen is longer than the +/// allocated length of buf or if buf is not allocated, or previously freed, memory. #[no_mangle] pub extern "C" fn getrandom(buf: *mut c_void, buflen: usize, flags: c_uint) -> ssize_t { if let Some(fakerand) = fakerand_value() { @@ -84,11 +143,13 @@ pub extern "C" fn getrandom(buf: *mut c_void, buflen: usize, flags: c_uint) -> s return std::mem::size_of::() as ssize_t; } - unsafe { - libc_getrandom(buf, buflen, flags) - } + unsafe { libc_getrandom(buf, buflen, flags) } } +/// # Safety +/// +/// Checked by caller. This function may result in undefined behavior if num is longer than the +/// allocated length of buf or if buf is not allocated, or previously freed, memory. #[no_mangle] pub extern "C" fn RAND_bytes(buf: *mut c_uchar, num: c_int) -> c_int { if let Some(fakerand) = fakerand_value() { @@ -103,14 +164,16 @@ pub extern "C" fn RAND_bytes(buf: *mut c_uchar, num: c_int) -> c_int { unsafe { let symbol = dlsym(RTLD_NEXT, "RAND_bytes\0".as_ptr() as *const _); if !symbol.is_null() { - let original_rand_bytes: extern "C" fn(*mut c_uchar, c_int) -> c_int = mem::transmute(symbol); - return original_rand_bytes(buf, num); + let original_rand_bytes: extern "C" fn(*mut c_uchar, c_int) -> c_int = + mem::transmute(symbol); + original_rand_bytes(buf, num) } else { eprintln!("Error: original RAND_bytes() not found"); std::process::exit(1); } } } +*/ #[cfg(test)] mod tests { @@ -138,8 +201,11 @@ mod tests { #[test] fn test_read_with_fakerand() { env::set_var("FAKERAND", "42"); - - let file = OpenOptions::new().read(true).open("/dev/urandom").expect("Failed to open /dev/urandom"); + + let file = OpenOptions::new() + .read(true) + .open("/dev/urandom") + .expect("Failed to open /dev/urandom"); let fd = file.as_raw_fd(); let mut buf = [0u8; 4]; let result = read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()); @@ -154,7 +220,10 @@ mod tests { fn test_read_without_fakerand() { env::remove_var("FAKERAND"); - let file = OpenOptions::new().read(true).open("/dev/urandom").expect("Failed to open /dev/urandom"); + let file = OpenOptions::new() + .read(true) + .open("/dev/urandom") + .expect("Failed to open /dev/urandom"); let fd = file.as_raw_fd(); let mut buf = [0u8; 4]; let result = read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()); @@ -175,4 +244,4 @@ mod tests { env::remove_var("FAKERAND"); } -} \ No newline at end of file +}