VYPR
Medium severityOSV Advisory· Published Jul 10, 2025· Updated Apr 15, 2026

CVE-2025-53549

CVE-2025-53549

Description

The Matrix Rust SDK is a collection of libraries that make it easier to build Matrix clients in Rust. An SQL injection vulnerability in the EventCache::find_event_with_relations method of matrix-sdk 0.11 and 0.12 allows malicious room members to execute arbitrary SQL commands in Matrix clients that directly pass relation types provided by those room members into this method, when used with the default sqlite-based store backend. Exploitation is unlikely, as no known clients currently use the API in this manner. This vulnerability is fixed in 0.13.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

An SQL injection flaw in the Matrix Rust SDK's EventCache::find_event_with_relations method lets malicious room members execute arbitrary SQL when relation types are passed unsanitized to the sqlite backend; fixed in 0.13.

Root

Cause

The EventCache::find_event_with_relations method in the Matrix Rust SDK versions 0.11 and 0.12 constructs a SQL query by directly interpolating relation type strings into an IN clause without sanitization. As shown in the fix commit [2], the original code built a filter string by formatting user-supplied relation types into double-quoted strings and concatenating them into the SQL statement. When the default SQLite store backend is used, this allows a malicious room member to inject arbitrary SQL commands.

Exploitation

Scenario

An attacker must be a member of a Matrix room and be able to craft a relation type value that, when passed to the vulnerable API, escapes the intended string context. The vulnerability requires that a client application call the affected method with relation types that originate from untrusted room events. The description and advisory note that no known clients currently use the API in this way, which significantly reduces the likelihood of real-world exploitation [1].

Impact

If successfully exploited, the attacker could read, modify, or delete arbitrary data from the SQLite database used by the SDK's event cache. This could lead to disclosure of private room history or corruption of the local store, potentially affecting the client's ability to synchronize correctly with the Matrix federation.

Mitigation

The vulnerability has been fixed in SDK version 0.13. The fix, visible in the referenced commit [2], parameterizes the relation type filter to prevent injection. The associated pull request [3] also reworks the event cache schema, including adding the room_id column to the events table for safer scoping. Users are advised to update to the latest SDK version [4].

AI Insight generated on May 19, 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
matrix-sdkcrates.io
>= 0.11.0, < 0.13.00.13.0
matrix-sdk-sqlitecrates.io
>= 0.11.0, < 0.13.00.13.0

Affected products

2
  • matrix-sdk-0.11.0, matrix-sdk-0.12.0, matrix-sdk-base-0.11.0, …+ 1 more
    • (no CPE)range: matrix-sdk-0.11.0, matrix-sdk-0.12.0, matrix-sdk-base-0.11.0, …
    • (no CPE)range: >=0.11, <=0.12

Patches

2
d0c01006e480

fix(sqlite): Fix a SQL injection issue in the find_event_relations function

