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

Unsound in the presence of contravariant or invariant lifetimes #1

Open
dtolnay opened this issue Apr 13, 2021 · 1 comment
Open

Unsound in the presence of contravariant or invariant lifetimes #1

dtolnay opened this issue Apr 13, 2021 · 1 comment

Comments

@dtolnay
Copy link

dtolnay commented Apr 13, 2021

Here is an example of Undefined Behavior (use after free) in safe code caused by escher.

use escher::{Escher, Rebindable};
use std::cell::Cell;

#[derive(Rebindable)]
struct Struct<'a>(fn(&'a String));

fn main() {
    static STRING: String = String::new();
    thread_local!(static CELL: Cell<&'static String> = Cell::new(&STRING));
    let escher = Escher::new(|r| async {
        r.capture(Struct(|x| CELL.with(|cell| cell.set(x)))).await;
    });
    let mut string = Ok(".".repeat(3));
    let f = escher.as_ref().0;
    let s = string.as_ref().unwrap();
    f(s);
    string = Err((s.as_ptr(), 100usize, 100usize));
    CELL.with(|cell| println!("{}", cell.get()));
    string.unwrap_err();
}

This should print a variety of interesting characters, such as:

$ cargo run
p*cOVQh@nMVpJծUQhð¢ӮUú-®)VQhú­)V

$ cargo run --release
p¨]UQ°¨]U

To provoke a segfault instead, you can replace s.as_ptr() with 0usize.

petrosagg added a commit that referenced this issue Apr 13, 2021
partial fix for #1

By implementing a function that rebind the lifetime of values we're
forcing the compiler to check that the types are covariant

Signed-off-by: Petros Angelatos <[email protected]>
@petrosagg
Copy link
Owner

@dtolnay thank you so much for taking a look! I was planning of posting about this method in the forum soon after improving the explanation.

Regarding the issue, I added a rebind method that rebinds values (whereas the Rebind type alias rebinds types). The generated implementation looks like this:

impl Rebindable for SomeType {
    fn rebind<'short, 'long: 'short>(&'long self) -> &'short Rebind<'short, Self>
    where Self: 'long
    {
        self
    }
}

The above acts as a covariance proof as it only typechecks if SomeType is covariant over its lifetimes and so the compiler will now complain if you attempt to implement this trait on a contravariant or invariant type.

However, the same approach doesn't work for mutable references because a &mut self receiver is invariant over the lifetime of self. I need to think about it more so for now I have removed the implementation of as_mut().

I'm curious of your thoughts on the general approach though, specifically if it is fundamentally unsound or if there is something along these lines.

Thank you again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants