Skip to content

Commit 4be96db

Browse files
committed
Improve the memory safety guarantees around open_readonly.
Do more complete NUL termination checking, at compile-time. Remove the `unsafe` from the function as it is now memory-safe.
1 parent 69e7c7b commit 4be96db

File tree

3 files changed

+89
-12
lines changed

3 files changed

+89
-12
lines changed

src/use_file.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Implementations that just need to read from a file
22
use crate::{
3-
util_libc::{open_readonly, sys_fill_exact},
3+
util_libc::{cstr, open_readonly, sys_fill_exact},
44
Error,
55
};
66
use core::{
@@ -16,7 +16,7 @@ use core::{
1616
/// - On Redox, only /dev/urandom is provided.
1717
/// - On AIX, /dev/urandom will "provide cryptographically secure output".
1818
/// - On Haiku and QNX Neutrino they are identical.
19-
const FILE_PATH: &str = "/dev/urandom\0";
19+
const FILE_PATH: cstr::Ref = cstr::unwrap_const_from_bytes_with_nul(b"/dev/urandom\0");
2020
const FD_UNINIT: usize = usize::max_value();
2121

2222
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
@@ -57,7 +57,7 @@ fn get_rng_fd() -> Result<libc::c_int, Error> {
5757
#[cfg(any(target_os = "android", target_os = "linux"))]
5858
wait_until_rng_ready()?;
5959

60-
let fd = unsafe { open_readonly(FILE_PATH)? };
60+
let fd = open_readonly(FILE_PATH)?;
6161
// The fd always fits in a usize without conflicting with FD_UNINIT.
6262
debug_assert!(fd >= 0 && (fd as usize) < FD_UNINIT);
6363
FD.store(fd as usize, Relaxed);
@@ -68,8 +68,9 @@ fn get_rng_fd() -> Result<libc::c_int, Error> {
6868
// Succeeds once /dev/urandom is safe to read from
6969
#[cfg(any(target_os = "android", target_os = "linux"))]
7070
fn wait_until_rng_ready() -> Result<(), Error> {
71+
const DEV_RANDOM: cstr::Ref = cstr::unwrap_const_from_bytes_with_nul(b"/dev/random\0");
7172
// Poll /dev/random to make sure it is ok to read from /dev/urandom.
72-
let fd = unsafe { open_readonly("/dev/random\0")? };
73+
let fd = open_readonly(DEV_RANDOM)?;
7374
let mut pfd = libc::pollfd {
7475
fd,
7576
events: libc::POLLIN,

src/util_cstr.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! Work around lack of `core::ffi::CStr` prior to Rust 1.64, and the lack of
2+
//! `const fn` support for `CStr` in later versions.
3+
4+
// TODO(MSRV 1.64): Use `core::ffi::c_char`.
5+
use libc::c_char;
6+
7+
// TODO(MSRV 1.64): Replace with `&core::ffi::CStr`.
8+
pub struct Ref(&'static [u8]);
9+
10+
impl Ref {
11+
#[inline(always)]
12+
pub fn as_ptr(&self) -> *const c_char {
13+
const _SAME_ALIGNMENT: () =
14+
assert!(core::mem::align_of::<u8>() == core::mem::align_of::<c_char>());
15+
const _SAME_SIZE: () =
16+
assert!(core::mem::size_of::<u8>() == core::mem::size_of::<c_char>());
17+
18+
// It is safe to cast a `*const u8` to a `const c_char` as they are the
19+
// same size and alignment.
20+
self.0.as_ptr().cast()
21+
}
22+
23+
// SAFETY: Same as `CStr::from_bytes_with_nul_unchecked`.
24+
const unsafe fn from_bytes_with_nul_unchecked(value: &'static [u8]) -> Self {
25+
Self(value)
26+
}
27+
}
28+
29+
pub const fn unwrap_const_from_bytes_with_nul(value: &'static [u8]) -> Ref {
30+
// XXX: We cannot use `unwrap_const` since `Ref`/`CStr` is not `Copy`.
31+
match const_from_bytes_with_nul(value) {
32+
Some(r) => r,
33+
None => panic!("const_from_bytes_with_nul failed"),
34+
}
35+
}
36+
37+
// TODO(MSRV 1.72): Replace with `CStr::from_bytes_with_nul`.
38+
#[inline(always)]
39+
const fn const_from_bytes_with_nul(value: &'static [u8]) -> Option<Ref> {
40+
const fn const_contains(mut value: &[u8], needle: &u8) -> bool {
41+
while let [head, tail @ ..] = value {
42+
if *head == *needle {
43+
return true;
44+
}
45+
value = tail;
46+
}
47+
false
48+
}
49+
50+
// TODO(MSRV 1.69): Use `core::ffi::CStr::from_bytes_until_nul`
51+
match value {
52+
[before_nul @ .., 0] if !const_contains(before_nul, &0) => {
53+
// SAFETY:
54+
// * `value` is nul-terminated according to the slice pattern.
55+
// * `value` doesn't contain any interior null, by the guard.
56+
// TODO(MSRV 1.64): Use `CStr::from_bytes_with_nul_unchecked`
57+
Some(unsafe { Ref::from_bytes_with_nul_unchecked(value) })
58+
}
59+
_ => None,
60+
}
61+
}
62+
63+
mod tests {
64+
use super::const_from_bytes_with_nul;
65+
66+
// Bad.
67+
const _EMPTY_UNTERMINATED: () = assert!(const_from_bytes_with_nul(b"").is_none());
68+
const _EMPTY_DOUBLE_TERMINATED: () = assert!(const_from_bytes_with_nul(b"\0\0").is_none());
69+
const _DOUBLE_NUL: () = assert!(const_from_bytes_with_nul(b"\0\0").is_none());
70+
const _LEADINGL_NUL: () = assert!(const_from_bytes_with_nul(b"\0a\0").is_none());
71+
const _INTERNAL_NUL_UNTERMINATED: () = assert!(const_from_bytes_with_nul(b"\0a").is_none());
72+
73+
// Good.
74+
const EMPTY_TERMINATED: () = assert!(const_from_bytes_with_nul(b"\0").is_some());
75+
const _NONEMPTY: () = assert!(const_from_bytes_with_nul(b"asdf\0").is_some());
76+
const _1_CHAR: () = assert!(const_from_bytes_with_nul(b"a\0").is_some());
77+
}

src/util_libc.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use core::{
88
};
99
use libc::c_void;
1010

11+
#[path = "util_cstr.rs"]
12+
pub(crate) mod cstr;
13+
1114
cfg_if! {
1215
if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android"))] {
1316
use libc::__errno as errno_location;
@@ -133,15 +136,11 @@ impl Weak {
133136
}
134137
}
135138

136-
// SAFETY: path must be null terminated, FD must be manually closed.
137-
pub unsafe fn open_readonly(path: &str) -> Result<libc::c_int, Error> {
138-
debug_assert_eq!(path.as_bytes().last(), Some(&0));
139+
// Memory leak hazard: The returned file descriptor must be manually closed to
140+
// avoid a file descriptor leak.
141+
pub fn open_readonly(path: cstr::Ref) -> Result<libc::c_int, Error> {
139142
loop {
140-
// XXX/FIXME: Unchecked UTF-8-to-c_char cast.
141-
let fd = libc::open(
142-
path.as_ptr().cast::<libc::c_char>(),
143-
libc::O_RDONLY | libc::O_CLOEXEC,
144-
);
143+
let fd = unsafe { libc::open(path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
145144
if fd >= 0 {
146145
return Ok(fd);
147146
}

0 commit comments

Comments
 (0)