Skip to content

Commit d7b998f

Browse files
authored
fix(tauri): deprecate Manager::unmanage to fix use-after-free (#12723)
close #12721
1 parent d9a07e6 commit d7b998f

File tree

3 files changed

+76
-44
lines changed

3 files changed

+76
-44
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
tauri: 'minor:bug'
3+
---
4+
5+
Deprecate `Manager::unmanage` to fix `use-after-free` unsoundness, see tauri-apps/tauri#12721 for details.

crates/tauri/src/lib.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -711,11 +711,31 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
711711
}
712712

713713
/// Removes the state managed by the application for T. Returns the state if it was actually removed.
714+
///
715+
/// <div class="warning">
716+
///
717+
/// This method is *UNSAFE* and calling it will cause previously obtained references through
718+
/// [Manager::state] and [State::inner] to become dangling references.
719+
///
720+
/// It is currently deprecated and may be removed in the future.
721+
///
722+
/// If you really want to unmanage a state, use [std::sync::Mutex] and [Option::take] to wrap the state instead.
723+
///
724+
/// See [tauri-apps/tauri#12721] for more information.
725+
///
726+
/// [tauri-apps/tauri#12721]: https://github.com/tauri-apps/tauri/issues/12721
727+
///
728+
/// </div>
729+
#[deprecated(
730+
since = "2.3.0",
731+
note = "This method is unsafe, since it can cause dangling references."
732+
)]
714733
fn unmanage<T>(&self) -> Option<T>
715734
where
716735
T: Send + Sync + 'static,
717736
{
718-
self.manager().state().unmanage()
737+
// The caller decides to break the safety here, then OK, just let it go.
738+
unsafe { self.manager().state().unmanage() }
719739
}
720740

721741
/// Retrieves the managed state for the type `T`.

crates/tauri/src/state.rs

+50-43
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
use std::{
66
any::{Any, TypeId},
7-
cell::UnsafeCell,
87
collections::HashMap,
98
hash::BuildHasherDefault,
9+
pin::Pin,
1010
sync::Mutex,
1111
};
1212

@@ -97,56 +97,56 @@ impl std::hash::Hasher for IdentHash {
9797
}
9898
}
9999

100-
type TypeIdMap = HashMap<TypeId, Box<dyn Any>, BuildHasherDefault<IdentHash>>;
100+
/// Safety:
101+
/// - The `key` must equal to `(*value).type_id()`, see the safety doc in methods of [StateManager] for details.
102+
/// - Once you insert a value, you can't remove/mutated/move it anymore, see [StateManager::try_get] for details.
103+
type TypeIdMap = HashMap<TypeId, Pin<Box<dyn Any + Sync + Send>>, BuildHasherDefault<IdentHash>>;
101104

102105
/// The Tauri state manager.
103106
#[derive(Debug)]
104107
pub struct StateManager {
105-
map: Mutex<UnsafeCell<TypeIdMap>>,
108+
map: Mutex<TypeIdMap>,
106109
}
107110

108-
// SAFETY: data is accessed behind a lock
109-
unsafe impl Sync for StateManager {}
110-
unsafe impl Send for StateManager {}
111-
112111
impl StateManager {
113112
pub(crate) fn new() -> Self {
114113
Self {
115114
map: Default::default(),
116115
}
117116
}
118117

119-
fn with_map_ref<'a, F: FnOnce(&'a TypeIdMap) -> R, R>(&'a self, f: F) -> R {
120-
let map = self.map.lock().unwrap();
121-
let map = map.get();
122-
// SAFETY: safe to access since we are holding a lock
123-
f(unsafe { &*map })
124-
}
125-
126-
fn with_map_mut<F: FnOnce(&mut TypeIdMap) -> R, R>(&self, f: F) -> R {
127-
let mut map = self.map.lock().unwrap();
128-
let map = map.get_mut();
129-
f(map)
130-
}
131-
132118
pub(crate) fn set<T: Send + Sync + 'static>(&self, state: T) -> bool {
133-
self.with_map_mut(|map| {
134-
let type_id = TypeId::of::<T>();
135-
let already_set = map.contains_key(&type_id);
136-
if !already_set {
137-
map.insert(type_id, Box::new(state) as Box<dyn Any>);
138-
}
139-
!already_set
140-
})
119+
let mut map = self.map.lock().unwrap();
120+
let type_id = TypeId::of::<T>();
121+
let already_set = map.contains_key(&type_id);
122+
if !already_set {
123+
let ptr = Box::new(state) as Box<dyn Any + Sync + Send>;
124+
let pinned_ptr = Box::into_pin(ptr);
125+
map.insert(
126+
type_id,
127+
// SAFETY: keep the type of the key is the same as the type of the value,
128+
// see [try_get] methods for details.
129+
pinned_ptr,
130+
);
131+
}
132+
!already_set
141133
}
142134

