rust-for-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* pinned_init vs. miri?
@ 2024-11-05 12:50 Paolo Bonzini
  2024-11-19 10:11 ` Alice Ryhl
  0 siblings, 1 reply; 2+ messages in thread
From: Paolo Bonzini @ 2024-11-05 12:50 UTC (permalink / raw)
  To: rust-for-linux, y86-dev

[-- Attachment #1: Type: text/plain, Size: 2523 bytes --]

Hi all,

tl;dr: while trying to learn more about pinned_init, I tried running a
simple example through miri, and I found out that it breaks.

The attached Rust code mimics a C component that calls a series of
callbacks with a void * opaque argument; plus Rust bindings for it,
that use pin_init!() to place a pointer to self in the opaque
argument.

The use of pin_init! is pretty simple, the only notable thing is that
in main() I "inlined" Box::pin_init() to clarify the miri output.

When I try to run this under miri, I get the following:

error: Undefined Behavior: trying to retag from <1750> for SharedReadOnly
permission at alloc877[0x0], but that tag does not exist in the borrow stack
for this location
   --> src/main.rs:25:37
    |
25  |         let owner: &'a T = unsafe { &*opaque.cast_const().cast::<T>() };
    |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |                                     |
    |                                     trying to retag from <1750>
for SharedReadOnly
permission at alloc877[0x0], but that tag does not exist in the borrow stack
for this location
    |                                     this error occurs as part of
retag at alloc877[0x0..0x20]
    |
help: <1750> was created by a SharedReadWrite retag at offsets [0x0..0x20]
   --> src/main.rs:99:16
    |
99  |     let slot = backend.as_mut_ptr();
    |                ^^^^^^^^^^^^^^^^^^^^
help: <1750> was later invalidated at offsets [0x0..0x20] by a Unique retag
   --> src/main.rs:101:32
    |
101 |     let mut backend = unsafe { backend.assume_init() };
    |                                ^^^^^^^


In other words, the pointer to the Box<MaybeUninit<PrintlnLogger>>,
which was stored to be used later as the opaque pointer, became
invalid when assume_init() changed the box to a Box<PrintlnLogger>. It
is then invalid to dereference it.

The cause of this is basically that assume_init() consumes the
MaybeUninit<>; which also means that the same thing happens with
std::mem::forget(), Box::leak() or Box::into_raw().

This is not _that_ surprising in the end, but I was wondering if it is
a fundamental incompatibility between the stacked borrows model and
interior pointers, or if there are ways to fix it (I can't think of
any).

To test the attached pinned_init.rs, stick it as src/main.rs with a
Cargo.toml file like this:

[package]
name = "logger"
version = "0.1.0"
edition = "2021"

[dependencies]
pinned-init = "0.0"

and compile/run it with nightly Rust.

Thanks,

Paolo

[-- Attachment #2: pinned_init.rs --]
[-- Type: text/rust, Size: 2867 bytes --]

use std::mem::MaybeUninit;
use std::os::raw::c_void;

use pinned_init::*;

/// A callback to a logging function `f`.
/// This would ordinarily be provided by C code.
pub struct LogRegistration {
    f: fn(&str, *mut c_void),
    opaque: *mut c_void,
}

/// The Rust trait that is implemented by a logging backend
/// It is wrapped by [`LogRegistration::log_fn`] to convert
/// `&self` into an opaque pointer.
pub trait LogBackendOps<'a> {
    fn log(&self, s: &str);
}

impl LogRegistration {
    fn log_fn<'a, T: 'a + LogBackendOps<'a>>(s: &str, opaque: *mut c_void) {
        // miri failure here.  The pointer was created by
        // backend.as_mut_ptr(), but then invalidated by
        // backend.assume_init()
        let owner: &'a T = unsafe { &*opaque.cast_const().cast::<T>() };
        owner.log(s);
    }

    pub fn new<'a, T: 'a + LogBackendOps<'a>>(
        owner: *const T,
    ) -> impl Init<Self> + use<'a, T> {
        init!(LogRegistration {
            f: Self::log_fn::<T>,
            opaque: owner.cast_mut().cast(),
        })
    }
}

/// A hypothetical logging component that accepts multiple backends in
/// the form of callbacks.  This would ordinarily be provided by C code.
#[derive(Default)]
pub struct Logger<'a> {
    vec: Vec<&'a LogRegistration>,
}

impl<'a> Logger<'a> {
    pub fn add_callback(&mut self, cb: &'a LogRegistration) {
        self.vec.push(cb);
    }

    pub fn log(&self, s: &str) {
        for cb in &self.vec {
            (cb.f)(s, cb.opaque);
        }
    }
}

// ----------------------------------------

/// Rust client for `Logger`.  Uses `pin_init!` to construct a
/// callback+opaque pair that points to itself and that can be
/// added to a `Logger`.
#[repr(C)]
#[pin_data]
pub struct PrintlnLogger {
    #[pin]
    backend: LogRegistration,
    pub prefix: &'static str,
}

impl LogBackendOps<'_> for PrintlnLogger {
    fn log(&self, s: &str) {
        println!("{}{}", self.prefix, s);
    }
}

impl PrintlnLogger {
    pub fn new() -> impl PinInit<Self> {
        pin_init!(&this in PrintlnLogger {
            backend <- LogRegistration::new::<Self>(this.as_ptr()),
            prefix: "test",
        })
    }

    pub fn add_to<'a>(&'a self, l: &mut Logger<'a>) {
        l.add_callback(&self.backend);
    }
}

// ----------------------------------------

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut l = Logger::default();

    // let mut backend = Box::pin_init(PrintlnLogger::new())?;
    //
    // expanded as follows for clearer miri output:
    let mut backend: Box<MaybeUninit<PrintlnLogger>> = Box::new_uninit();
    let slot = backend.as_mut_ptr();
    unsafe { PrintlnLogger::new().__pinned_init(slot)? };
    let mut backend = unsafe { backend.assume_init() };

    backend.prefix = "test: ";
    backend.add_to(&mut l);
    l.log("hello world");
    Ok(())
}

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: pinned_init vs. miri?
  2024-11-05 12:50 pinned_init vs. miri? Paolo Bonzini
@ 2024-11-19 10:11 ` Alice Ryhl
  0 siblings, 0 replies; 2+ messages in thread
