When matrix-rust-sdk recieves forwarded room keys, the reciever doesn't check if it requested the key from the forwarder
Description
matrix-rust-sdk is an implementation of a Matrix client-server library in Rust, and matrix-sdk-crypto is the Matrix encryption library. Prior to version 0.6, when a user requests a room key from their devices, the software correctly remembers the request. When the user receives a forwarded room key, the software accepts it without checking who the room key came from. This allows homeservers to try to insert room keys of questionable validity, potentially mounting an impersonation attack. Version 0.6 fixes this issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
matrix-rust-sdk cryptographic library prior to 0.6 accepts forwarded room keys without verifying the sender, enabling impersonation attacks.
Vulnerability
Overview
CVE-2022-39252 is a vulnerability in the matrix-rust-sdk and matrix-sdk-crypto libraries, which implement the Matrix client-server protocol and its end-to-end encryption (E2EE) in Rust. The bug, present in versions prior to 0.6, lies in the room key forwarding mechanism: when a user requests a room key from their devices, the software correctly records the request; however, when a forwarded room key is received, the software accepts it without verifying which device or user it actually came from [1][4].
Attack
Vector
An attacker who controls a homeserver can exploit this weakness by injecting room keys of questionable validity into the key distribution process. Because the library does not check the origin of the forwarded key against the original request [1], a malicious homeserver can impersonate another user's device and supply a fake room key. The attack is network-based, requires no authentication from the attacker beyond homeserver control, and no user interaction [4].
The vulnerable flow is demonstrated in a test added with the fix, which explicitly verifies that forwarded keys from a different user should be rejected [3].
Impact
Successful exploitation allows a homeserver operator to mount an impersonation attack. By inserting a fraudulent room key, the attacker can decrypt or manipulate encrypted messages, effectively breaking the trust provided by end-to-end encryption [1][4]. The CVSS v3.1 score is 7.5 (High), with high integrity impact but no direct confidentiality impact, though the practical effect can compromise the secrecy of communications [4].
Mitigation
The vulnerability is fixed in version 0.6.0 of matrix-sdk-crypto and the broader matrix-rust-sdk [2][4]. Users should update their dependencies to at least 0.6.0. There are no known workarounds other than upgrading [1]. The CVE was published on 2022-09-29, and the fix was released the same day [1][2].
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 |
|---|---|---|
matrix-sdk-cryptocrates.io | < 0.6.0 | 0.6.0 |
Affected products
2- matrix-org/matrix-rust-sdkv5Range: < 0.6
Patches
241449d2cc360test(crypto): Test that we reject forwarded room keys from other users
1 file changed · +60 −4
crates/matrix-sdk-crypto/src/gossiping/machine.rs+60 −4 modified@@ -1148,6 +1148,7 @@ mod tests { } async fn machines_for_key_share( + other_machine_owner: &UserId, create_sessions: bool, ) -> (GossipMachine, Account, OutboundGroupSession, GossipMachine) { let alice_machine = get_machine().await; @@ -1157,12 +1158,14 @@ mod tests { }; let alice_device = ReadOnlyDevice::from_account(alice_machine.store.account()).await; - let bob_machine = test_gossip_machine(alice_id()); + let bob_machine = test_gossip_machine(other_machine_owner); let bob_device = ReadOnlyDevice::from_account(bob_machine.store.account()).await; // We need a trusted device, otherwise we won't request keys + let second_device = ReadOnlyDevice::from_account(&alice_2_account()).await; + second_device.set_trust_state(LocalTrust::Verified); bob_device.set_trust_state(LocalTrust::Verified); - alice_machine.store.save_devices(&[bob_device]).await.unwrap(); + alice_machine.store.save_devices(&[bob_device, second_device]).await.unwrap(); bob_machine.store.save_devices(&[alice_device.clone()]).await.unwrap(); if create_sessions { @@ -1535,7 +1538,7 @@ mod tests { #[async_test] async fn key_share_cycle() { let (alice_machine, alice_account, group_session, bob_machine) = - machines_for_key_share(true).await; + machines_for_key_share(alice_id(), true).await; // Get the request and convert it into a event. let requests = alice_machine.outgoing_to_device_requests().await.unwrap(); @@ -1599,6 +1602,59 @@ mod tests { assert_eq!(session.session_id(), group_session.session_id()) } + #[async_test] + async fn reject_forward_from_another_user() { + let (alice_machine, alice_account, group_session, bob_machine) = + machines_for_key_share(bob_id(), true).await; + + // Get the request and convert it into a event. + let requests = alice_machine.outgoing_to_device_requests().await.unwrap(); + let request = &requests[0]; + let event = request_to_event(alice_id(), alice_id(), request); + + alice_machine.mark_outgoing_request_as_sent(&request.request_id).await.unwrap(); + + // Bob doesn't have any outgoing requests. + assert!(bob_machine.outgoing_requests.is_empty()); + + // Receive the room key request from alice. + bob_machine.receive_incoming_key_request(&event); + bob_machine.collect_incoming_key_requests().await.unwrap(); + // Now bob does have an outgoing request. + assert!(!bob_machine.outgoing_requests.is_empty()); + + // Get the request and convert it to a encrypted to-device event. + let requests = bob_machine.outgoing_to_device_requests().await.unwrap(); + let request = &requests[0]; + + let event: EncryptedToDeviceEvent = request_to_event(alice_id(), bob_id(), request); + bob_machine.mark_outgoing_request_as_sent(&request.request_id).await.unwrap(); + + // Check that alice doesn't have the session. + assert!(alice_machine + .store + .get_inbound_group_session( + room_id(), + &bob_machine.store.account().identity_keys().curve25519.to_base64(), + group_session.session_id() + ) + .await + .unwrap() + .is_none()); + + let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap(); + if let AnyDecryptedOlmEvent::ForwardedRoomKey(e) = decrypted.result.event { + let session = alice_machine + .receive_forwarded_room_key(decrypted.result.sender_key, &e) + .await + .unwrap(); + + assert!(session.is_none(), "We should not receive a room key from another user"); + } else { + panic!("Invalid decrypted event type"); + } + } + #[async_test] async fn secret_share_cycle() { let alice_machine = get_machine().await; @@ -1672,7 +1728,7 @@ mod tests { #[async_test] async fn key_share_cycle_without_session() { let (alice_machine, alice_account, group_session, bob_machine) = - machines_for_key_share(false).await; + machines_for_key_share(alice_id(), false).await; // Get the request and convert it into a event. let requests = alice_machine.outgoing_to_device_requests().await.unwrap();
093fb5d0aa21fix(crypto): Only accept forwarded room keys from our own trusted devices
1 file changed · +31 −2
crates/matrix-sdk-crypto/src/gossiping/machine.rs+31 −2 modified@@ -945,6 +945,21 @@ impl GossipMachine { } } + async fn should_accept_forward( + &self, + info: &GossipRequest, + sender_key: Curve25519PublicKey, + ) -> Result<bool, CryptoStoreError> { + let device = + self.store.get_device_from_curve_key(&info.request_recipient, sender_key).await?; + + if let Some(device) = device { + Ok(device.user_id() == self.user_id() && device.is_verified()) + } else { + Ok(false) + } + } + /// Receive a forwarded room key event that was sent using any of our /// supported content types. async fn receive_supported_keys( @@ -956,8 +971,22 @@ impl GossipMachine { let algorithm = event.content.algorithm(); if let Some(info) = self.get_key_info(event).await? { - self.accept_forwarded_room_key(&info, &event.sender, sender_key, algorithm, content) - .await + if self.should_accept_forward(&info, sender_key).await? { + self.accept_forwarded_room_key(&info, &event.sender, sender_key, algorithm, content) + .await + } else { + warn!( + sender = %event.sender, + %sender_key, + room_id = %content.room_id, + session_id = content.session_id.as_str(), + claimed_sender_key = %content.claimed_sender_key, + "Received a forwarded room key from an unknown device, or \ + from a device that the key request recipient doesn't own", + ); + + Ok(None) + } } else { warn!( sender = %event.sender,
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-vp68-2wrm-69qmghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-39252ghsaADVISORY
- github.com/matrix-org/matrix-rust-sdk/commit/093fb5d0aa21c0b5eaea6ec96b477f1075271cbbghsax_refsource_MISCWEB
- github.com/matrix-org/matrix-rust-sdk/commit/41449d2cc360e347f5d4e1c154ec1e3185f11acdghsax_refsource_MISCWEB
- github.com/matrix-org/matrix-rust-sdk/releases/tag/matrix-sdk-0.6.0ghsax_refsource_MISCWEB
- github.com/matrix-org/matrix-rust-sdk/security/advisories/GHSA-vp68-2wrm-69qmghsax_refsource_CONFIRMWEB
- rustsec.org/advisories/RUSTSEC-2022-0085.htmlghsaWEB
News mentions
0No linked articles in our index yet.