VYPR
High severityNVD Advisory· Published Dec 31, 2020· Updated Aug 4, 2024

CVE-2020-35871

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.

PackageAffected versionsPatched versions
rusqlitecrates.io
< 0.23.00.23.0

Affected products

2

Patches

2
54043c803c83

Prep release 0.23.0

https://github.com/rusqlite/rusqliteThom ChiovoloniApr 23, 2020via osv
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"
    
2ef3628dac35

Actually fix auxdata api...

https://github.com/rusqlite/rusqliteThom ChiovoloniApr 13, 2020via ghsa
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

News mentions

0

No linked articles in our index yet.