rust-random / getrandom

A small cross-platform library for retrieving random data from (operating) system source

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Potential undefined behavior in hermit implementation.

briansmith opened this issue · comments

extern "C" {
fn sys_read_entropy(buffer: *mut u8, length: usize, flags: u32) -> isize;
}

getrandom/src/hermit.rs

Lines 9 to 16 in 35808a4

while !dest.is_empty() {
let res = unsafe { sys_read_entropy(dest.as_mut_ptr() as *mut u8, dest.len(), 0) };
if res < 0 {
// SAFETY: all Hermit error codes use i32 under the hood:
// https://github.com/hermitcore/libhermit-rs/blob/master/src/errno.rs
let code = unsafe { NonZeroU32::new_unchecked((-res) as u32) };
return Err(code.into());
}

  • If res == -sizie::MIN then (-res) will overflow. To fix, use checked_neg().
  • if res < (i32::MIN as isize) then (-res) as u32 is a truncating, i.e. lossy conversion. To fix: avoid using as for integer conversions and instead use TryInto for this conversion.
  • if (-res) % 0x1_0000_0000 == 0 then (-res) as u32 is zero and then NonZeroU32::new_unchecked((-res) as u32) is undefined behavior. To fix, use the safe NonZerou32::new() instead.

Maybe somewhere hermit guarantees errors codes will never be this large but better to avoid the issue completely.

Something like:

     let code = res.checked_neg().map(u32::try_from).map(NonZeroU32::new).unwrap_or(something);

There would be no undefined behavior in the new version.

Negation of isize::MIN by itself does not cause UB, according to the reference in such cases negation simply returns MIN. The problem, as you noted, is with truncated conversion, which may result in 0. For example, a similar issue may be caused on 64 bit targets by -(1isize << 33).

As can be seen by the linked source code, Hermit uses positive i32s for error codes, so return syscall's result should be always strictly bigger than i32::MIN. Also, according to the syscall docs, sys_read_entropy can only return -EINVAL and -ENOSYS.

So I think the current code is fine, but I am not against improving robustness of the code.

I think if we want things to be more robust, we should pursue just using the hermit-abi crate. This seems to be what libstd does for hermit targets, so it would also make things more consistent.

Using hermit-abi does not change anything. read_entropy returns isize, same as our binding, thus the issue would stay the same.

We should be consistent with our approach. We either use platform crates for all targets where applicable, or do not use them at all. Personally, I prefer the current approach, but we could discuss it in a separate issue.