Skip to content

UB in Default impl of GenericArray #97

Closed
@Robbepop

Description

@Robbepop

Today I was running cargo miri test on one of my projects that is using the generic-array crate internally. The cargo miri test runs tests of the project using the Rust MIR interpreter miri. Due to execution the tool is sometimes able to detect undefined behaviour in Rust code upon execution - however, there might be false positives.

The output of miri's execution:

error: Undefined Behavior: type validation failed: encountered uninitialized bytes at .value.data.parent1.parent1.data, but expected a valid enum discriminant
   --> /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/mem/maybe_uninit.rs:502:34
    |
502 |         ManuallyDrop::into_inner(self.value)
    |                                  ^^^^^^^^^^ type validation failed: encountered uninitialized bytes at .value.data.parent1.parent1.data, but expected a valid enum discriminant
    |
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: inside `std::mem::MaybeUninit::<generic_array::GenericArray<std::option::Option<storage2::lazy::entry::Entry<u8>>, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>>>::assume_init` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/mem/maybe_uninit.rs:502:34
    = note: inside `std::mem::uninitialized::<generic_array::GenericArray<std::option::Option<storage2::lazy::entry::Entry<u8>>, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>>>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/mem/mod.rs:664:5
    = note: inside `generic_array::ArrayBuilder::<std::option::Option<storage2::lazy::entry::Entry<u8>>, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>>::new` at /home/.cargo/registry/src/github.jpy.wang-1ecc6299db9ec823/generic-array-0.13.2/src/lib.rs:187:38
    = note: inside `<generic_array::GenericArray<std::option::Option<storage2::lazy::entry::Entry<u8>>, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>> as generic_array::sequence::GenericSequence<std::option::Option<storage2::lazy::entry::Entry<u8>>>>::generate::<[closure@DefId(32:51 ~ generic_array[53b7]::impls[0]::{{impl}}[0]::default[0]::{{closure}}[0])]>` at /home/.cargo/registry/src/github.jpy.wang-1ecc6299db9ec823/generic-array-0.13.2/src/lib.rs:344:35
    = note: inside `generic_array::impls::<impl std::default::Default for generic_array::GenericArray<std::option::Option<storage2::lazy::entry::Entry<u8>>, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>>>::default` at /home/.cargo/registry/src/github.jpy.wang-1ecc6299db9ec823/generic-array-0.13.2/src/impls.rs:17:9
note: inside `storage2::lazy::lazy_array::EntryArray::<u8, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>>::new` at core/src/storage2/lazy/lazy_array.rs:109:22
   --> core/src/storage2/lazy/lazy_array.rs:109:22
    |
109 |             entries: Default::default(),
    |                      ^^^^^^^^^^^^^^^^^^
note: inside `<storage2::lazy::lazy_array::EntryArray<u8, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>> as std::default::Default>::default` at core/src/storage2/lazy/lazy_array.rs:119:9
   --> core/src/storage2/lazy/lazy_array.rs:119:9
    |
119 |         Self::new()
    |         ^^^^^^^^^^^
note: inside `storage2::lazy::lazy_array::LazyArray::<u8, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>>::new` at core/src/storage2/lazy/lazy_array.rs:182:45
   --> core/src/storage2/lazy/lazy_array.rs:182:45
    |
182 |             cached_entries: UnsafeCell::new(Default::default()),
    |                                             ^^^^^^^^^^^^^^^^^^
note: inside `<storage2::lazy::lazy_array::LazyArray<u8, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>> as std::default::Default>::default` at core/src/storage2/lazy/lazy_array.rs:165:9
   --> core/src/storage2/lazy/lazy_array.rs:165:9
    |
165 |         Self::new()
    |         ^^^^^^^^^^^
note: inside `storage2::collections::smallvec::SmallVec::<u8, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>>::new` at core/src/storage2/collections/smallvec/mod.rs:79:20
   --> core/src/storage2/collections/smallvec/mod.rs:79:20
    |
79  |             elems: Default::default(),
    |                    ^^^^^^^^^^^^^^^^^^
note: inside `storage2::collections::smallvec::tests::first_last_of_empty` at core/src/storage2/collections/smallvec/tests.rs:61:19
   --> core/src/storage2/collections/smallvec/tests.rs:61:19
    |
61  |     let mut vec = <SmallVec<u8, U4>>::new();
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside closure at core/src/storage2/collections/smallvec/tests.rs:60:1
   --> core/src/storage2/collections/smallvec/tests.rs:60:1
    |
60  | / fn first_last_of_empty() {
61  | |     let mut vec = <SmallVec<u8, U4>>::new();
62  | |     assert_eq!(vec.first(), None);
63  | |     assert_eq!(vec.first_mut(), None);
64  | |     assert_eq!(vec.last(), None);
65  | |     assert_eq!(vec.last_mut(), None);
66  | | }
    | |_^
    = note: inside `<[closure@core/src/storage2/collections/smallvec/tests.rs:60:1: 66:2] as std::ops::FnOnce<()>>::call_once - shim` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/ops/function.rs:232:5
    = note: inside `<fn() as std::ops::FnOnce<()>>::call_once - shim(fn())` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/ops/function.rs:232:5
    = note: inside `test::__rust_begin_short_backtrace::<fn()>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libtest/lib.rs:517:5
    = note: inside closure at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libtest/lib.rs:508:30
    = note: inside `<[closure@DefId(38:631 ~ test[140d]::run_test[0]::{{closure}}[2]) 0:fn()] as std::ops::FnOnce<()>>::call_once - shim(vtable)` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/ops/function.rs:232:5
    = note: inside `<std::boxed::Box<dyn std::ops::FnOnce() + std::marker::Send> as std::ops::FnOnce<()>>::call_once` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/boxed.rs:1008:9
    = note: inside `<std::panic::AssertUnwindSafe<std::boxed::Box<dyn std::ops::FnOnce() + std::marker::Send>> as std::ops::FnOnce<()>>::call_once` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panic.rs:318:9
    = note: inside `std::panicking::r#try::do_call::<std::panic::AssertUnwindSafe<std::boxed::Box<dyn std::ops::FnOnce() + std::marker::Send>>, ()>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:331:40
    = note: inside `std::panicking::r#try::<(), std::panic::AssertUnwindSafe<std::boxed::Box<dyn std::ops::FnOnce() + std::marker::Send>>>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:274:15
    = note: inside `std::panic::catch_unwind::<std::panic::AssertUnwindSafe<std::boxed::Box<dyn std::ops::FnOnce() + std::marker::Send>>, ()>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panic.rs:394:14
    = note: inside `test::run_test_in_process` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libtest/lib.rs:541:18
    = note: inside closure at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libtest/lib.rs:450:39
    = note: inside `test::run_test::run_test_inner` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libtest/lib.rs:475:13
    = note: inside `test::run_test` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libtest/lib.rs:505:28
    = note: inside `test::run_tests::<[closure@DefId(38:230 ~ test[140d]::console[0]::run_tests_console[0]::{{closure}}[2]) 0:&mut test::console::ConsoleTestState, 1:&mut std::boxed::Box<dyn test::formatters::OutputFormatter>]>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libtest/lib.rs:284:13
    = note: inside `test::run_tests_console` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libtest/console.rs:280:5
    = note: inside `test::test_main` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libtest/lib.rs:120:15
    = note: inside `test::test_main_static` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libtest/lib.rs:139:5
    = note: inside `main`
    = note: inside closure at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:67:34
    = note: inside closure at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:52:73
    = note: inside `std::sys_common::backtrace::__rust_begin_short_backtrace::<[closure@DefId(1:6025 ~ std[1995]::rt[0]::lang_start_internal[0]::{{closure}}[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/sys_common/backtrace.rs:130:5
    = note: inside closure at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:52:13
    = note: inside `std::panicking::r#try::do_call::<[closure@DefId(1:6024 ~ std[1995]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:331:40
    = note: inside `std::panicking::r#try::<i32, [closure@DefId(1:6024 ~ std[1995]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe]>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:274:15
    = note: inside `std::panic::catch_unwind::<[closure@DefId(1:6024 ~ std[1995]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panic.rs:394:14
    = note: inside `std::rt::lang_start_internal` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:51:25
    = note: inside `std::rt::lang_start::<()>` at /home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:67:5
    = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

So it seems to be a problem with uninitialized data in the Default implementation of GenericArray where it is using a ManuallyDrop with uninitialized data.

This is the test code that triggered the UB of my crate that is using generic-array:
https://github.com/paritytech/ink/blob/redo-init-and-flush/core/src/storage2/collections/smallvec/tests.rs#L60

I belive the problem is somewhere in this method:
https://github.com/fizyk20/generic-array/blob/master/src/lib.rs#L207
The array field is of type MaybeUninit and during the loop in fn generate (same file) the underlying array is initialized. However, in the linked method no call to assume_init is made and instead a core::mem::ptr::read is performed on the potentially uninitialized array contents. This doesn't necessarily need to be wrong, however, it could be that it confuses miri.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions