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

Use-after-free in Drop impl for vec::Drain #91772

Closed
camelid opened this issue Dec 11, 2021 · 6 comments · Fixed by #91797
Closed

Use-after-free in Drop impl for vec::Drain #91772

camelid opened this issue Dec 11, 2021 · 6 comments · Fixed by #91797
Assignees
Labels
A-collections Area: std::collections. C-bug Category: This is a bug. E-needs-bisection Call for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustc I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness regression-from-stable-to-nightly Performance or correctness regression from stable to nightly. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Milestone

Comments

@camelid
Copy link
Member

camelid commented Dec 11, 2021

MCVE (could be reduced further):

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];
    v.splice(1..2, [1, 2]);
    // NOTE: using the correct range 1..3 fixes the use-after-free
}
error: Undefined Behavior: pointer to alloc1035 was dereferenced after this allocation got freed
  --> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/raw.rs:91:14
   |
91 |     unsafe { &*ptr::slice_from_raw_parts(data, len) }
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to alloc1035 was dereferenced after this allocation got freed
   |
   = 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::slice::from_raw_parts::<i32>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/raw.rs:91:14
   = note: inside `std::slice::Iter::<i32>::make_slice` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/iter/macros.rs:78:26
   = note: inside `std::slice::Iter::<i32>::as_slice` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/iter.rs:130:9
   = note: inside `<std::vec::Drain<i32> as std::ops::Drop>::drop` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/vec/drain.rs:131:24
   = note: inside `std::ptr::drop_in_place::<std::vec::Drain<i32>> - shim(Some(std::vec::Drain<i32>))` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:188:1
   = note: inside `std::ptr::drop_in_place::<std::vec::Splice<std::array::IntoIter<i32, 2_usize>>> - shim(Some(std::vec::Splice<std::array::IntoIter<i32, 2_usize>>))` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:188:1
note: inside `main` at src/main.rs:3:27
  --> src/main.rs:3:27
   |
3  |     v.splice(1..2, [1, 2]);
   |                           ^
   = note: inside `<fn() as std::ops::FnOnce<()>>::call_once - shim(fn())` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227:5
   = note: inside `std::sys_common::backtrace::__rust_begin_short_backtrace::<fn(), ()>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys_common/backtrace.rs:123:18
   = note: inside closure at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:145:18
   = note: inside `std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:259:13
   = note: inside `std::panicking::r#try::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:406:40
   = note: inside `std::panicking::r#try::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:370:19
   = note: inside `std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:133:14
   = note: inside closure at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:128:48
   = note: inside `std::panicking::r#try::do_call::<[closure@std::rt::lang_start_internal::{closure#2}], isize>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:406:40
   = note: inside `std::panicking::r#try::<isize, [closure@std::rt::lang_start_internal::{closure#2}]>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:370:19
   = note: inside `std::panic::catch_unwind::<[closure@std::rt::lang_start_internal::{closure#2}], isize>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:133:14
   = note: inside `std::rt::lang_start_internal` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:128:20
   = note: inside `std::rt::lang_start::<()>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:144:17

error: aborting due to previous error

Originally found by miri-test-libstd. See this thread for more.

@camelid camelid added A-collections Area: std::collections. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness C-bug Category: This is a bug. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Dec 11, 2021
@rustbot rustbot added the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Dec 11, 2021
@camelid
Copy link
Member Author

camelid commented Dec 11, 2021

I suspect this is a regression from 2d8a11b (#85157, cc @the8472), but I have not yet bisected.

@camelid camelid added E-needs-bisection Call for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustc regression-from-stable-to-nightly Performance or correctness regression from stable to nightly. labels Dec 11, 2021
@the8472
Copy link
Member

the8472 commented Dec 11, 2021

@rustbot claim

@the8472
Copy link
Member

the8472 commented Dec 11, 2021

I have added debug printing for the slice length to Drain::drop and the test test_replace_range reported by @RalfJung prints that the iterator is zero-length, which means a zero-length slice is created.

ptr::slice_from_raw_parts refers to slice::from_raw_parts regarding safety requirements, which in turn states

data must be non-null and aligned even for zero-length slices. One reason for this is that enum layout optimizations may rely on references (including slices of any length) being aligned and non-null to distinguish them from other data. You can obtain a pointer that is usable as data for zero-length slices using NonNull::dangling().

Which I interpret as any aligned, non-null pointer is valid for zero-length slices, which includes recently freed allocations.

So this seems to be a false positive caused by miri not taking the slice length into account.

@RalfJung
Copy link
Member

Which I interpret as any aligned, non-null pointer is valid for zero-length slices, which includes recently freed allocations.

That is not the case, unfortunately. Quoting from https://doc.rust-lang.org/nightly/core/ptr/index.html:

Even for operations of size zero, the pointer must not be pointing to deallocated memory, i.e., deallocation makes pointers invalid even for zero-sized operations. However, casting any non-zero integer literal to a pointer is valid for zero-sized accesses, even if some memory happens to exist at that address and gets deallocated. This corresponds to writing your own allocator: allocating zero-sized objects is not very hard. The canonical way to obtain a pointer that is valid for zero-sized accesses is NonNull::dangling.

I am not happy with this rule, but currently, LLVM does not really let us a choice here (rust-lang/unsafe-code-guidelines#93).

@camelid
Copy link
Member Author

camelid commented Dec 11, 2021

Added 1.59.0 milestone since that's my understanding of when the regression occurred.

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Dec 11, 2021
…imulacrum

Fix zero-sized reference to deallocated memory

fixes rust-lang#91772

r? `@camelid`
@bors bors closed this as completed in 9063b64 Dec 12, 2021
@apiraino
Copy link
Contributor

Removing prioritization label as per discussion on Zulip

@rustbot label -I-prioritize

@rustbot rustbot removed the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Dec 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-collections Area: std::collections. C-bug Category: This is a bug. E-needs-bisection Call for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustc I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness regression-from-stable-to-nightly Performance or correctness regression from stable to nightly. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants