Potential undefined behavior in hermit implementation.
briansmith opened this issue · comments
Lines 4 to 7 in 35808a4
Lines 9 to 16 in 35808a4
- If
res == -sizie::MIN
then(-res)
will overflow. To fix, usechecked_neg()
. - if
res < (i32::MIN as isize)
then(-res) as u32
is a truncating, i.e. lossy conversion. To fix: avoid usingas
for integer conversions and instead useTryInto
for this conversion. - if
(-res) % 0x1_0000_0000 == 0
then(-res) as u32
is zero and thenNonZeroU32::new_unchecked((-res) as u32)
is undefined behavior. To fix, use the safeNonZerou32::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 i32
s 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.