CVE-2022-48336:
Buffer Overflow in Widevine Trustlet (PRDiagParseAndStoreData @ 0x5cc8)

CVE:

CVE-2022-48336

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), 5.1.1 (LYZ28N), 6.0.0 (MRA58K), 6.0.0 (MRA58N), 6.0.0 (MRA58R), 6.0.0 (MRA58X), 7.0.0 (NBD90Z), 7.0.0 (NBD91P), 7.0.0 (NBD91U), 7.0.0 (NBD91X), 7.0.0 (NBD91Y), 7.0.0 (NBD91Z), 7.0.0 (NBD92F), 7.0.0 (NBD92G), 7.1.1 (N6F26Q), 7.1.1 (N6F26R), 7.1.1 (N6F26U), 7.1.1 (N6F27C), 7.1.1 (N6F27E).

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 PRDiagProvisionDataHandler() command.

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

switch(*req_buf) {
case 0x50004: // PRDiagProvisionDataHandler
    if ((0x2807 < req_buf_size) && (0x107 < resp_buf_size)) {
        ret = PRDiagProvisionDataHandler(req_buf + 2, req_buf[1]);
        ...
    }
    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 0x50004, it jumps to the PRDiagProvisionDataHandler() function.

The structure of the Request Buffer for the PRDiagProvisionDataHandler() function is similar to the one used in PRDiagMaintenanceHandler (refer to CVE-2022-48335):

struct widevine_PRDiagProvisionDataHandler_cmd {
        uint32_t cmd_id;
        uint32_t payload_size;
        struct widevine_PRDiagProvisionDataHandler_payload payload;
};

It contains an embedded structure and an integer indicating its size. The embedded structure is defined in the following code-block:

// Internal payload/request buffer for PRDiagProvisionDataHandler command
struct PRDiagProvisionDataHandler_payload {
        enum {
                op_PRDiagParseAndStoreData      = 0,
                PRDiagPDH_FORCE_ENUM_32_BITS = (INT_MAX | ~INT_MAX),
        } op;
        uint32_t compare_flag;
        uint32_t msg_buf_size;
        uint32_t data_len;
        // NOTE: The widevine trustlet checks the size of the request buffer
        // containing the widevine_PRDiagMaintenanceHandler_cmd structure.
        // The entire request buffer must have at least 0x2808 bytes
        char msg_buf[];
};

The PRDiagProvisionDataHandler function firstly determines if keys can be provisioned and then simply copies the payload/request into a temporary heap buffer and passes it to the PRDiagParseAndStoreData() function:

int PRDiagProvisionDataHandler(void *payload, uint32_t payload_size) {
     struct widevine_PRDiagProvisionDataHandler_payload *req;

     ...
     // Check if keys can be provisioned
     ...

     if (!payload || !payload_size) return 0x10;

     req = malloc(payload_size);
     if (!req) return 0xf;

     memcpy(req, payload, payload_size);
     if (req->op == 0) {
         PRDiagParseAndStoreData(req);
     }
     ...

The bug is inside the PRDiagParseAndStoreData function. The main functionality of this function is to store provisioning data into the Secure File System (SFS). The following code snippet shows a simplified version of the PRDiagParseAndStoreData function:

int PRDiagParseAndStoreData(struct PRDiagProvisionDataHandler_payload *req) {
    // Check input and operation
    if (!req || req->op != 0) return 0x10;

    msg_buf_size = req->msg_buf_size;
    msg_buf = &req->msg_buf;

    if (!req->data_len) {
        // CVE-2015-6639
        ...
    }
    else {
        // data_len is not zero
        // filePathBuffer @ 0x2e0fc
        // fullFilePathBuffer @ 0x2e5fc
        memzero(&fullFilePathBuffer, 0x80);
        pcVar2 = strncpy(&fullFilePathBuffer, &persist_prefix, 0x80);
        sVar3 = strlen(&filePathBuffer);
        sVar4 = strlen(&persist_prefix);
        memcpy(&fullFilePathBuffer + sVar4, &filePathBuffer, sVar3);
        sVar3 = strlen(&filePathBuffer);
        pcVar2[&fullFilePathBuffer + sVar3] = '/';
        sVar4 = strlen(&filePathBuffer);
        memcpy(&filePathBuffer + 1 + pcVar2 + sVar3, &filePathBuffer, sVar4);
        iVar5 = PRDiagPruneTrailingSlashes(&fullFilePathBuffer);
        if (msg_buf_size + iVar5 < 0x80) {
            if (iVar5 >= 1) {
                (&fullFilePathBuffer)[iVar5] = '/';
                memcpy(&fullFilePathBuffer + 1 + iVar5, msg_buf, msg_buf_size);
                ...

The function first performs some validations on the input and the operation being requested and then checks the data_len value. If this value is not zero, it constructs a file path by concatenating two strings: persist_prefix and filePathBuffer, which are global buffers stored in the data segment. The resulting SFS file path is stored in the fullFilePathBuffer variable, which is used later in the function to write the provisioning data.

The persist_prefix string is initialized with the value "/persist/data/" by default, and the filePathBuffer is initially empty, so that the total length of the final string afer calling PRDiagPruneTrailingSlashes() is 13. After that, the contents of the user-controlled msg_buf are appended to fullFilePathBuffer after checking if the length does not exeed 128 bytes.

The bug is present in this check. Since the addition is done using a signed integer, this calculation causes an integer overflow if the sum of msg_buf_size and the string length of fullFilePathBuffer is large enough, resulting in a negative value and bypassing the if statement. Consequently, the subsequent memory copy can cause a buffer overflow:

_images/widevine-memory-5cc8.svg

The range of values for msg_buf_size that can cause a buffer overflow extends from -13 (0xfffffff3) to -1 (0xffffffff).

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

Since there are 13 specific and large msg_buf_size 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 = 0x50004;  // PRDiagProvisionDataHandler
req->payload.op = 0;    // PRDiagParseAndStoreData
...
req->payload.msg_buf_size = 0xffffffff;
req->payload.data_len = 1;

The Commmand ID 0x50004 is specified to invoke PRDiagProvisionDataHandler(), and the PRDiagParseAndStoreData operation is being specified. In addition, msg_buf_size is set to 0xffffffff so that the sum of msg_buf_size + iVar5 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: "PRDiagProvisionDataHandler: pTzMsg 0xfff00008, reqlen 10240"
<5>widevine: "PRDiagParseAndStoreData: begins!"
<5>widevine: "PRDiagPruneTrailingSlashes:  pPath 0x2e5fc"
<5>widevine: "PRDiagPruneTrailingSlashes: ends!"
*** 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.