make read() safer and add copy_slice_to_ptr()

This commit is contained in:
Ryan Heywood 2024-07-18 17:01:24 -04:00
parent f3a453a88e
commit 76354f144a
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
1 changed files with 125 additions and 56 deletions

View File

@ -1,21 +1,19 @@
use std::env; #![allow(clippy::not_unsafe_ptr_arg_deref)]
use std::mem;
use std::ffi::CStr;
use libc::{ use libc::{
c_int, c_int, c_uchar, c_uint, c_void, dlsym, getrandom as libc_getrandom, ssize_t, RTLD_NEXT,
c_uchar,
c_uint,
c_void,
dlsym,
getrandom as libc_getrandom,
RTLD_NEXT,
ssize_t
}; };
use std::env;
use std::ffi::{CStr, CString};
use std::mem;
use std::sync::OnceLock;
fn fakerand_value() -> Option<i32> { fn fakerand_value() -> Option<i32> {
env::var("FAKERAND").ok().and_then(|val| val.parse().ok()) static FAKERAND: OnceLock<Option<i32>> = OnceLock::new();
*FAKERAND.get_or_init(|| env::var("FAKERAND").ok().and_then(|v| v.parse().ok()))
} }
/*
#[no_mangle] #[no_mangle]
pub extern "C" fn rand() -> c_int { pub extern "C" fn rand() -> c_int {
println!("rand() called"); println!("rand() called");
@ -29,52 +27,113 @@ pub extern "C" fn rand() -> c_int {
if !symbol.is_null() { if !symbol.is_null() {
let original_rand: extern "C" fn() -> c_int = std::mem::transmute(symbol); let original_rand: extern "C" fn() -> c_int = std::mem::transmute(symbol);
return original_rand(); original_rand()
} else { } else {
eprintln!("Error: original rand() not found"); eprintln!("Error: original rand() not found");
std::process::exit(1); std::process::exit(1);
} }
} }
} }
*/
#[no_mangle] #[inline(always)]
pub extern "C" fn read(fd: c_int, buf: *mut c_void, count: usize) -> ssize_t { unsafe fn copy_slice_to_ptr(slice: &[u8], buf: *mut c_void, buf_size: usize) {
static mut ORIGINAL_READ: Option<extern "C" fn(c_int, *mut c_void, usize) -> ssize_t> = None; for offset in (0..buf_size).step_by(4) {
unsafe {
unsafe { std::ptr::copy(
if ORIGINAL_READ.is_none() { slice.as_ptr() as *mut c_void,
let symbol = dlsym(RTLD_NEXT, "read\0".as_ptr() as *const _); buf.byte_add(offset),
if symbol.is_null() { std::cmp::min(buf_size - offset, slice.len()),
eprintln!("Error: original read() not found"); )
std::process::exit(1);
}
ORIGINAL_READ = Some(std::mem::transmute(symbol));
} }
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::<c_int>() 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<extern "C" fn(c_int, *mut c_void, usize) -> 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::<c_int>() 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] #[no_mangle]
pub extern "C" fn getrandom(buf: *mut c_void, buflen: usize, flags: c_uint) -> ssize_t { pub extern "C" fn getrandom(buf: *mut c_void, buflen: usize, flags: c_uint) -> ssize_t {
if let Some(fakerand) = fakerand_value() { 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::<c_int>() as ssize_t; return std::mem::size_of::<c_int>() as ssize_t;
} }
unsafe { unsafe { libc_getrandom(buf, buflen, flags) }
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] #[no_mangle]
pub extern "C" fn RAND_bytes(buf: *mut c_uchar, num: c_int) -> c_int { pub extern "C" fn RAND_bytes(buf: *mut c_uchar, num: c_int) -> c_int {
if let Some(fakerand) = fakerand_value() { 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 { unsafe {
let symbol = dlsym(RTLD_NEXT, "RAND_bytes\0".as_ptr() as *const _); let symbol = dlsym(RTLD_NEXT, "RAND_bytes\0".as_ptr() as *const _);
if !symbol.is_null() { if !symbol.is_null() {
let original_rand_bytes: extern "C" fn(*mut c_uchar, c_int) -> c_int = mem::transmute(symbol); let original_rand_bytes: extern "C" fn(*mut c_uchar, c_int) -> c_int =
return original_rand_bytes(buf, num); mem::transmute(symbol);
original_rand_bytes(buf, num)
} else { } else {
eprintln!("Error: original RAND_bytes() not found"); eprintln!("Error: original RAND_bytes() not found");
std::process::exit(1); std::process::exit(1);
} }
} }
} }
*/
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -138,8 +201,11 @@ mod tests {
#[test] #[test]
fn test_read_with_fakerand() { fn test_read_with_fakerand() {
env::set_var("FAKERAND", "42"); 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 fd = file.as_raw_fd();
let mut buf = [0u8; 4]; let mut buf = [0u8; 4];
let result = read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()); 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() { fn test_read_without_fakerand() {
env::remove_var("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 fd = file.as_raw_fd();
let mut buf = [0u8; 4]; let mut buf = [0u8; 4];
let result = read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()); let result = read(fd, buf.as_mut_ptr() as *mut c_void, buf.len());
@ -175,4 +244,4 @@ mod tests {
env::remove_var("FAKERAND"); env::remove_var("FAKERAND");
} }
} }