On Sun, Jun 22, 2025 at 06:40:40PM +0200, Danilo Krummrich wrote: > So far Devres uses an inner memory allocation and reference count, i.e. > an inner Arc, in order to ensure that the devres callback can't run into > a use-after-free in case where the Devres object is dropped while the > devres callback runs concurrently. > > Instead, use a completion in order to avoid a potential UAF: In > Devres::drop(), if we detect that we can't remove the devres action > anymore, we wait for the completion that is completed from the devres > callback. If, in turn, we were able to successfully remove the devres > action, we can just go ahead. > > This, again, allows us to get rid of the internal Arc, and instead let I like the idea ;-) > Devres consume an `impl PinInit<T, E>` in order to return an > `impl PinInit<Devres<T>, E>`, which enables us to get away with less > memory allocations. > > Additionally, having the resulting explicit synchronization in > Devres::drop() prevents potential subtle undesired side effects of the > devres callback dropping the final Arc reference asynchronously within > the devres callback. > > Signed-off-by: Danilo Krummrich <dakr@xxxxxxxxxx> > --- [...] > +impl<T> Devres<T> { [...] > > #[allow(clippy::missing_safety_doc)] > unsafe extern "C" fn devres_callback(ptr: *mut kernel::ffi::c_void) { > - let ptr = ptr as *mut DevresInner<T>; > - // Devres owned this memory; now that we received the callback, drop the `Arc` and hence the > - // reference. > - // SAFETY: Safe, since we leaked an `Arc` reference to devm_add_action() in > - // `DevresInner::new`. > - let inner = unsafe { Arc::from_raw(ptr) }; > + // SAFETY: In `Self::new` we've passed a valid pointer to `Inner` to `devm_add_action()`, > + // hence `ptr` must be a valid pointer to `Inner`. I think you also need to mention that `inner` only remains valid until `inner.devm.complete_all()` unblocks `Devres::drop()`, because after `Devres::drop()`'s `devm.wait_for_completion()` returns, `inner` may be dropped or freed. Regards, Boqun > + let inner = unsafe { &*ptr.cast::<Inner<T>>() }; > > if !inner.data.revoke() { > // If `revoke()` returns false, it means that `Devres::drop` already started revoking > - // `inner.data` for us. Hence we have to wait until `Devres::drop()` signals that it > - // completed revoking `inner.data`. > + // `data` for us. Hence we have to wait until `Devres::drop` signals that it > + // completed revoking `data`. > inner.revoke.wait_for_completion(); > } [...] > + // Signal that we're done using `inner`. > + inner.devm.complete_all(); > + } [...]