https://github.com/matrix-org/matrix-rust-sdkDamir JelićJul 3, 2025via ghsa
1 file changed · +62 39
  • crates/matrix-sdk-sqlite/src/event_cache_store.rs+62 39 modified
    @@ -1156,52 +1156,75 @@ impl EventCacheStore for SqliteEventCacheStore {
             self.acquire()
                 .await?
                 .with_transaction(move |txn| -> Result<_> {
    -                let filter_query = if let Some(filters) = compute_filters_string(filters.as_deref())
    -                {
    -                    format!(
    -                        " AND rel_type IN ({})",
    -                        filters
    -                            .into_iter()
    -                            .map(|f| format!(r#""{f}""#))
    -                            .collect::<Vec<_>>()
    -                            .join(", ")
    -                    )
    -                } else {
    -                    "".to_owned()
    -                };
    -
    -                let query = format!(
    -                    "SELECT events.content, event_chunks.chunk_id, event_chunks.position
    -                    FROM events
    -                    LEFT JOIN event_chunks ON events.event_id = event_chunks.event_id AND event_chunks.linked_chunk_id = ?
    -                    WHERE relates_to = ? AND room_id = ? {filter_query}"
    -                );
    -
    -                // Collect related events.
    -                let mut related = Vec::new();
    -                for result in
    -                    txn.prepare(&query)?.query_map((hashed_linked_chunk_id, event_id.as_str(), hashed_room_id), |row| {
    -                        Ok((
    +                let get_rows = |row: &rusqlite::Row<'_>| {
    +                    Ok((
                                 row.get::<_, Vec<u8>>(0)?,
                                 row.get::<_, Option<u64>>(1)?,
                                 row.get::<_, Option<usize>>(2)?,
    -                        ))
    -                    })?
    -                {
    -                    let (event_blob, chunk_id, index) = result?;
    +                    ))
    +                };
     
    -                    let event: Event = serde_json::from_slice(&this.decode_value(&event_blob)?)?;
    +                // Collect related events.
    +                let collect_results = |transaction| {
    +                    let mut related = Vec::new();
     
    -                    // Only build the position if both the chunk_id and position were present; in
    -                    // theory, they should either be present at the same time, or not at all.
    -                    let pos = chunk_id.zip(index).map(|(chunk_id, index)| {
    -                        Position::new(ChunkIdentifier::new(chunk_id), index)
    -                    });
    +                    for result in transaction {
    +                        let (event_blob, chunk_id, index): (Vec<u8>, Option<u64>, _) = result?;
     
    -                    related.push((event, pos));
    -                }
    +                        let event: Event = serde_json::from_slice(&this.decode_value(&event_blob)?)?;
    +
    +                        // Only build the position if both the chunk_id and position were present; in
    +                        // theory, they should either be present at the same time, or not at all.
    +                        let pos = chunk_id.zip(index).map(|(chunk_id, index)| {
    +                            Position::new(ChunkIdentifier::new(chunk_id), index)
    +                        });
    +
    +                        related.push((event, pos));
    +                    }
    +
    +                    Ok(related)
    +                };
    +
    +                let related = if let Some(filters) = compute_filters_string(filters.as_deref()) {
    +                    let question_marks = repeat_vars(filters.len());
    +                    let query = format!(
    +                        "SELECT events.content, event_chunks.chunk_id, event_chunks.position
    +                        FROM events
    +                        LEFT JOIN event_chunks ON events.event_id = event_chunks.event_id AND event_chunks.linked_chunk_id = ?
    +                        WHERE relates_to = ? AND room_id = ? AND rel_type IN ({question_marks})"
    +                    );
    +
    +                    let filters: Vec<_> = filters.iter().map(|f| f.to_sql().unwrap()).collect();
    +                    let parameters = params_from_iter(
    +                        [
    +                            hashed_linked_chunk_id.to_sql().expect("We should be able to convert a hashed linked chunk ID to a SQLite value"),
    +                            event_id.as_str().to_sql().expect("We should be able to convert an event ID to a SQLite value"),
    +                            hashed_room_id.to_sql().expect("We should be able to convert a room ID to a SQLite value")
    +                        ]
    +                            .into_iter()
    +                            .chain(filters)
    +                    );
    +
    +                    let mut transaction = txn.prepare(&query)?;
    +                    let transaction = transaction.query_map(parameters, get_rows)?;
    +
    +                    collect_results(transaction)
    +
    +                } else {
    +                    let query =
    +                        "SELECT events.content, event_chunks.chunk_id, event_chunks.position
    +                        FROM events
    +                        LEFT JOIN event_chunks ON events.event_id = event_chunks.event_id AND event_chunks.linked_chunk_id = ?
    +                        WHERE relates_to = ? AND room_id = ?";
    +                    let parameters = (hashed_linked_chunk_id, event_id.as_str(), hashed_room_id);
    +
    +                    let mut transaction = txn.prepare(query)?;
    +                    let transaction = transaction.query_map(parameters, get_rows)?;
    +
    +                    collect_results(transaction)
    +                };
     
    -                Ok(related)
    +                related
                 })
                 .await
         }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.