I have a static variable, to store callback for some system event:
static mut CALLBACK: Option<Box<dyn FnMut()>> = None;
My goal is to be able to assign my closure to this CALLBACK (by "closure" i mean closure that really captures it's environment, and cannot be casted to 'static lifetime)
So made a struct CbGuard, that works as Guard for that CALLBACK, and guarantees it's valid state.
impl CbGuard
{
fn new(user_cb: impl FnMut()) -> Self
{
unsafe{
CALLBACK = Some(Box::new(user_cb));
};
CbGuard{}
}
}
impl Drop for CbGuard
{
fn drop(&mut self) {
unsafe{
CALLBACK = None;
};
}
}
but compiler says that user_cb must be 'static. As i understand - to match lifetimes of user_cb and CALLBACK. But that is what im trying to avoid. I want to use it like that:
let mut my_var:i32 = 0;
let guard = CbGuard::new(||{
my_var = 5;
});
My thoughts:
What might be the solution?
What you think is sound is actually not. It's true that CbGuard
is the "owner", metaphorically, of the callback, but CbGuard
itself has a 'static
lifetime, so it is possible to carry it around freely. With that, it is possible to use a non-'static
closure and invoke it after it has expired.
So the compiler is right: you need 'static
. Alternatively, you can make CbGuard
generic over the callback type, which will prevent it from being used after its lifetime has expired (and it is possible for it to carry only a lifetime, not a whole type).
I'm attaching an example of exploit. Note that I'm ignoring thread safety here, which also is a severe problem.
static mut CALLBACK: Option<Box<dyn FnMut()>> = None;
struct CbGuard {}
impl CbGuard {
fn new<T: FnMut()>(cb: T) -> Self {
unsafe {
CALLBACK = Some(std::mem::transmute::<Box<dyn FnMut() + '_>, Box<dyn FnMut()>>(Box::new(cb)));
};
CbGuard {}
}
fn call(&mut self) {
unsafe { CALLBACK.as_mut().unwrap()() }
}
}
impl Drop for CbGuard {
fn drop(&mut self) {
unsafe {
CALLBACK = None;
};
}
}
fn exploit() -> CbGuard {
let s = "abc".to_owned();
let callback = || println!("{s}");
CbGuard::new(callback)
}
fn main() {
exploit().call();
}
Example of a sound CbGuard
, that also considers thread safety:
use std::marker::PhantomData;
use std::mem::transmute;
use std::sync::atomic::{AtomicBool, Ordering};
static CALLBACK_STORED: AtomicBool = AtomicBool::new(false);
static mut CALLBACK: Option<Box<dyn FnMut() + Send>> = None;
pub struct CbGuard<'a> {
_marker: PhantomData<&'a ()>,
}
impl<'a> CbGuard<'a> {
#[inline]
pub fn new<T: FnMut() + Send + 'a>(cb: T) -> Result<Self, ()> {
if CALLBACK_STORED.swap(true, Ordering::Acquire) {
// Another callback already stored.
return Err(());
}
// SAETY: We verify no other callback is stored here and locked the place, so nobody else could access it.
unsafe {
CALLBACK = Some(transmute::<
Box<dyn FnMut() + Send + '_>,
Box<dyn FnMut() + Send>,
>(Box::new(cb)));
};
Ok(CbGuard {
_marker: PhantomData,
})
}
#[inline]
pub fn call(&mut self) {
// SAFETY: We have a callback stored and we have a mutable reference so nobody else is accessing.
unsafe { CALLBACK.as_mut().unwrap_unchecked()() }
}
}
impl Drop for CbGuard<'_> {
#[inline]
fn drop(&mut self) {
// SAFETY: We have a mutable reference so nobody else can access.
unsafe {
CALLBACK = None;
}
CALLBACK_STORED.store(false, Ordering::Release);
}
}