VYPR
Moderate severityNVD Advisory· Published Sep 13, 2021· Updated Aug 4, 2024

CVE-2021-40823

CVE-2021-40823

Description

A logic error in the room key sharing functionality of matrix-js-sdk (aka Matrix Javascript SDK) before 12.4.1 allows a malicious Matrix homeserver present in an encrypted room to steal room encryption keys (via crafted Matrix protocol messages) that were originally sent by affected Matrix clients participating in that room. This allows the homeserver to decrypt end-to-end encrypted messages sent by affected clients.

AI Insight

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

A logic error in matrix-js-sdk before 12.4.1 allows a malicious homeserver to steal room encryption keys, breaking E2EE.

Vulnerability

A logic error in the room key sharing functionality of matrix-js-sdk (Matrix JavaScript SDK) before version 12.4.1 allows a malicious Matrix homeserver present in an encrypted room to steal room encryption keys via crafted Matrix protocol messages [1][2][3]. The bug occurs in the OutboundSessionInfo class where the markSharedWithDevice method did not verify the target device's identity key, allowing a homeserver to impersonate a user's device [4].

Exploitation

An attacker must control a Matrix homeserver that is a member of an encrypted room. The attacker can then send crafted Matrix protocol messages to a vulnerable client, tricking it into sharing encryption keys for messages previously sent by that client to a compromised user account [2]. The attack requires prior compromise of the recipient's account or homeserver credentials [2].

Impact

Successful exploitation allows the malicious homeserver to decrypt end-to-end encrypted messages sent by affected clients in that room [1][2]. This breaks the confidentiality guarantees of Matrix's E2EE, potentially exposing all past and future messages in the room.

Mitigation

The vulnerability is fixed in matrix-js-sdk version 12.4.1, released on 2021-09-13 [2][4]. Users should upgrade immediately. If unable to upgrade, keep vulnerable clients offline to prevent key disclosure [2]. No workaround exists other than patching. There is no evidence of exploitation in the wild [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-js-sdknpm
< 12.4.112.4.1

Affected products

2

Patches

1
894c24880da0

Verify target device key on reshare

https://github.com/matrix-org/matrix-js-sdkRiotRobotSep 13, 2021via ghsa
1 file changed · +29 9
  • src/crypto/algorithms/megolm.ts+29 9 modified
    @@ -101,6 +101,13 @@ interface IPayload extends Partial<IMessage> {
     }
     /* eslint-enable camelcase */
     
    +interface SharedWithData {
    +    // The identity key of the device we shared with
    +    deviceKey: string;
    +    // The message index of the ratchet we shared with that device
    +    messageIndex: number;
    +}
    +
     /**
      * @private
      * @constructor
    @@ -115,12 +122,12 @@ interface IPayload extends Partial<IMessage> {
      *
      * @property {object} sharedWithDevices
      *    devices with which we have shared the session key
    - *        userId -> {deviceId -> msgindex}
    + *        userId -> {deviceId -> SharedWithData}
      */
     class OutboundSessionInfo {
         public useCount = 0;
         public creationTime: number;
    -    public sharedWithDevices: Record<string, Record<string, number>> = {};
    +    public sharedWithDevices: Record<string, Record<string, SharedWithData>> = {};
         public blockedDevicesNotified: Record<string, Record<string, boolean>> = {};
     
         constructor(public readonly sessionId: string, public readonly sharedHistory = false) {
    @@ -150,11 +157,11 @@ class OutboundSessionInfo {
             return false;
         }
     
    -    public markSharedWithDevice(userId: string, deviceId: string, chainIndex: number): void {
    +    public markSharedWithDevice(userId: string, deviceId: string, deviceKey: string, chainIndex: number): void {
             if (!this.sharedWithDevices[userId]) {
                 this.sharedWithDevices[userId] = {};
             }
    -        this.sharedWithDevices[userId][deviceId] = chainIndex;
    +        this.sharedWithDevices[userId][deviceId] = { deviceKey, messageIndex: chainIndex };
         }
     
         public markNotifiedBlockedDevice(userId: string, deviceId: string): void {
    @@ -572,6 +579,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
             payload: IPayload,
         ): Promise<void> {
             const contentMap = {};
    +        const deviceInfoByDeviceId = new Map<string, DeviceInfo>();
     
             const promises = [];
             for (let i = 0; i < userDeviceMap.length; i++) {
    @@ -584,6 +592,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
                 const userId = val.userId;
                 const deviceInfo = val.deviceInfo;
                 const deviceId = deviceInfo.deviceId;
    +            deviceInfoByDeviceId.set(deviceId, deviceInfo);
     
                 if (!contentMap[userId]) {
                     contentMap[userId] = {};
    @@ -636,7 +645,10 @@ class MegolmEncryption extends EncryptionAlgorithm {
                     for (const userId of Object.keys(contentMap)) {
                         for (const deviceId of Object.keys(contentMap[userId])) {
                             session.markSharedWithDevice(
    -                            userId, deviceId, chainIndex,
    +                            userId,
    +                            deviceId,
    +                            deviceInfoByDeviceId.get(deviceId).getIdentityKey(),
    +                            chainIndex,
                             );
                         }
                     }
    @@ -719,19 +731,27 @@ class MegolmEncryption extends EncryptionAlgorithm {
                 logger.debug(`megolm session ${sessionId} never shared with user ${userId}`);
                 return;
             }
    -        const sentChainIndex = obSessionInfo.sharedWithDevices[userId][device.deviceId];
    -        if (sentChainIndex === undefined) {
    +        const sessionSharedData = obSessionInfo.sharedWithDevices[userId][device.deviceId];
    +        if (sessionSharedData === undefined) {
                 logger.debug(
                     "megolm session ID " + sessionId + " never shared with device " +
                     userId + ":" + device.deviceId,
                 );
                 return;
             }
     
    +        if (sessionSharedData.deviceKey !== device.getIdentityKey()) {
    +            logger.warn(
    +                `Session has been shared with device ${device.deviceId} but with identity ` +
    +                `key ${sessionSharedData.deviceKey}. Key is now ${device.getIdentityKey()}!`,
    +            );
    +            return;
    +        }
    +
             // get the key from the inbound session: the outbound one will already
             // have been ratcheted to the next chain index.
             const key = await this.olmDevice.getInboundGroupSessionKey(
    -            this.roomId, senderKey, sessionId, sentChainIndex,
    +            this.roomId, senderKey, sessionId, sessionSharedData.messageIndex,
             );
     
             if (!key) {
    @@ -882,7 +902,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
                 const deviceId = deviceInfo.deviceId;
     
                 session.markSharedWithDevice(
    -                userId, deviceId, key.chain_index,
    +                userId, deviceId, deviceInfo.getIdentityKey(), key.chain_index,
                 );
             }
     
    

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.