From: Alice Ryhl @ 2024-11-19 10:11 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: rust-for-linux, y86-dev

On Tue, Nov 5, 2024 at 1:50 PM Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> Hi all,
>
> tl;dr: while trying to learn more about pinned_init, I tried running a
> simple example through miri, and I found out that it breaks.
>
> The attached Rust code mimics a C component that calls a series of
> callbacks with a void * opaque argument; plus Rust bindings for it,
> that use pin_init!() to place a pointer to self in the opaque
> argument.
>
> The use of pin_init! is pretty simple, the only notable thing is that
> in main() I "inlined" Box::pin_init() to clarify the miri output.
>
> When I try to run this under miri, I get the following:
>
> error: Undefined Behavior: trying to retag from <1750> for SharedReadOnly
> permission at alloc877[0x0], but that tag does not exist in the borrow stack
> for this location
>    --> src/main.rs:25:37
>     |
> 25  |         let owner: &'a T = unsafe { &*opaque.cast_const().cast::<T>() };
>     |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>     |                                     |
>     |                                     trying to retag from <1750>
> for SharedReadOnly
> permission at alloc877[0x0], but that tag does not exist in the borrow stack
> for this location
>     |                                     this error occurs as part of
> retag at alloc877[0x0..0x20]
>     |
> help: <1750> was created by a SharedReadWrite retag at offsets [0x0..0x20]
>    --> src/main.rs:99:16
>     |
> 99  |     let slot = backend.as_mut_ptr();
>     |                ^^^^^^^^^^^^^^^^^^^^
> help: <1750> was later invalidated at offsets [0x0..0x20] by a Unique retag
>    --> src/main.rs:101:32
>     |
> 101 |     let mut backend = unsafe { backend.assume_init() };
>     |                                ^^^^^^^
>
>
> In other words, the pointer to the Box<MaybeUninit<PrintlnLogger>>,
> which was stored to be used later as the opaque pointer, became
> invalid when assume_init() changed the box to a Box<PrintlnLogger>. It
> is then invalid to dereference it.
>
> The cause of this is basically that assume_init() consumes the
> MaybeUninit<>; which also means that the same thing happens with
> std::mem::forget(), Box::leak() or Box::into_raw().
>
> This is not _that_ surprising in the end, but I was wondering if it is
> a fundamental incompatibility between the stacked borrows model and
> interior pointers, or if there are ways to fix it (I can't think of
> any).
>
> To test the attached pinned_init.rs, stick it as src/main.rs with a
> Cargo.toml file like this:
>
> [package]
> name = "logger"
> version = "0.1.0"
> edition = "2021"
>
> [dependencies]
> pinned-init = "0.0"
>
> and compile/run it with nightly Rust.
>
> Thanks,
>
> Paolo

You need to mark the type `!Unpin` so that `Box`/`&mut` are not
considered unique, which avoids breaking the provenance.

Alice

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2024-11-19 10:11 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-11-05 12:50 pinned_init vs. miri? Paolo Bonzini
2024-11-19 10:11 ` Alice Ryhl

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).