mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(allocator): fix unsound lifetime extension in Box::new_in (#23685)
Fix unsoundness in `Box::new_in`.
`Box::new_in` had this signature:
```rust
impl<T> Box<'_, T> {
pub fn new_in(value: T, allocator: &Allocator) -> Self {
Self(NonNull::from(allocator.alloc(value)), PhantomData)
}
}
```
The returned `Box<'alloc, T>`'s lifetime `'alloc` and the `&Allocator` borrow were two independent, unconstrained lifetimes. Nothing tied them together, so the caller was free to choose any `'alloc` - including one that outlives the allocator. In effect you could mint a `Box<'static, T>` from a temporary `Allocator`:
```rust
let boxed = {
let allocator = Allocator::default();
Box::new_in(5, &allocator) // returns Box<'_, i32> with a lifetime unrelated to `allocator`
}; // `allocator` dropped here, freeing the backing memory
assert_eq!(*boxed, 5); // use after free
```
Fix by tying the 2 lifetimes together.
```rust
impl<'alloc, T> Box<'alloc, T> {
pub fn new_in(value: T, allocator: &'alloc Allocator) -> Self { ... }
}
```
This bug crept in in #2943 and has gone unnoticed since - largely because we rarely use `Box::new_in` directly, and `AstBuilder::alloc` was correctly providing a tighter lifetime bound.
This commit is contained in:
1 parent
b1b83d5b9f
commit
7231d5542f
1 file changed
+16
-4
@@ -41,23 +41,35 @@ impl<T: ?Sized> Box<'_, T> {
|
||||
assert!(!std::mem::needs_drop::<T>(), "Cannot create a Box<T> where T is a Drop type");
|
||||
}
|
||||
|
||||
impl<T> Box<'_, T> {
|
||||
/// Put a `value` into a memory arena and get back a [`Box`] with ownership
|
||||
/// to the allocation.
|
||||
impl<'alloc, T> Box<'alloc, T> {
|
||||
/// Allocate `value` into the memory arena, and receive a [`Box`] which owns the value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use oxc_allocator::{Allocator, Box};
|
||||
///
|
||||
/// let arena = Allocator::default();
|
||||
/// let in_arena: Box<i32> = Box::new_in(5, &arena);
|
||||
/// ```
|
||||
///
|
||||
/// The `Box` cannot outlive the `Allocator`. This fails to compile:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use oxc_allocator::{Allocator, Box};
|
||||
///
|
||||
/// let boxed = {
|
||||
/// let allocator = Allocator::default();
|
||||
/// Box::new_in(5, &allocator)
|
||||
/// };
|
||||
/// assert_eq!(*boxed, 5);
|
||||
/// ```
|
||||
//
|
||||
// `#[inline(always)]` because this is a hot path and `Allocator::alloc` is a very small function.
|
||||
// We always want it to be inlined.
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
pub fn new_in(value: T, allocator: &Allocator) -> Self {
|
||||
pub fn new_in(value: T, allocator: &'alloc Allocator) -> Self {
|
||||
const { Self::ASSERT_T_IS_NOT_DROP };
|
||||
|
||||
Self(NonNull::from(allocator.alloc(value)), PhantomData)
|
||||
|
||||
Reference in new issue
Block a user