CVE-2022-48333:
Buffer Overflow in Widevine Trustlet (drm_verify_keys @ 0x730c)

CVE:

CVE-2022-48333

Vendor:

Google

Device:

Nexus 6

Affected Component:

Widevine

Publication Date:

March 2023

Credits:

CyberIntel Team

Last edited: 11/04/23

1. Description

This entry describes a vulnerability that we found in the Widevine Trusted Application (TA), which runs within Qualcomm’s Secure Execution Environment (QSEE). The vulnerability is an integer overflow that leads to a subsequent buffer overflow. This bug has been found using tools we have developed to assist in the security evaluation of TrustZone, including a debugger and a coverage-based fuzzer for QSEE TAs. Please, refer to this page for further information.

Qualcomm Secure Execution Environment is one of the most widespread commercial TEE solutions in the smartphone space, used by many different devices such as Xiaomi, Motorola and several devices of the Google Nexus and Pixel series.

Widevine is a Digital Rights Management (DRM) technology developed by Google to protect copyrighted content and to enable secure distribution and consumption of video and audio content. The technology involves encryption, licensing, and key management to ensure that content can only be decrypted and played back on authorized devices.

An attacker could potentially exploit the vulnerability in the Trusted Application by sending a command from the Normal World, causing the application to crash and possibly execute arbitrary code. This vulnerability could have potential consequences, such as elevating attacker’s privileges and exposing sensitive information.

This post is part of a series of related bugs affecting Widevine Trusted Application of Google Nexus 6:

2. Affected Versions

Affected versions are: 5.0.0 (LRX21O), 5.0.1 (LRX22C), 5.1.0 (LMY47D), 5.1.0 (LMY47E), 5.1.0 (LMY47I), 5.1.0 (LMY47M), 5.1.1 (LMY47Z), 5.1.1 (LMY48I), 5.1.1 (LMY48M), 5.1.1 (LMY48T), 5.1.1 (LMY48W), 5.1.1 (LMY48X), 5.1.1 (LMY48Y), 5.1.1 (LVY48C), 5.1.1 (LVY48E), 5.1.1 (LVY48F), 5.1.1 (LVY48H), 5.1.1 (LVY48I), 5.1.1 (LYZ28E), 5.1.1 (LYZ28J), 5.1.1 (LYZ28K), 5.1.1 (LYZ28M) and 5.1.1 (LYZ28N).

3. Impact

This vulnerability has the potential to compromise the security of the system in multiple ways.

An attacker with high priviledges in Normal World can exploit the vulnerability to compromise the Trusted Application running in the Secure World, eventually executing arbitrary code and reading and/or modifying information of critical files, compromising the confidentiality and integrity of the system.

On the other hand, the attacker is able to crash the Trusted Application, potentially resulting in a denial of service.

4. Vulnerability

The bug is present in Widevine’s drm_verify_keys() command. This command closely resembles drm_save_keys(), which has been featured in other blog entries (refer to CVE-2022-48331 and CVE-2022-48332).

The following snippet shows a switch-case present in the TA’s command handler:

switch(*req_buf) {
case 0x50002: // drm_verify_keys() command
    if ((0x2a0f < req_buf_size) && (0x107 < resp_buf_size)) {
        drm_verify_keys(
            req_buf + 4,    req_buf[1], // feature_name
            req_buf + 0x44, req_buf[2]  // file_name
        );
        ...
    }
    break;
}

It takes the Command ID from the first word of the Request Buffer, which is sent by client applications in the Normal World. Thus, if the received command is 0x50002, it jumps to the drm_verify_keys() function.

The structure of the Request Buffer for this command is the following:

struct widevine_drm_save_keys_cmd {
    uint32_t cmd_id;
    uint32_t feature_name_len;
    uint32_t file_name_len;
    char feature_name[0x100];
    char file_name[0x100];
};

It contains two buffers (i.e., feature_name and file_name) and two integers indicating the effective length of each buffer. It is important to note that even though the maximum size of each buffer is 256 bytes, the client has control over the values of feature_name_len and file_name_len, since they are directly taken from the Request Buffer.

