Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix unsoundness in FromBytes::new_box_slice_zeroed #63

Merged
merged 1 commit into from
Oct 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ default-features = false

[dev-dependencies]
rand = "0.6"
rustversion = "1.0"
trybuild = "1.0"
51 changes: 41 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,17 +358,14 @@ pub unsafe trait FromBytes {
{
// TODO(#2): Use `Layout::repeat` when `alloc_layout_extra` is
// stabilized.
//
// This will intentionally panic if it overflows.
let layout = Layout::from_size_align(
mem::size_of::<Self>()
.checked_mul(len)
.expect("mem::size_of::<Self>() * len overflows `usize`"),
mem::align_of::<Self>(),
)
.expect("total allocation size overflows `isize`");
unsafe {
// SAFETY: `from_size_align_unchecked` is sound because
// slice_len_bytes is guaranteed to be properly aligned (we just
// multiplied it by `size_of::<T>()`, which is guaranteed to be
// aligned).
let layout = Layout::from_size_align_unchecked(
mem::size_of::<Self>().checked_mul(len).unwrap(),
mem::align_of::<Self>(),
);
if layout.size() != 0 {
let ptr = alloc::alloc::alloc_zeroed(layout) as *mut Self;
if ptr.is_null() {
Expand Down Expand Up @@ -2204,6 +2201,40 @@ mod alloc_support {
let s: Box<[()]> = <()>::new_box_slice_zeroed(0);
assert_eq!(s.len(), 0);
}

#[test]
#[should_panic(expected = "mem::size_of::<Self>() * len overflows `usize`")]
fn test_new_box_slice_zeroed_panics_mul_overflow() {
let _ = u16::new_box_slice_zeroed(usize::MAX);
}

// This test fails on stable <= 1.64.0, but succeeds on 1.65.0-beta.2
// (2022-09-13). In particular, on stable <= 1.64.0,
// `new_box_slice_zeroed` attempts to perform the allocation (which of
// course fails). `Layout::from_size_align` should be returning an error
// due to `isize` overflow, but it doesn't. I (joshlf) haven't
// investigated enough to confirm, but my guess is that this was a bug
// that was fixed recently.
//
// Triggering the bug requires calling `FromBytes::new_box_slice_zeroed`
// with an allocation which overflows `isize`, and all that happens is
// that the program panics due to a failed allocation. Even on 32-bit
// platforms, this requires a 2GB allocation. On 64-bit platforms, this
// requires a 2^63-byte allocation. In both cases, even a slightly
// smaller allocation that didn't trigger this bug would likely
// (absolutely certainly in the case of 64-bit platforms) fail to
// allocate in exactly the same way regardless.
//
// Given how minor the impact is, and given that the bug seems fixed in
// 1.65.0, I've chosen to just release the code as-is and disable the
// test on buggy toolchains. Once our MSRV is at or above 1.65.0, we can
// remove this conditional compilation (and this comment) entirely.
#[rustversion::since(1.65.0)]
#[test]
#[should_panic(expected = "total allocation size overflows `isize`: LayoutError")]
fn test_new_box_slice_zeroed_panics_isize_overflow() {
let _ = u16::new_box_slice_zeroed((isize::MAX as usize / mem::size_of::<u16>()) + 1);
}
}
}

Expand Down