VYPR
Moderate severityNVD Advisory· Published Sep 29, 2022· Updated Apr 23, 2025

When matrix-rust-sdk recieves forwarded room keys, the reciever doesn't check if it requested the key from the forwarder

CVE-2022-39252

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.

PackageAffected versionsPatched versions
matrix-sdk-cryptocrates.io
< 0.6.00.6.0

Affected products

2

Patches

2
41449d2cc360

test(crypto): Test that we reject forwarded room keys from other users

https://github.com/matrix-org/matrix-rust-sdkDamir JelićAug 2, 2022via ghsa
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();
    
093fb5d0aa21

fix(crypto): Only accept forwarded room keys from our own trusted devices

https://github.com/matrix-org/matrix-rust-sdkDamir JelićAug 2, 2022via ghsa
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

News mentions

0

No linked articles in our index yet.