The main functionality of drm_verify_keys() is to verify the integrity of encryption keys or DRM-related data stored within the Widevine DRM system. It performs the following high-level tasks:

  • Validate input parameters and check for any constraints that may prevent the verification process.

  • Construct a full file path using the provided feature name and file name.

  • Set the CRC (cyclic redundancy check) file path, which is used for verifying the integrity of the keys.

  • Verify the stored encryption keys or DRM-related data and check for any tampering or corruption.

The bug is present in the validation of input parameters while constructing the SFS file path:

heap_buf = malloc(0x100);
...
memzero(heap_buf, 0x80);
prefix_len = strncpy(heap_buf, &persist_prefix, 0x80);
total_len = prefix_len + feature_name_len; // Signed int
if (total_len < 0x100) {
    prefix_len = strlen(&persist_prefix);
    memcpy(heap_buf + prefix_len, feature_name, feature_name_len);
    ...
}

In this code snippet, a heap buffer is allocated and the persist_prefix string is copied into it using the strncpy() function. This string is initialized with the value "/persist/data/" by default, so that it’s length is 14. The buffer is then filled with the provided feature name, ensuring the total length does not exceed the specified limit. This is done using total_len by adding the length of the persist_prefix string and feature_name_len, which is directly specified by the client through the Request Buffer.

The bug is present in this calculation. Since total_len is a signed integer, this calculation causes an integer overflow if the sum of prefix_len and feature_name_len is large enough, resulting in a negative value for total_len. As a result, the subsequent if statement passes even if the actual total length of the string is greater than the allocated buffer size.

Since the check is if (total_len < 0x100), for the condition 14 + feature_name_len < 0x100 to be triggered, the value of feature_name_len must be within the inclusive range of -14 to 241.

The code after the check calculates again the prefix_len and calls the memcpy() function to copy the contents of the feature_name string into the buffer, immediately following the persist_prefix, using feature_name_len. Since this value can be large enough, it can cause a buffer overflow in this copy operation:

_images/widevine-memory-730c.svg

The range of values for feature_name_len that can cause a buffer overflow extends from -14 (0xfffffff2) to -1 (0xffffffff).

The user has full control over the value of feature_name_len and the contents of the feature_name buffer in the Request Buffer.

Since there are 14 specific and large feature_name_len values that can cause the buffer to overflow, a memory copy operation with any of these values will exceed the bounds of the data segment, resulting in an invalid memory access and potentially causing the application to crash.

5. Exploitation

This section shows a Proof of Concept (PoC) of how to trigger the vulnerability by sending a command to the Widevine trusted application running in the Secure World. Based on the command structure we have derived by reverse engineering, we can craft the command request to be sent to the trusted application:

req->cmd_id = 0x50002;  // drm_verify_keys
req->feature_name_len = 0xffffffff;
req->file_name_len = 1;
...

The Commmand ID 0x50002 is specified to invoke drm_verify_keys(), and feature_name_len is set to 0xffffffff so that the sum of prefix_len + feature_name_len overflows producing a small value, bypassing the check and eventually calling memcpy() producing the buffer overflow. When this request is sent to Widevine, it ends up crashing.

<5>widevine: "drm_verify_keys: feature_name 0xfff00010, feature_name_len 4294967295, file_name 0xfff00110,  file_name_len 1"
*** crash ***

The Android log in normal world reports that qseecom_send_cmd has failed with error -22, which means APP_FAULTED:

<4>QSEECOM: qseecom_load_app: App (widevine) does'nt exist, loading apps for first time
<4>QSEECOM: qseecom_load_app: App with id 3 (widevine) now loaded
<3>QSEECOM: __qseecom_send_cmd: Response result -1 not supported
<3>QSEECOM: qseecom_ioctl: failed qseecom_send_cmd: -22
<4>QSEECOM: qseecom_unload_app: App id 3 now unloaded

The reason it crashes is because the large memory copy ends up writing to unmapped memory due to exceeding the data segment.