CVE-2020-35871
Description
An issue was discovered in the rusqlite crate before 0.23.0 for Rust. Memory safety can be violated via an Auxdata API data race.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Memory safety violation in rusqlite crate before 0.23.0 due to data race in Auxdata API, allowing potential undefined behavior.
Vulnerability
Overview
CVE-2020-35871 is a memory safety vulnerability in the rusqlite crate for Rust, affecting versions prior to 0.23.0. The issue stems from a data race in the Auxdata API, which is used to attach auxiliary data to SQLite user-defined functions. The lack of proper synchronization when accessing or modifying this auxiliary data can lead to concurrent memory access violations, potentially resulting in undefined behavior [1][3].
Exploitation
Conditions
Exploitation requires an attacker to supply crafted SQL queries that invoke user-defined functions leveraging the Auxdata API. If an application exposes SQL execution to untrusted input (e.g., via a web interface or file parsing), an attacker can trigger the race condition without authentication. The vulnerability is particularly relevant in multi-threaded environments where the same SQLite connection or function context is shared across threads [2][4].
Impact
A successful exploit could corrupt memory, leading to crashes, data corruption, or potentially arbitrary code execution. The RustSec advisory classifies this as a memory safety issue, and the CVSS score reflects the high severity due to the possibility of remote exploitation in certain deployment scenarios [3][4].
Mitigation
The vulnerability is fixed in rusqlite version 0.23.0. Users are strongly advised to update to this version or later. The fix, introduced in commit 2ef3628, replaces the unsafe get_aux/set_aux pattern with a thread-safe get_or_create_aux method that uses Arc for safe sharing [2]. No workarounds are available; updating the crate is the only reliable mitigation.
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 |
|---|---|---|
rusqlitecrates.io | < 0.23.0 | 0.23.0 |
Affected products
2- Rust/rusqlitedescription
Patches
254043c803c83Prep release 0.23.0
1 file changed · +1 −1
Cargo.toml+1 −1 modified@@ -1,6 +1,6 @@ [package] name = "rusqlite" -version = "0.22.0" +version = "0.23.0" authors = ["The rusqlite developers"] edition = "2018" description = "Ergonomic wrapper for SQLite"
2ef3628dac35Actually fix auxdata api...
1 file changed · +54 −65
src/functions.rs+54 −65 modified@@ -12,6 +12,8 @@ //! use regex::Regex; //! use rusqlite::functions::FunctionFlags; //! use rusqlite::{Connection, Error, Result, NO_PARAMS}; +//! use std::sync::Arc; +//! type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>; //! //! fn add_regexp_function(db: &Connection) -> Result<()> { //! db.create_scalar_function( @@ -20,34 +22,19 @@ //! FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, //! move |ctx| { //! assert_eq!(ctx.len(), 2, "called with unexpected number of arguments"); -//! -//! let saved_re: Option<&Regex> = ctx.get_aux(0)?; -//! let new_re = match saved_re { -//! None => { -//! let s = ctx.get::<String>(0)?; -//! match Regex::new(&s) { -//! Ok(r) => Some(r), -//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))), -//! } -//! } -//! Some(_) => None, -//! }; -//! +//! let regexp: Arc<Regex> = ctx +//! .get_or_create_aux(0, |vr| -> Result<_, BoxError> { +//! Ok(Regex::new(vr.as_str()?)?) +//! })?; //! let is_match = { -//! let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap()); -//! //! let text = ctx //! .get_raw(1) //! .as_str() //! .map_err(|e| Error::UserFunctionError(e.into()))?; //! -//! re.is_match(text) +//! regexp.is_match(text) //! }; //! -//! if let Some(re) = new_re { -//! ctx.set_aux(0, re); -//! } -//! //! Ok(is_match) //! }, //! ) @@ -67,11 +54,12 @@ //! Ok(()) //! } //! ``` -use std::any::TypeId; +use std::any::Any; use std::os::raw::{c_int, c_void}; use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe}; use std::ptr; use std::slice; +use std::sync::Arc; use crate::ffi; use crate::ffi::sqlite3_context; @@ -121,6 +109,7 @@ unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) { pub struct Context<'a> { ctx: *mut sqlite3_context, args: &'a [*mut sqlite3_value], + // conn: PhantomData<&'conn mut Connection>, } impl Context<'_> { @@ -174,47 +163,60 @@ impl Context<'_> { unsafe { ValueRef::from_value(arg) } } + pub fn get_or_create_aux<T, E, F>(&self, arg: c_int, func: F) -> Result<Arc<T>> + where + T: Send + Sync + 'static, + E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>, + F: FnOnce(ValueRef<'_>) -> Result<T, E>, + { + if let Some(v) = self.get_aux(arg)? { + Ok(v) + } else { + let vr = self.get_raw(arg as usize); + self.set_aux( + arg, + func(vr).map_err(|e| Error::UserFunctionError(e.into()))?, + ) + } + } + /// Sets the auxilliary data associated with a particular parameter. See /// https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of /// this feature, or the unit tests of this module for an example. - pub fn set_aux<T: 'static>(&self, arg: c_int, value: T) { - let boxed = Box::into_raw(Box::new(AuxData { - id: TypeId::of::<T>(), - value, - })); + pub fn set_aux<T: Send + Sync + 'static>(&self, arg: c_int, value: T) -> Result<Arc<T>> { + let orig: Arc<T> = Arc::new(value); + let inner: AuxInner = orig.clone(); + let outer = Box::new(inner); + let raw: *mut AuxInner = Box::into_raw(outer); unsafe { ffi::sqlite3_set_auxdata( self.ctx, arg, - boxed as *mut c_void, - Some(free_boxed_value::<AuxData<T>>), + raw as *mut _, + Some(free_boxed_value::<AuxInner>), ) }; + Ok(orig) } - /// Gets the auxilliary data that was associated with a given parameter - /// via `set_aux`. Returns `Ok(None)` if no data has been associated, - /// and . - pub fn get_aux<T: 'static>(&self, arg: c_int) -> Result<Option<&T>> { - let p = unsafe { ffi::sqlite3_get_auxdata(self.ctx, arg) as *const AuxData<T> }; + /// Gets the auxilliary data that was associated with a given parameter via + /// `set_aux`. Returns `Ok(None)` if no data has been associated, and + /// Ok(Some(v)) if it has. Returns an error if the requested type does not + /// match. + pub fn get_aux<T: Send + Sync + 'static>(&self, arg: c_int) -> Result<Option<Arc<T>>> { + let p = unsafe { ffi::sqlite3_get_auxdata(self.ctx, arg) as *const AuxInner }; if p.is_null() { Ok(None) } else { - let id = unsafe { (*p).id }; - if TypeId::of::<T>() != id { - Err(Error::GetAuxWrongType) - } else { - Ok(Some(unsafe { &(*p).value })) - } + let v: AuxInner = AuxInner::clone(unsafe { &*p }); + v.downcast::<T>() + .map(Some) + .map_err(|_| Error::GetAuxWrongType) } } } -#[repr(C)] -struct AuxData<T: 'static> { - id: TypeId, - value: T, -} +type AuxInner = Arc<dyn Any + Send + Sync + 'static>; /// `feature = "functions"` Aggregate is the callback interface for user-defined /// aggregate function. @@ -776,34 +778,21 @@ mod test { // expression multiple times within one query. fn regexp_with_auxilliary(ctx: &Context<'_>) -> Result<bool> { assert_eq!(ctx.len(), 2, "called with unexpected number of arguments"); - - let saved_re: Option<&Regex> = ctx.get_aux(0)?; - let new_re = match saved_re { - None => { - let s = ctx.get::<String>(0)?; - match Regex::new(&s) { - Ok(r) => Some(r), - Err(err) => return Err(Error::UserFunctionError(Box::new(err))), - } - } - Some(_) => None, - }; + type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>; + let regexp: std::sync::Arc<Regex> = ctx + .get_or_create_aux(0, |vr| -> Result<_, BoxError> { + Ok(Regex::new(vr.as_str()?)?) + })?; let is_match = { - let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap()); - let text = ctx .get_raw(1) .as_str() .map_err(|e| Error::UserFunctionError(e.into()))?; - re.is_match(text) + regexp.is_match(text) }; - if let Some(re) = new_re { - ctx.set_aux(0, re); - } - Ok(is_match) } @@ -878,10 +867,10 @@ mod test { let db = Connection::open_in_memory().unwrap(); db.create_scalar_function("example", 2, FunctionFlags::default(), |ctx| { if !ctx.get::<bool>(1)? { - ctx.set_aux::<i64>(0, 100); + ctx.set_aux::<i64>(0, 100)?; } else { assert_eq!(ctx.get_aux::<String>(0), Err(Error::GetAuxWrongType)); - assert_eq!(ctx.get_aux::<i64>(0), Ok(Some(&100))); + assert_eq!(*ctx.get_aux::<i64>(0).unwrap().unwrap(), 100); } Ok(true) })
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-rjh8-p66p-jrh5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-35871ghsaADVISORY
- github.com/rusqlite/rusqlite/commit/2ef3628dac35aeba0a97d5fb3a57746b4e1d62b3ghsaWEB
- github.com/rusqlite/rusqlite/releases/tag/0.23.0ghsax_refsource_MISCWEB
- rustsec.org/advisories/RUSTSEC-2020-0014.htmlghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.