CVE-2020-36212
Description
An issue was discovered in the abi_stable crate before 0.9.1 for Rust. DrainFilter lacks soundness because of a double drop.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A double drop in abi_stable's DrainFilter iterator causes memory corruption, enabling denial of service via crafted input.
Root
Cause
The vulnerability in the abi_stable crate (before version 0.9.1) lies in the DrainFilter iterator implementation. When the user-provided predicate returns true, the code uses ptr::read to move the element out of the vector without removing it from the allocation, and then increments a deletion counter. However, the iterator's drop implementation later unconditionally drops all elements in the original range, including those already moved out via the predicate. This results in a double free of those elements, which violates Rust's memory safety guarantees and leads to undefined behavior [1][3].
Attack
Surface
The issue is triggered through the DrainFilter API, which is a standard iterator adapter. An attacker would need to supply or control the predicate function (a closure) passed to `Vec::drain_filter` (or the equivalent in abi_stable). Because the bug is in the library code itself, any application that uses a user-controlled filter and iterates the drain results is vulnerable. No special network position or authentication is required—the attack surface is entirely in how the library is called [1][2].
Impact
Successful exploitation causes memory corruption, as the same memory region is freed twice. This can lead to a denial of service (crash) or, in some environments, potentially allow an attacker to corrupt heap metadata. The RustSec advisory assigns a CVSS score of 7.5 (High) with an attack vector of Network (the vulnerability is in a library that may be used in networked services) and a High availability impact [2][3].
Mitigation
The abi_stable project released version 0.9.1 which fixes the unsoundness by ensuring that elements moved out by the predicate are not later dropped. Users should update to abi_stable >= 0.9.1. The advisory also notes that the same issue affects the RString::retain method, which was fixed in the same release [1][3].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
abi_stablecrates.io | < 0.9.1 | 0.9.1 |
Affected products
2- Rust/abi_stabledescription
Patches
1342de83f7c260 9.1 patch (#45)
7 files changed · +147 −22
abi_stable/Cargo.toml+1 −1 modified@@ -1,6 +1,6 @@ [package] name = "abi_stable" -version = "0.9.0" +version = "0.9.1" authors = ["rodrimati1992 <rodrimatt1985@gmail.com>"] edition="2018" license = "MIT/Apache-2.0"
abi_stable/src/std_types/string.rs+11 −7 modified@@ -22,7 +22,8 @@ use crate::std_types::{RStr, RVec}; mod iters; -#[cfg(all(test,not(feature="only_new_tests")))] +#[cfg(test)] +// #[cfg(all(test,not(feature="only_new_tests")))] mod tests; pub use self::iters::{Drain, IntoIter}; @@ -643,10 +644,12 @@ impl RString { let mut del_bytes = 0; let mut idx = 0; + unsafe { + self.inner.set_len(0); + } + while idx < len { - let ch = unsafe { - self.get_unchecked(idx..len).chars().next().unwrap() - }; + let ch = unsafe { self.get_unchecked(idx..len).chars().next().unwrap() }; let ch_len = ch.len_utf8(); if !pred(ch) { @@ -656,16 +659,17 @@ impl RString { ptr::copy( self.inner.as_ptr().add(idx), self.inner.as_mut_ptr().add(idx - del_bytes), - ch_len + ch_len, ); } } + // Point idx to the next char idx += ch_len; } - if del_bytes > 0 { - unsafe { self.inner.set_len(len - del_bytes); } + unsafe { + self.inner.set_len(len - del_bytes); } }
abi_stable/src/std_types/string/tests.rs+17 −1 modified@@ -201,7 +201,23 @@ fn retain(){ assert_eq!(&*rstr, &*string); } - + { + // Copied from: + // https://github.com/rust-lang/rust/blob/48c4afbf9c29880dd946067d1c9aee1e7f75834a/library/alloc/tests/string.rs#L383 + let mut s = RString::from("0è0"); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let mut count = 0; + s.retain(|_| { + count += 1; + match count { + 1 => false, + 2 => true, + _ => panic!(), + } + }); + })); + assert!(std::str::from_utf8(s.as_bytes()).is_ok()); + } }
abi_stable/src/std_types/vec/iters.rs+58 −10 modified@@ -277,7 +277,8 @@ impl<'a, T> Drop for Drain<'a, T> { -// copy-paste of the std library DrainFilter +// copy of the std library DrainFilter, without the allocator parameter. +// (from rustc 1.50.0-nightly (eb4fc71dc 2020-12-17)) #[derive(Debug)] pub(crate) struct DrainFilter<'a, T, F> where F: FnMut(&mut T) -> bool, @@ -287,20 +288,30 @@ pub(crate) struct DrainFilter<'a, T, F> pub(super) del: usize, pub(super) old_len: usize, pub(super) pred: F, + pub(super) panic_flag: bool, } +// copy of the std library DrainFilter impl, without the allocator parameter. +// (from rustc 1.50.0-nightly (eb4fc71dc 2020-12-17)) impl<T, F> Iterator for DrainFilter<'_, T, F> - where F: FnMut(&mut T) -> bool, +where + F: FnMut(&mut T) -> bool, { type Item = T; fn next(&mut self) -> Option<T> { unsafe { - while self.idx != self.old_len { + while self.idx < self.old_len { let i = self.idx; - self.idx += 1; let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len); - if (self.pred)(&mut v[i]) { + self.panic_flag = true; + let drained = (self.pred)(&mut v[i]); + self.panic_flag = false; + // Update the index *after* the predicate is called. If the index + // is updated prior and the predicate panics, the element at this + // index would be leaked. + self.idx += 1; + if drained { self.del += 1; return Some(ptr::read(&v[i])); } else if self.del > 0 { @@ -319,14 +330,51 @@ impl<T, F> Iterator for DrainFilter<'_, T, F> } } +// copy of the std library DrainFilter impl, without the allocator parameter. +// (from rustc 1.50.0-nightly (eb4fc71dc 2020-12-17)) impl<T, F> Drop for DrainFilter<'_, T, F> - where F: FnMut(&mut T) -> bool, +where + F: FnMut(&mut T) -> bool, { fn drop(&mut self) { - self.for_each(drop); - unsafe { - self.vec.set_len(self.old_len - self.del); + struct BackshiftOnDrop<'a, 'b, T, F> + where + F: FnMut(&mut T) -> bool, + { + drain: &'b mut DrainFilter<'a, T, F>, + } + + impl<'a, 'b, T, F> Drop for BackshiftOnDrop<'a, 'b, T, F> + where + F: FnMut(&mut T) -> bool, + { + fn drop(&mut self) { + unsafe { + if self.drain.idx < self.drain.old_len && self.drain.del > 0 { + // This is a pretty messed up state, and there isn't really an + // obviously right thing to do. We don't want to keep trying + // to execute `pred`, so we just backshift all the unprocessed + // elements and tell the vec that they still exist. The backshift + // is required to prevent a double-drop of the last successfully + // drained item prior to a panic in the predicate. + let ptr = self.drain.vec.as_mut_ptr(); + let src = ptr.add(self.drain.idx); + let dst = src.sub(self.drain.del); + let tail_len = self.drain.old_len - self.drain.idx; + src.copy_to(dst, tail_len); + } + self.drain.vec.set_len(self.drain.old_len - self.drain.del); + } + } + } + + let backshift = BackshiftOnDrop { drain: self }; + + // Attempt to consume any remaining elements if the filter predicate + // has not yet panicked. We'll backshift any remaining elements + // whether we've already panicked or if the consumption here panics. + if !backshift.drain.panic_flag { + backshift.drain.for_each(drop); } } } -
abi_stable/src/std_types/vec.rs+1 −0 modified@@ -812,6 +812,7 @@ impl<T> RVec<T> { del: 0, old_len, pred: |x| !pred(x), + panic_flag: false, }; }
abi_stable/src/std_types/vec/tests.rs+55 −3 modified@@ -165,7 +165,7 @@ fn truncate() { #[test] fn retain(){ - let orig = vec![2, 3, 4 , 5, 6,7,8]; + let orig = vec![2, 3, 4, 5, 6, 7, 8]; let copy = orig.clone().into_(RVec::T); { let mut copy=copy.clone(); @@ -235,7 +235,7 @@ fn retain(){ true }); }).unwrap(); - assert_eq!(©[..], <&[i32]>::default()); + assert_eq!(©[..], &orig[..]); } } @@ -397,4 +397,56 @@ fn rvec_macro(){ assert_eq!(RVec::from(vec![0,3]), rvec![0,3]); assert_eq!(RVec::from(vec![0,3,6]), rvec![0,3,6]); assert_eq!(RVec::from(vec![1;10]), rvec![1;10]); -} \ No newline at end of file +} + +// Adapted from Vec tests +// (from rustc 1.50.0-nightly (eb4fc71dc 2020-12-17)) +#[test] +fn retain_panic() { + use std::rc::Rc; + use std::sync::Mutex; + use std::panic::AssertUnwindSafe; + + struct Check { + index: usize, + drop_counts: Rc<Mutex<RVec<usize>>>, + } + + impl Drop for Check { + fn drop(&mut self) { + self.drop_counts.lock().unwrap()[self.index] += 1; + println!("drop: {}", self.index); + } + } + + let check_count = 10; + let drop_counts = Rc::new(Mutex::new(rvec![0_usize; check_count])); + let mut data: RVec<Check> = (0..check_count) + .map(|index| Check { index, drop_counts: Rc::clone(&drop_counts) }) + .collect(); + + let _ = std::panic::catch_unwind(AssertUnwindSafe(move || { + let filter = |c: &Check| { + if c.index == 2 { + panic!("panic at index: {}", c.index); + } + // Verify that if the filter could panic again on another element + // that it would not cause a double panic and all elements of the + // vec would still be dropped exactly once. + if c.index == 4 { + panic!("panic at index: {}", c.index); + } + c.index < 6 + }; + data.retain(filter); + })); + + let drop_counts = drop_counts.lock().unwrap(); + assert_eq!(check_count, drop_counts.len()); + + for (index, count) in drop_counts.iter().cloned().enumerate() { + assert_eq!(1, count, "unexpected drop count at index: {} (count: {})", index, count); + } +} + +
Changelog.md+4 −0 modified@@ -2,6 +2,10 @@ This is the changelog,summarising changes in each version(some minor changes may # 0.9 +# 0.9.1 + +Fixed a memory safety bug in RString::retain and RVec::retain. + # 0.9.0 Rewrote how prefix types work. now they aren't by reference,
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-vq23-5h4f-vwpvghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-36212ghsaADVISORY
- github.com/rodrimati1992/abi_stable_crates/issues/44ghsaWEB
- rustsec.org/advisories/RUSTSEC-2020-0105.htmlghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.