Skip to content

Stacked Borrows violation in BufWriter::into_parts #127584

Closed
@CAD97

Description

I tried this code: [playground]

use std::io::{Cursor, BufWriter};

fn main() {
    let mut v = vec![0; 1024];
    let c = Cursor::new(&mut v);
    let w = BufWriter::new(Box::new(c));
    let _ = w.into_parts();
}

I expected to see this happen: it runs cleanly under Miri.

Instead, this happened:

error: Undefined Behavior: trying to retag from <2686> for Unique permission at alloc1164[0x0], but that tag does not exist in the borrow stack for this location
   --> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/buffered/bufwriter.rs:175:10
    |
175 |         (inner, buf)
    |          ^^^^^
    |          |
    |          trying to retag from <2686> for Unique permission at alloc1164[0x0], but that tag does not exist in the borrow stack for this location
    |          this error occurs as part of retag at alloc1164[0x0..0x10]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <2686> was created by a Unique retag at offsets [0x0..0x10]
   --> src/main.rs:7:13
    |
7   |     let _ = w.into_parts();
    |             ^^^^^^^^^^^^^^
help: <2686> was later invalidated at offsets [0x0..0x10] by a Unique retag (of a reference/box inside this compound value)
   --> src/main.rs:7:13
    |
7   |     let _ = w.into_parts();
    |             ^^^^^^^^^^^^^^
    = note: BACKTRACE (of the first span):
    = note: inside `std::io::BufWriter::<std::boxed::Box<std::io::Cursor<&mut std::vec::Vec<u8>>>>::into_parts` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/buffered/bufwriter.rs:175:10: 175:15
note: inside `main`
   --> src/main.rs:7:13
    |
7   |     let _ = w.into_parts();
    |             ^^^^^^^^^^^^^^

The issue is in this construct:

https://github.com/rust-lang/rust/blob/master/library%2Fstd%2Fsrc%2Fio%2Fbuffered%2Fbufwriter.rs#L171-L173

By the SB model, moving a (struct directly containing) Box retags that Box as the live unique owner of the referenced allocation. Thus passing self to forget invalidates the duplicated Box (W) we just retrieved. The correct way to write this is with ManuallyDrop:

// inner is the only remaining field with nontrivial drop glue
let this = ManuallyDrop::new(self);
// SAFETY: this takes logical ownership of inner
let inner = unsafe { ptr::read(&this.inner) };

Meta

Tested against 1.81.0-nightly (2024-07-09 6be96e3).

This also catches BufWriter::into_inner, which is implemented using into_parts.

IIRC the LLVM backend is currently not informed about this instance of this UB, as the relevant attributes are only added when the argument passing ABI is Scalar or ScalarPair, which it is not in this case. Additionally, Miri's SB implementation does not complain when the box is Box<dyn Write>, (conjecture: since dyn Write is not Unpin).

cc @RalfJung — another case of read; forget instead of ManuallyDrop::new; read in std.

This was randomly spotted. It might be worth it to do a grep for forget to try to spot further such mistakes. forget's docs call this out as incorrect, but it's still quite easy to overlook.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

C-bugCategory: This is a bug.T-libsRelevant to the library team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions