Set 'static lifetime for local variables of diverging functions?

I have made the following PoC:

It grants an “arbitrary” shared reference through a closure that must diverge

  • since a diverging closure cannot use ! (it’s the never type instead of a diverging notation), I have used an empty enum for the same effect;

  • the lifetime is caller-chosen, hence the term “arbitrary”, but cannot of course “outlive the type”; i.e., for all types T of the local, the lifetime parameter 'a mut uphold that T : 'a (else &'a T doesn’t make sense);

  • an abort-on-drop bomb is used to prevent exploitation through stack unwinding; I have set up a scenario that would use-after-free otherwise (you may go an comment the let guard = ... line to see that for yourselves).

// #![feature(never_type)]
enum Diverging {}

use ::std::*;

struct AbortOnDrop;

impl Drop for AbortOnDrop { fn drop(&mut self) {
    // Triggered the abort bomb!
    process::abort();
}}

trait WithDiverging<'a> : Sized + 'a {
    fn with_diverging (
        self,
        f: impl FnOnce(&'a Self) -> Diverging,
    ) -> !
    {
        #![allow(unused_variables)]
        unsafe {
            let guard = AbortOnDrop;
            let diverged = f(mem::transmute(&self));
            match diverged {
                // !
            }
            // kabooms here if stack unwinds
            // (*before* self is dropped)
        }
    }
}

impl<'a, T : Sized + 'a> WithDiverging<'a> for T {}


fn main ()
{
    let _ = panic::catch_unwind(|| {
        // Our local
        let s = String::from("hi");
        s.with_diverging(|at_s: &'static String| {
            // can transmute to &'static since this closure diverges ...
            assert_eq!(at_s, "hi");
            thread::spawn(move || {
                // &'static is given to another thread
                // that constantly reads it
                loop {
                    thread::sleep(time::Duration::from_millis(100));
                    dbg!(at_s);
                }
            });
            thread::sleep(time::Duration::from_secs(1));
            // ... and thanks to the abortbomb guard
            panic!("Attempt at being evil");
        })
    });
    // sleep to give time to the other thread to use at_s if unwind
    thread::sleep(time::Duration::from_secs(3));
}

EDIT: the code can be changed into granting a unique reference, since it already takes ownership of T:

trait WithDiverging<'a> : Sized + 'a {
    fn with_diverging (
        self,
        f: impl FnOnce(&'a mut Self) -> Diverging,
    ) -> !

EDIT2: s/Fn/FnOnce/g

8 Likes