Hello — while researching panic-safety in Rust crates, I found that several
public APIs in fastvec 2.0.0 are not panic-safe. Each is reachable from safe
Rust and leads to a double-free (CWE-415) / use-after-free (CWE-416).
There are two root causes.
Destruction before the length commit
clear and truncate destroy elements with drop_in_place and set the length
metadata only afterwards (self.len, or len_and_flag for SmallVec). An
element's Drop is user-controlled and may panic; if it does, the length update
is skipped and the container keeps a stale length that still identifies
already-destroyed elements as live. When the container is later dropped, its Drop
trusts the stale length and destroys those elements a second time.
Representative shape:
ptr::drop_in_place(elements); // T::drop() may panic
self.len = new_len; // skipped on unwind -> stale len
Affected methods (this class):
- FastVecData::clear
- FastVecData::truncate
- ArrayVec::clear
- ArrayVec::truncate
- SmallVec::clear
- SmallVec::truncate
(The FastVecData methods are reachable through FastVec::data().)
Non-destructive compaction before the length commit (ArrayVec::retain_mut)
ArrayVec::retain_mut iterates 0..self.len directly, compacts kept elements
toward the front with ptr::copy, and commits self.len only after the loop —
with no pre-loop guard. It has two panic points:
- A removed element's
Drop panics at drop_in_place (the first class above).
- The predicate
f panics after a compaction. ptr::copy does not invalidate the
source slot, so once a kept element is compacted the same value exists in two
slots both inside 0..self.len; if f then unwinds, the duplicate stays live
and is dropped a second time. In this path no element Drop needs to panic —
the predicate alone is enough (unwrap(), indexing, assert!).
for index in 0..self.len {
let dst = base_ptr.add(index);
if f(&mut *dst) { // predicate may unwind
ptr::copy(dst, base_ptr.add(count), 1); // duplicates the value
count += 1;
} else {
ptr::drop_in_place(dst); // element Drop may panic
}
}
self.len = count; // skipped on unwind
Affected methods:
- ArrayVec::retain_mut (
src/array.rs:706)
- ArrayVec::retain (delegates to
retain_mut)
Two things worth noting for retain_mut:
ArrayVec declares impl UnwindSafe for ArrayVec<T, N> where T: UnwindSafe
(src/array.rs:64), which this behaviour contradicts — a panic in retain_mut
leaves the container in a state that double-frees on drop.
- The crate already guards its other
retain_mut implementations by setting the
length to zero before the loop (self.len = 0; // Ensure safety if panicked),
so the same guard applied here would fix it.
Trigger conditions
- The first class: an element whose
Drop can panic, plus a call to the affected
method.
ArrayVec::retain_mut: any element that owns memory, plus a predicate that can
panic. No panicking Drop is required.
All are reachable from safe Rust with unwinding panics and catch_unwind.
Affected versions
Confirmed on 2.0.0.
Thank you for your time.
Hello — while researching panic-safety in Rust crates, I found that several
public APIs in
fastvec2.0.0 are not panic-safe. Each is reachable from safeRust and leads to a double-free (CWE-415) / use-after-free (CWE-416).
There are two root causes.
Destruction before the length commit
clearandtruncatedestroy elements withdrop_in_placeand set the lengthmetadata only afterwards (
self.len, orlen_and_flagforSmallVec). Anelement's
Dropis user-controlled and may panic; if it does, the length updateis skipped and the container keeps a stale length that still identifies
already-destroyed elements as live. When the container is later dropped, its
Droptrusts the stale length and destroys those elements a second time.
Representative shape:
Affected methods (this class):
(The
FastVecDatamethods are reachable throughFastVec::data().)Non-destructive compaction before the length commit (
ArrayVec::retain_mut)ArrayVec::retain_mutiterates0..self.lendirectly, compacts kept elementstoward the front with
ptr::copy, and commitsself.lenonly after the loop —with no pre-loop guard. It has two panic points:
Droppanics atdrop_in_place(the first class above).fpanics after a compaction.ptr::copydoes not invalidate thesource slot, so once a kept element is compacted the same value exists in two
slots both inside
0..self.len; iffthen unwinds, the duplicate stays liveand is dropped a second time. In this path no element
Dropneeds to panic —the predicate alone is enough (
unwrap(), indexing,assert!).Affected methods:
src/array.rs:706)retain_mut)Two things worth noting for
retain_mut:ArrayVecdeclaresimpl UnwindSafe for ArrayVec<T, N> where T: UnwindSafe(
src/array.rs:64), which this behaviour contradicts — a panic inretain_mutleaves the container in a state that double-frees on drop.
retain_mutimplementations by setting thelength to zero before the loop (
self.len = 0; // Ensure safety if panicked),so the same guard applied here would fix it.
Trigger conditions
Dropcan panic, plus a call to the affectedmethod.
ArrayVec::retain_mut: any element that owns memory, plus a predicate that canpanic. No panicking
Dropis required.All are reachable from safe Rust with unwinding panics and
catch_unwind.Affected versions
Confirmed on 2.0.0.
Thank you for your time.