You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fnfoo(app: tauri::App){use tauri::Manager;#[derive(Debug)]structFoo(i32);
app.manage(Foo(0));let state = app.state::<Foo>().inner();letmut returned_state = app.unmanage::<Foo>().unwrap();
returned_state.0 = 42;println!("{}", state.0);// 👈 encountered a dangling reference (use-after-free)}
While retaining a reference to the state, removing the managed state from the manager will result in a dangling pointer.
Reproduction
Here is an equivalent implementation, run it with miri (miri cannot directly run tauri):
use std::{
any::{Any,TypeId},
cell::UnsafeCell,
collections::HashMap,
hash::BuildHasherDefault,
sync::{Arc,Mutex},};/// A guard for a state value.////// See [`Manager::manage`](`crate::Manager::manage`) for usage examples.pubstructState<'r,T:Send + Sync + 'static>(&'rT);impl<'r,T:Send + Sync + 'static>State<'r,T>{/// Retrieve a borrow to the underlying value with a lifetime of `'r`./// Using this method is typically unnecessary as `State` implements/// [`std::ops::Deref`] with a [`std::ops::Deref::Target`] of `T`.#[inline(always)]pubfninner(&self) -> &'rT{self.0}}impl<T:Send + Sync + 'static> std::ops::DerefforState<'_,T>{typeTarget = T;#[inline(always)]fnderef(&self) -> &T{self.0}}impl<T:Send + Sync + 'static>CloneforState<'_,T>{fnclone(&self) -> Self{State(self.0)}}impl<T:Send + Sync + 'static + PartialEq>PartialEqforState<'_,T>{fneq(&self,other:&Self) -> bool{self.0 == other.0}}impl<T:Send + Sync + std::fmt::Debug> std::fmt::DebugforState<'_,T>{fnfmt(&self,f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result{
f.debug_tuple("State").field(&self.0).finish()}}#[derive(Default)]structIdentHash(u64);impl std::hash::HasherforIdentHash{fnfinish(&self) -> u64{self.0}fnwrite(&mutself,bytes:&[u8]){for byte in bytes {self.write_u8(*byte);}}fnwrite_u8(&mutself,i:u8){self.0 = (self.0 << 8) | (i asu64);}fnwrite_u64(&mutself,i:u64){self.0 = i;}}typeTypeIdMap = HashMap<TypeId,Box<dynAny>,BuildHasherDefault<IdentHash>>;/// The Tauri state manager.#[derive(Debug)]pubstructStateManager{map:Mutex<UnsafeCell<TypeIdMap>>,}// SAFETY: data is accessed behind a lockunsafeimplSyncforStateManager{}unsafeimplSendforStateManager{}implStateManager{pub(crate)fnnew() -> Self{Self{map:Default::default(),}}fnwith_map_ref<'a,F:FnOnce(&'aTypeIdMap) -> R,R>(&'aself,f:F) -> R{let map = self.map.lock().unwrap();let map = map.get();// SAFETY: safe to access since we are holding a lockf(unsafe{&*map })}fnwith_map_mut<F:FnOnce(&mutTypeIdMap) -> R,R>(&self,f:F) -> R{letmut map = self.map.lock().unwrap();let map = map.get_mut();f(map)}pub(crate)fnset<T:Send + Sync + 'static>(&self,state:T) -> bool{self.with_map_mut(|map| {let type_id = TypeId::of::<T>();let already_set = map.contains_key(&type_id);if !already_set {
map.insert(type_id,Box::new(state)asBox<dynAny>);}
!already_set
})}pub(crate)fnunmanage<T:Send + Sync + 'static>(&self) -> Option<T>{self.with_map_mut(|map| {let type_id = TypeId::of::<T>();
map.remove(&type_id).and_then(|ptr| ptr.downcast().ok().map(|b| *b))})}/// Gets the state associated with the specified type.pubfnget<T:Send + Sync + 'static>(&self) -> State<'_,T>{self.try_get().expect("state: get() when given type is not managed")}/// Gets the state associated with the specified type.pubfntry_get<T:Send + Sync + 'static>(&self) -> Option<State<'_,T>>{self.with_map_ref(|map| {
map.get(&TypeId::of::<T>()).and_then(|ptr| ptr.downcast_ref::<T>()).map(State)})}}fnmain(){#[derive(Debug)]structFoo(i32);let state_manager = Arc::new(StateManager::new());
state_manager.set(Foo(0));// manage the statelet state = state_manager.try_get::<Foo>().unwrap().inner();// then get the stateletmut returned_state = state_manager.unmanage::<Foo>().unwrap();// unmanage the state
returned_state.0 = 42;println!("{}", state.0);// 👈 encountered a dangling reference (use-after-free)}
It allows retaining a reference to the Hashmap value after the MutexGuard is dropped.
Full tauri info output
rust 1.82 stable
tauri 2.2.5
Stack trace
cargo +nightly miri run
error: Undefined Behavior: out-of-bounds pointer use: alloc1105 has been freed, so this pointer is dangling
--> src/main.rs:152:20
|
152 | println!("{}", state.0); // 👈 encountered a dangling reference (use-after-free)
| ^^^^^^^ out-of-bounds pointer use: alloc1105 has been freed, so this pointer is dangling
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
help: alloc1105 was allocated here:
--> src/main.rs:111:37
|
| ^^^^^^^^^^^^^^^
help: alloc1105 was deallocated here:
--> src/main.rs:121:62
|
121 | .and_then(|ptr| ptr.downcast().ok().map(|b| *b))
| ^
= note: BACKTRACE (of the first span):
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error
Additional context
No response
The text was updated successfully, but these errors were encountered:
Describe the bug
While retaining a reference to the state, removing the managed state from the manager will result in a dangling pointer.
Reproduction
Here is an equivalent implementation, run it with miri (miri cannot directly run tauri):
Expected behavior
The unsoundness comes from this line:
tauri/crates/tauri/src/state.rs
Line 123 in 3f68058
It allows retaining a reference to the Hashmap value after the
MutexGuard
is dropped.Full
tauri info
outputStack trace
cargo +nightly miri run
Additional context
No response
The text was updated successfully, but these errors were encountered: