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 thatT : '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