From c8c4c96f92a87403ecbae61c29b8a5674b49b2e5 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 8 Apr 2026 23:27:43 +0100 Subject: [PATCH 1/2] Add Miri test for use-after-free in DrainFilter::keep_rest keep_rest() checks mem::size_of::() (the Array type) instead of mem::size_of::() (the element type) to decide whether to backshift tail elements. For SmallVec<[T; 0]>, the array size is always 0, so the backshift is skipped and subsequent accesses read freed memory. This test triggers the bug under Miri. Co-Authored-By: Claude --- src/tests.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/tests.rs b/src/tests.rs index 88c5f32..8907421 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1040,6 +1040,29 @@ fn drain_keep_rest() { assert_eq!(a, SmallVec::<[i32; 3]>::from_slice(&[1i32, 3, 5, 6, 7, 8])); } +// Regression test: keep_rest on a SmallVec with zero inline capacity must +// still move tail elements to fill the gap left by drained items. +// Using Box so that Miri detects the use-after-move / double-drop +// caused by the missing backshift. +#[cfg(feature = "drain_keep_rest")] +#[test] +fn drain_keep_rest_zero_inline_capacity() { + let mut a: SmallVec<[Box; 0]> = SmallVec::new(); + for i in 1u8..=8 { + a.push(Box::new(i)); + } + + let mut df = a.drain_filter(|x| **x % 2 == 0); + + assert_eq!(*df.next().unwrap(), 2); + assert_eq!(*df.next().unwrap(), 4); + + df.keep_rest(); + + let values: Vec = a.iter().map(|b| **b).collect(); + assert_eq!(values, vec![1, 3, 5, 6, 7, 8]); +} + /// This assortment of tests, in combination with miri, verifies we handle UB on fishy arguments /// given to SmallVec. Draining and extending the allocation are fairly well-tested earlier, but /// `smallvec.insert(usize::MAX, val)` once slipped by! From d60e80e221c57b588cf974d76a56922788fdd074 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 8 Apr 2026 23:32:08 +0100 Subject: [PATCH 2/2] Fix use-after-free in DrainFilter::keep_rest for zero-capacity SmallVecs Check mem::size_of::() (the element type) instead of mem::size_of::() (the Array type) when deciding whether to backshift tail elements. The previous code skipped the backshift for SmallVec<[T; 0]> because the array [T; 0] has size 0, leaving dangling data accessible through the SmallVec. Co-Authored-By: Claude --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3259774..aff8401 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -602,7 +602,7 @@ where unsafe { // ZSTs have no identity, so we don't need to move them around. - let needs_move = mem::size_of::() != 0; + let needs_move = mem::size_of::() != 0; if needs_move && this.idx < this.old_len && this.del > 0 { let ptr = this.vec.as_mut_ptr();