143-
pub(crate) fn unmanage<T: Send + Sync + 'static>(&self) -> Option<T> {
144-
self.with_map_mut(|map| {
145-
let type_id = TypeId::of::<T>();
146-
map
147-
.remove(&type_id)
148-
.and_then(|ptr| ptr.downcast().ok().map(|b| *b))
149-
})
135+
/// SAFETY: Calling this method will move the `value`,
136+
/// which will cause references obtained through [Self::try_get] to dangle.
137+
pub(crate) unsafe fn unmanage<T: Send + Sync + 'static>(&self) -> Option<T> {
138+
let mut map = self.map.lock().unwrap();
139+
let type_id = TypeId::of::<T>();
140+
let pinned_ptr = map.remove(&type_id)?;
141+
// SAFETY: The caller decides to break the immovability/safety here, then OK, just let it go.
142+
let ptr = unsafe { Pin::into_inner_unchecked(pinned_ptr) };
143+
let value = unsafe {
144+
ptr
145+
.downcast::<T>()
146+
// SAFETY: the type of the key is the same as the type of the value
147+
.unwrap_unchecked()
148+
};
149+
Some(*value)
150150
}
151151

152152
/// Gets the state associated with the specified type.
@@ -158,12 +158,18 @@ impl StateManager {
158158

159159
/// Gets the state associated with the specified type.
160160
pub fn try_get<T: Send + Sync + 'static>(&self) -> Option<State<'_, T>> {
161-
self.with_map_ref(|map| {
162-
map
163-
.get(&TypeId::of::<T>())
164-
.and_then(|ptr| ptr.downcast_ref::<T>())
165-
.map(State)
166-
})
161+
let map = self.map.lock().unwrap();
162+
let type_id = TypeId::of::<T>();
163+
let ptr = map.get(&type_id)?;
164+
let value = unsafe {
165+
ptr
166+
.downcast_ref::<T>()
167+
// SAFETY: the type of the key is the same as the type of the value
168+
.unwrap_unchecked()
169+
};
170+
// SAFETY: We ensure the lifetime of `value` is the same as [StateManager] and `value` will not be mutated/moved.
171+
let v_ref = unsafe { &*(value as *const T) };
172+
Some(State(v_ref))
167173
}
168174
}
169175

@@ -197,8 +203,9 @@ mod tests {
197203
let state = StateManager::new();
198204
assert!(state.set(1u32));
199205
assert_eq!(*state.get::<u32>(), 1);
200-
assert!(state.unmanage::<u32>().is_some());
201-
assert!(state.unmanage::<u32>().is_none());
206+
// safety: the reference returned by `try_get` is already dropped.
207+
assert!(unsafe { state.unmanage::<u32>() }.is_some());
208+
assert!(unsafe { state.unmanage::<u32>() }.is_none());
202209
assert_eq!(state.try_get::<u32>(), None);
203210
assert!(state.set(2u32));
204211
assert_eq!(*state.get::<u32>(), 2);

0 commit comments

Comments
 (0)