impalabs space base graphics
Huawei TrustZone Task_Phone_Novelchd Vulnerabilities
This advisory contains information about the following vulnerabilities:

We identified several vulnerabilities affecting Huawei's TASK_PHONE_NOVELCHD trusted application. To keep the report concise, we have tried to regroup similar vulnerabilities. We ended up with the categories given below.

  • Missing length check in pack_tlv_data resulting in 39 buffer overflows of the TEE_Param output buffer
  • Missing length check in DRM_Secure_Store_Read resulting in 25 buffer overflows (23 stack-allocated, 2 heap-allocated)
  • Missing length check in getvaluewithtypeandindex resulting in 13 stack buffer overflows
  • Missing length checks in GetOCSPResponse resulting in 14 stack buffer overflows
  • Missing length checks in Secure_Store_EncryptWrite and Secure_Store_PlainWrite resulting in 12 stack buffer overflows
  • Missing length and offset checks in NOVEL_CHDRM_Copyordecrypt resulting in:
    • 1 stack buffer overflow
    • 6 ION buffer OOB writes
    • 3 ION buffer OOB reads
  • Missing length checks in NOVEL_CHDRM_SetDRMCertData resulting in 2 stack buffer overflows
  • Missing length checks in NOVEL_CHDRM_SetRegisterResData resulting in:
    • 1 stack buffer overflow
    • 1 BSS buffer overflow
  • Missing length checks after calling unpack_tlv_data resulting in 6 heap buffer overflows
  • Stack / heap / bss pointer leaks in DRM_AES_Encrypt_xxx
  • Integer underflow in unpack_tlv_data resulting in OOB read/buffer overread
  • Integer underflow in find_tlv_data resulting in OOB read/buffer overread
  • Unchecked malloc return values resulting in 10 null pointer dereferences

Missing Length Check in pack_tlv_data

The pack_tlv_data function is used to pack TLV data into an output buffer. The size of the output buffer is user-controlled and never checked prior to writing to it, resulting in a buffer overflow if the buffer is not big enough for the data being written to it.

void pack_tlv_data(uint8_t type, uint8_t *value, uint32_t length, uint8_t *outbuf, uint32_t *outbuf_size) {
    outbuf[0] = type;
    *(uint32_t *)(outbuf + 1) = bswap32(length);
    if (length)
        NOVEL_CHDRMw_Memcpy(outbuf + 5, value, length);
    *outbuf_size = length + 5;
}

As an example, we will look at a vulnerable call to pack_tlv_data in NOVEL_CHDRM_GetDeviceID, which is the handler for the command ID #0x1.

TEE_Result TA_InvokeCommandEntryPoint(
        void *sessionContext,
        uint32_t commandID,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    /* [...] */
    if (commandID == 1) {
        DRM_CheckParamType(paramTypes, 5, 6, 0, 0);
        /* [...] */
        NOVEL_CHDRM_GetDeviceID(params[1].memref.buffer, &params[1].memref.size);
    }
    /* [...] */
}

NOVEL_CHDRM_GetDeviceID passes the TEE_Param output buffer obuf1_addr and its size obuf1_size to pack_tlv_data without any prior verification. This will write 0x20 bytes into obuf1_addr no matter its actual size. If, for example, we provide a TEE_Param output buffer of size 0x8 bytes, it will result in a buffer overflow of 0x18 bytes.

int NOVEL_CHDRM_GetDeviceID(uint8_t *obuf1_addr, uint32_t *obuf1_size) {
    pack_tlv_data(END_OF_CONTENT, &OTPChipIDHex, 0x20, obuf1_addr, obuf1_size);
    return 0;
}

In this particular example, the TA won't crash. But there are calls to pack_tlv_data with controllable sizes which are then able to cross page boundary, causing a crash.

We identified 39 vulnerable calls to pack_tlv_data, all writing OOB of the TEE_Param output buffer:

Address Caller Impact
0x1420 NOVEL_CHDRM_GetDeviceID+20 0x20-byte buffer overflow of params[1]
0x1540 NOVEL_CHDRM_GetSerialNumber+10c n-byte buffer overflow of params[1]
0x1638 NOVEL_CHDRM_GetSecurityReqData+bc 0x10-byte buffer overflow of params[1]
0x165c NOVEL_CHDRM_GetSecurityReqData+e0 0x20-byte buffer overflow of params[1]
0x169c NOVEL_CHDRM_GetSecurityReqData+120 4-byte buffer overflow of params[1]
0x1740 NOVEL_CHDRM_GetSecurityReqData+1c4 n-byte buffer overflow of params[1]
0x1774 NOVEL_CHDRM_GetSecurityReqData+1f8 n-byte buffer overflow of params[1]
0x199c NOVEL_CHDRM_GetSecurityReqData+420 n-byte buffer overflow of params[1]
0x1a44 NOVEL_CHDRM_GetSecurityReqData+4c8 n-byte buffer overflow of params[1]
0x1d30 NOVEL_CHDRM_GetSignature+22c n-byte buffer overflow of params[1]
0x2004 NOVEL_CHDRM_GetAttestationSignature+244 n-byte buffer overflow of params[1]
0x2828 NOVEL_CHDRM_GetDRMTime+40 4-byte buffer overflow of params[1]
0x28d0 NOVEL_CHDRM_GetTAVersion+90 n-byte buffer overflow of params[1]
0x2f90 NOVEL_CHDRM_GetLicenseReqData+66 8 1-byte buffer overflow of params[1]
0x30ec NOVEL_CHDRM_GetLicenseReqData+7c4 0x14-byte buffer overflow of params[1]
0x3154 NOVEL_CHDRM_GetLicenseReqData+82c 0x10-byte buffer overflow of params[1]
0x3184 NOVEL_CHDRM_GetLicenseReqData+85c 4-byte buffer overflow of params[1]
0x31bc NOVEL_CHDRM_GetLicenseReqData+894 n-byte buffer overflow of params[1]
0x31e8 NOVEL_CHDRM_GetLicenseReqData+8c0 0x20-byte buffer overflow of params[1]
0x35ac NOVEL_CHDRM_DelLicenseReqData+280 1-byte buffer overflow of params[1]
0x5134 NOVEL_CHDRM_SetDRMDataLicenseResData+1ae0 1-byte buffer overflow of params[1]
0x5ff8 NOVEL_CHDRM_GetDRMCertData+234 n-byte buffer overflow of params[1]
0x6110 NOVEL_CHDRM_GetDRMCertData+34c n-byte buffer overflow of params[1]
0x6220 NOVEL_CHDRM_GetDRMCertData+45c n-byte buffer overflow of params[1]
0x6348 NOVEL_CHDRM_GetDRMCertData+584 n-byte buffer overflow of params[1]
0x644c NOVEL_CHDRM_GetDRMCertData+688 n-byte buffer overflow of params[1]
0x6554 NOVEL_CHDRM_GetDRMCertData+790 n-byte buffer overflow of params[1]
0x6e30 NOVEL_CHDRM_GetRegisterReqData+13c 1-byte buffer overflow of params[1]
0x6ebc NOVEL_CHDRM_GetRegisterReqData+1c8 n-byte buffer overflow of params[1]
0x7028 NOVEL_CHDRM_GetRegisterReqData+334 2-byte buffer overflow of params[1]
0x70f8 NOVEL_CHDRM_GetRegisterReqData+404 n-byte buffer overflow of params[1]
0x7154 NOVEL_CHDRM_GetRegisterReqData+460 2-byte buffer overflow of params[1]
0x724c NOVEL_CHDRM_GetRegisterReqData+558 n-byte buffer overflow of params[1]
0x7274 NOVEL_CHDRM_GetRegisterReqData+580 20-byte buffer overflow of params[1]
0x72d4 NOVEL_CHDRM_GetRegisterReqData+5e0 n-byte buffer overflow of params[1]
0x7db4 NOVEL_CHDRM_GetRegisterStatus+124 n-byte buffer overflow of params[1]
0x7df8 NOVEL_CHDRM_GetRegisterStatus+168 1-byte buffer overflow of params[1]
0x7f0c NOVEL_CHDRM_GetRegisterStatus+27c 1-byte buffer overflow of params[1]
0x7fa8 NOVEL_CHDRM_GetRegisterStatus+318 1-byte buffer overflow of params[1]

Missing Length Check in DRM_Secure_Store_Read

DRM_Secure_Store_Read reads data from the secure storage using either Secure_Store_PlainRead or Secure_Store_EncryptRead, depending on which file is being read.

unsigned int DRM_Secure_Store_Read(void* data, uint32_t *data_len, uint32_t store_type)
{
    if (store_type - 0x10 <= 0x4F) {
        // drmCipherData
        return Secure_Store_EncryptRead("64726D43697068657244617461.cli",
            data, data_len, store_type, &version);
    }

    if (store_type - 0x60 <= 0x9F) {
        // drmPlainData
        return Secure_Store_PlainRead("64726D506C61696E44617461.cli",
            data, data_len, store_type, &version);
    }

    if (store_type <= 0xF) {
        // drmCipherData
        ret1 = Secure_Store_EncryptRead("64726D43697068657244617461.cli",
            data, &outsize, store_type, &version);
        *data_len = outsize;
        // drmPlainData
        ret2 = Secure_Store_PlainRead("64726D506C61696E44617461.cli",
            data + outsize, &outsize, store_type, &version);
        *data_len += outsize;
        return ret1 | ret2;
    }

    // [...]
}

Here Secure_Store_EncryptRead will:

  • read the file filename;
  • decrypt the data inside it;
  • retrieve the TLV object based on the value store_type;
  • copy the result into the output buffer data.
unsigned int Secure_Store_EncryptRead(uint8_t *filename, void *data,
    uint32_t *outsize, uint8_t store_type, uint32_t *version) {
    /* [...] */
    // Reads the file from the file
    HiTEE_FlashRead(filename, &rpmb_data, 0x2000, &rpmb_data_size);
    rpmb_data_size = _byteswap_ulong(rpmb_data.size);
    /* [...] */
    // In-place RPMB data decryption.
    Secure_Store_DataDecrypt(rpmb_data.data, &rpmb_data_size);
    /* [...] */
    // Extracts data from the file according to the `store_type` argument.
    unpack_tlv_data(store_type, rpmb_data.data, &rpmb_data_size,
        &unpacked_tlv_buf_p, &unpacked_tlv_len);
    /* [...] */
    // Copies the result into the `data` output buffer provided to the function.
    NOVEL_CHDRMw_Memcpy(data, unpacked_tlv_buf_p, unpacked_tlv_len);
    /* [...] */
}

Note: Secure_Store_PlainRead performs the same operations apart from the decryption process.

The output buffer data is first passed as argument to DRM_Secure_Store_Read and is then propagated to Secure_Store_PlainRead and/or Secure_Store_EncryptRead. However, its size is never checked, which could result in a buffer overflow when data gets copied into it.

As an example, we will look at a vulnerable call to DRM_Secure_Store_Read in NOVEL_CHDRM_GetSerialNumber, which is the handler for the command ID #0x17.

TEE_Result TA_InvokeCommandEntryPoint(
        void *sessionContext,
        uint32_t commandID,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    /* [...] */
    if (commandID == 0x17) {
        DRM_CheckParamType(paramTypes, 5, 6, 0, 0);
        /* [...] */
        NOVEL_CHDRM_GetSerialNumber(params[1].memref.buffer,
            &params[1].memref.size);
    }
    /* [...] */
}

NOVEL_CHDRM_GetSerialNumber reads from the secure storage the file with type 0x60 using DRM_Secure_Store_Read. The data read will be written into a stack-allocated buffer of size 2048.

int NOVEL_CHDRM_GetSerialNumber(uint8_t *obuf1_addr, uint32_t obuf1_size) {
    /* [...] */
    uint8_t cert[2048];
    memset(cert, 0, sizeof(cert));
    /* [...] */
    DRM_Secure_Store_Read(cert, &cert_size, 0x60);
    /* [...] */
}

However, a user could call NOVEL_CHDRM_SetDRMCertData to write into the secure storage the value corresponding to type 0x60 using DRM_Secure_Store_Write. The file data and size come from a call to unpack_tlv_data operating on the TEE_Param input buffer, which is user-controlled. Thus, it should be possible to write more than 2048 bytes, resulting in a buffer overflow when this data is read using DRM_Secure_Store_Read.

int NOVEL_CHDRM_SetDRMCertData(uint8_t *ibuf0_addr, uint32_t ibuf0_size) {
    /* [...] */
    if (!unpack_tlv_data(0x16, ibuf0_addr, &ibuf0_size, &cert, &cert_size)) {
        DRM_Secure_Store_Write(cert, cert_size, 0x60);
        /* [...] */
    }
    /* [...] */
}

We identified 25 vulnerable calls to DRM_Secure_Store_Read, all potentially resulting in buffer overflows:

Address Caller Impact
0x149c NOVEL_CHDRM_GetSerialNumber+68 Stack-based buffer overflow (if size > 2048)
0x16c8 NOVEL_CHDRM_GetSecurityReqData+14c Stack-based buffer overflow (if size > 2048)
0x16e4 NOVEL_CHDRM_GetSecurityReqData+168 Stack-based buffer overflow (if size > 2048)
0x17b4 NOVEL_CHDRM_GetSecurityReqData+238 Stack-based buffer overflow (if size > 4)
0x19e8 NOVEL_CHDRM_GetSecurityReqData+46c Stack-based buffer overflow (if size > 8)
0x1bb0 NOVEL_CHDRM_GetSignature+ac Stack-based buffer overflow (if size > 2048)
0x2344 NOVEL_CHDRM_VerifySignature+2b8 Stack-based buffer overflow (if size > 2048)
0x2fe0 NOVEL_CHDRM_GetLicenseReqData+6b8 Stack-based buffer overflow (if size > 2048)
0x303c NOVEL_CHDRM_GetLicenseReqData+714 Stack-based buffer overflow (if size > 2048)
0x3974 NOVEL_CHDRM_SetDRMDataLicenseResData+320 Stack-based buffer overflow (if size > 2048)
0x53c4 NOVEL_CHDRM_SetDRMCertData+1dc Stack-based buffer overflow (if size > 2048)
0x546c NOVEL_CHDRM_SetDRMCertData+284 Stack-based buffer overflow (if size > 2048)
0x5f9c NOVEL_CHDRM_GetDRMCertData+1d8 Stack-based buffer overflow (if size > 2048)
0x60b0 NOVEL_CHDRM_GetDRMCertData+2ec Stack-based buffer overflow (if size > 2048)
0x61c0 NOVEL_CHDRM_GetDRMCertData+3fc Stack-based buffer overflow (if size > 2048)
0x62d4 NOVEL_CHDRM_GetDRMCertData+510 Stack-based buffer overflow (if size > 2048)
0x63f4 NOVEL_CHDRM_GetDRMCertData+630 Stack-based buffer overflow (if size > 2048)
0x64f8 NOVEL_CHDRM_GetDRMCertData+734 Heap-based buffer overflow (any size)
0x6d98 NOVEL_CHDRM_GetRegisterReqData+a4 Stack-based buffer overflow (if size > 2048)
0x6e64 NOVEL_CHDRM_GetRegisterReqData+170 Heap-based buffer overflow (if size > 256)
0x7cf4 NOVEL_CHDRM_GetRegisterStatus+64 Stack-based buffer overflow (if size > 2048)
0x7d64 NOVEL_CHDRM_GetRegisterStatus+d4 Stack-based buffer overflow (if size > 2048)
0x7e28 NOVEL_CHDRM_GetRegisterStatus+198 Stack-based buffer overflow (if size > 2048)
0x7e90 NOVEL_CHDRM_GetRegisterStatus+200 Stack-based buffer overflow (if size > 2048)
0x7f3c NOVEL_CHDRM_GetRegisterStatus+2ac Stack-based buffer overflow (if size > 2048)

Missing Length Check in getvaluewithtypeandindex

getvaluewithtypeandindex parses the input buffer inbuf to extract the value with the given type and key type. It does so by first locating the value inside the input buffer, computing its offset and length. The value is then copied into the output buffer using NOVEL_CHDRMw_Memcpy. The value length is bounded by the input buffer size insize. The output buffer length is never checked, resulting in a potential buffer overflow.

uint32_t getvaluewithtypeandindex(
        uint32_t type,
        uint32_t key_type,
        void *inbuf,
        uint32_t insize,
        void *outbuf,
        uint32_t *outsize_p)
{
    /* [...] */
    /*
     * Parses the input buffer `inbuf` to compute `value_size` and `value_offset`.
     * A value is then extracted from `inbuf` and written into `outbuf`.
     */
    if (insize >= value_size + value_offset) {
        NOVEL_CHDRMw_Memcpy(outbuf, inbuf + value_offset, value_size);
        *outsize_p = value_size;
        return 0;
    }
    /* [...] */
}

As an example, we will look at a vulnerable call to getvaluewithtypeandindex in NOVEL_CHDRM_SetDRMDataLicenseResData, which is the handler for the command ID #0xE.

In NOVEL_CHDRM_SetDRMDataLicenseResData, a buffer (of unbounded size) is first extracted from ibuf0_addr and copied into data1_p. This buffer is then used as input to a call to getvaluewithtypeandindex. The output of this call is a stack variable OutputProtection of size 4. As mentioned above, getvaluewithtypeandindex can be made to copy more than 4 bytes into its output, resulting in a buffer overflow.

unsigned int NOVEL_CHDRM_SetDRMDataLicenseResData(
        uint8_t *ibuf0_addr,
        uint32_t ibuf0_size,
        uint8_t *obuf1_addr,
        uint32_t *obuf1_size)
{
    /* [...] */
    unpack_tlv_data(0xD, ibuf0_addr, &ibuf0_size, &data1_p, &data1_size);
    /* [...] */
    OutputProtection = 0;
    OutputProtection_size = 0;
    getvaluewithtypeandindex(4, 6, data1_p, data1_size,
                             &OutputProtection, &OutputProtection_size);
    /* [...] */

We identified 13 vulnerable calls to getvaluewithtypeandindex, all potentially resulting in stack buffer overflows:

Address Caller Impact
0x3aa4 NOVEL_CHDRM_SetDRMDataLicenseResData+450 Stack-based buffer overflow (if size > 512)
0x3b14 NOVEL_CHDRM_SetDRMDataLicenseResData+4c0 Stack-based buffer overflow (if size > 2048)
0x3b84 NOVEL_CHDRM_SetDRMDataLicenseResData+530 Stack-based buffer overflow (if size > 128)
0x3c3c NOVEL_CHDRM_SetDRMDataLicenseResData+5e8 Stack-based buffer overflow (if size > 128)
0x3f74 NOVEL_CHDRM_SetDRMDataLicenseResData+920 Stack-based buffer overflow (if size > 256)
0x4188 NOVEL_CHDRM_SetDRMDataLicenseResData+b34 Stack-based buffer overflow (if size > 32)
0x42c8 NOVEL_CHDRM_SetDRMDataLicenseResData+c74 Stack-based buffer overflow (if size > 32)
0x443c NOVEL_CHDRM_SetDRMDataLicenseResData+de8 Stack-based buffer overflow (if size > 32)
0x4570 NOVEL_CHDRM_SetDRMDataLicenseResData+f1c Stack-based buffer overflow (if size > 32)
0x48e0 NOVEL_CHDRM_SetDRMDataLicenseResData+128c Stack-based buffer overflow (if size > 32)
0x4a1c NOVEL_CHDRM_SetDRMDataLicenseResData+13c8 Stack-based buffer overflow (if size > 4)
0x4ad0 NOVEL_CHDRM_SetDRMDataLicenseResData+147c Stack-based buffer overflow (if size > 256)
0x4be4 NOVEL_CHDRM_SetDRMDataLicenseResData+1590 Stack-based buffer overflow (if size > 16)

Missing Length Checks in GetOCSPResponse

NOVEL_CHDRM_SetDRMCertData is the handler for the command ID #0xF which takes an input TEE_Param buffer and its size as arguments. It first calls unpack_tlv_data to extract a value from the input buffer, which is user-controlled, before passing it to the function GetOCSPResponse.

int NOVEL_CHDRM_SetDRMCertData(uint8_t *ibuf0_addr, uint32_t ibuf0_size) {
    /* [...] */
    ibuf0_size_cpy = ibuf0_size;
    rsp_status_t rsp_status;
    /* [...] */
    if (!unpack_tlv_data(0x1C, ibuf0_addr, &ibuf0_size_cpy, &data_p, &data_size)) {
        GetOCSPResponse(data_p, data_size, &rsp_status);
        /* [...] */
    }
    /* [...] */
}

GetOCSPResponse parses the ASN.1 data located in the input buffer data_p. In this function, there are many instances of the following pattern:

  • a call to asn1_get_tag which increments the data pointer and retrieves the length (unbounded) of the current tag
  • a call to NOVEL_CHDRMw_Memcpy that copies the tag data from the input buffer into the rsp_status_p output buffer
int GetOCSPResponse(uint8_t *data_p, uint32_t data_size, rsp_status_t *rsp_status) {
    /* [...] */
    asn1_get_tag(&data_p, seq0_end, &length, OID);
    /* [...] */
    NOVEL_CHDRMw_Memcpy(&rsp_status->field_14, data_p, length);
    data_p += length;
    /* [...] */
}

Since the length of the tag is given without any checking to NOVEL_CHDRMw_Memcpy, this will result in a buffer overflow on rsp_status_p (which is a stack-allocated buffer). We identified 14 vulnerable patterns to GetOCSPResponse, all resulting in a stack buffer overflow of rsp_status_p:

Address Caller Impact
0x3a7d0 GetOCSPResponse+1e0 Stack-based buffer overflow
0x3a8ec GetOCSPResponse+2fc Stack-based buffer overflow
0x3a994 GetOCSPResponse+3a4 Stack-based buffer overflow
0x3abc4 GetOCSPResponse+5d4 Stack-based buffer overflow
0x3ac84 GetOCSPResponse+694 Stack-based buffer overflow
0x3acec GetOCSPResponse+6fc Stack-based buffer overflow
0x3ad54 GetOCSPResponse+764 Stack-based buffer overflow
0x3ae18 GetOCSPResponse+828 Stack-based buffer overflow
0x3aee0 GetOCSPResponse+8f0 Stack-based buffer overflow
0x3b084 GetOCSPResponse+a94 Stack-based buffer overflow
0x3b138 GetOCSPResponse+b48 Stack-based buffer overflow
0x3b1f8 GetOCSPResponse+c08 Stack-based buffer overflow
0x3b2bc GetOCSPResponse+ccc Stack-based buffer overflow
0x3b338 GetOCSPResponse+d48 Stack-based buffer overflow

Missing Length Checks in Secure_Store_EncryptWrite and Secure_Store_PlainWrite

Secure_Store_EncryptWrite, which can be called via DRM_Secure_Store_Write with user-controlled buffer and buffer_size arguments, first packs buffer as a TLV object into a heap-allocated buffer buffer_tlv of size buffer_size + 5. It then reads the 0x2000 bytes of the file into the stack-allocated structure rpmb_data. This function then exhibits many times the same vulnerable pattern, which is a call to NOVEL_CHDRMw_Memcpy with:

  • rpmb_data.data as the destination;
  • buffer_tlv as the source;
  • buffer_tlv_size as the length.
TEE_Result Secure_Store_EncryptWrite(
        char *filename,
        void *buffer,
        uint32_t buffer_size,
        uint32_t store_type,
        uint32_t a5)
{
    /* [...] */
    rpmb_data_t rpmb_data;
    /* [...] */
    buffer_tlv = NOVEL_CHDRMw_Malloc(buffer_size + 5);
    pack_tlv_data(store_type, buffer, buffer_size, buffer_tlv, &buffer_tlv_size);
    ret = HiTEE_FlashRead(filename, &rpmb_data, 0x2000, &rpmb_data_size);
    if (ret == 0xFFFF7106) {
        /* [...] */
        NOVEL_CHDRMw_Memcpy(&rpmb_data.data[secure_store_len], buffer_tlv, buffer_tlv_size);
        secure_store_len += buffer_tlv_size;
    }
    /* [...] */
}

Because rpmb_data is of size 0x2000, and buffer_tlv_size is user-controlled, a stack-buffer overflow occurs.

Note: Secure_Store_PlainWrite contains the same vulnerable patterns as Secure_Store_EncryptWrite. These 2 functions can be called in the same manner as explained in the Missing Length Check in DRM_Secure_Store_Read section.

All in all, we identified 12 instances of the vulnerable pattern leading to a stack buffer overflow:

Address Caller Impact
0x37e60 Secure_Store_EncryptWrite+1cc Stack-based buffer overflow
0x38058 Secure_Store_EncryptWrite+3c4 Stack-based buffer overflow
0x380a0 Secure_Store_EncryptWrite+40c Stack-based buffer overflow
0x380d8 Secure_Store_EncryptWrite+444 Stack-based buffer overflow
0x38120 Secure_Store_EncryptWrite+48c Stack-based buffer overflow
0x38190 Secure_Store_EncryptWrite+4fc Stack-based buffer overflow
Address Caller Impact
0x385c4 Secure_Store_PlainWrite+1c4 Stack-based buffer overflow
0x38764 Secure_Store_PlainWrite+364 Stack-based buffer overflow
0x387ac Secure_Store_PlainWrite+3ac Stack-based buffer overflow
0x387e4 Secure_Store_PlainWrite+3e4 Stack-based buffer overflow
0x3882c Secure_Store_PlainWrite+42c Stack-based buffer overflow
0x38878 Secure_Store_PlainWrite+478 Stack-based buffer overflow

Missing Length and Offset Checks in NOVEL_CHDRM_Copyordecrypt

The NOVEL_CHDRM_Copyordecrypt function extracts offsets and lengths from the TEE_Param input buffer provided by the user, byte-swaps them, and uses them to perform various operations. In particular, many of the calls to NOVEL_CHDRMw_Memcpy and DRM_SVP_AES with arguments derived from these user-controlled values lead to ION buffer OOB writes, ION buffer OOB reads, or even a stack buffer overflow.

For example, here is the stack-based buffer overflow:

int NOVEL_CHDRM_Copyordecrypt(TEE_Param params[4]) {
    /* [...] */
    int userprovided_license[4];

    /* [...] */
    ibuf3_addr = (uint32_t *)params[3].memref.buffer;
    ibuf3_int0 = bswap32(ibuf3_addr[0]); /* user-controlled */
    ibuf3_int1 = bswap32(ibuf3_addr[1]); /* user-controlled */
    if (ibuf3_int1)
        /* Stack-based buffer overflow */
        NOVEL_CHDRMw_Memcpy(userprovided_license, ibuf3_addr + 2, ibuf3_int1);
    /* [...] */
}

Here is one of the calls to NOVEL_CHDRMw_Memcpy resulting in a ION buffer OOB write/OOB read:

int NOVEL_CHDRM_Copyordecrypt(TEE_Param params[4]) {
    /* [...] */
    NOVEL_CHDRM_Mmap(params[0].memref.buffer, 0, params[0].memref.size, &ionmap0_addr, 1, 1);
    NOVEL_CHDRM_Mmap(params[2].memref.buffer, 0, params[0].memref.size, &ionmap2_addr, ibuf3_int0 == 0, 1);

    /* [...] */
    ibuf3_curr_ptr = ibuf3_addr + 7;
    before_datalen = 0;
    before_offset = 0;
    while (1) {
        offset = bswap32(ibuf3_curr_ptr[0]); /* user-controlled */
        datalen = bswap32(ibuf3_curr_ptr[1]); /* user-controlled */

        /* [...] */
        if (before_datalen + before_offset != offset)
            /* ION buffer OOB write/OOB read */
            NOVEL_CHDRMw_Memcpy(
                ionmap2_addr + before_datalen + before_offset,
                ionmap0_addr + before_datalen + before_offset,
                offset - (before_datalen + before_offset));

        /* [...] */
        before_datalen = datalen;
        before_offset = offset;
    }
    /* [...] */
}

Here is one of the calls to DRM_SVP_AES resulting in a ION buffer OOB write/OOB read:

int NOVEL_CHDRM_Copyordecrypt(TEE_Param params[4]) {
    /* [...] */
    ibuf3_curr_ptr = ibuf3_addr + 7;
    tmp_enc_datalen = 0;
    global_offset = 0;
    while (1) {
        offset = bswap32(ibuf3_curr_ptr[0]); /* user-controlled */
        datalen = bswap32(ibuf3_curr_ptr[1]); /* user-controlled */

        if (tmp_enc_datalen) {
            /* [...] */
        } else {
            enc_datalen = datalen;
        }

        if ((flag & 2) != 0) {
            /* [...] */
        } else if (enc_datalen <= 0xF) {
            /* [...] */
        } else {
            tmp_enc_datalen = enc_datalen & 0xF;
            if ((enc_datalen & 0xF) != 0) {
                /* [...] */
            } else {
                /* ION buffer OOB write/OOB read */
                DRM_SVP_AES(aes_op, aes_obj, flag,
                            ionmap0_addr + offset + global_offset,
                            ionmap2_addr + offset + global_offset,
                            enc_datalen);
                /* [...] */
            }
        }
    }
    /* [...] */
}

We identified 7 vulnerable calls to NOVEL_CHDRMw_Memcpy in NOVEL_CHDRM_Copyordecrypt:

Address Caller Impact
0x8204 NOVEL_CHDRM_Copyordecrypt+108 Stack-based buffer overflow (if size > 0x10)
0x8584 NOVEL_CHDRM_Copyordecrypt+488 ION buffer OOB write/OOB read
0x85c0 NOVEL_CHDRM_Copyordecrypt+4c4 ION buffer OOB read
0x8634 NOVEL_CHDRM_Copyordecrypt+538 ION buffer OOB write
0x864c NOVEL_CHDRM_Copyordecrypt+550 ION buffer OOB write
0x8724 NOVEL_CHDRM_Copyordecrypt+628 ION buffer OOB read
0x8850 NOVEL_CHDRM_Copyordecrypt+754 ION buffer OOB read

We also identified 7 vulnerable calls to DRM_SVP_AES in NOVEL_CHDRM_Copyordecrypt:

Address Caller Impact
0x86a4 NOVEL_CHDRM_Copyordecrypt+5a8 ION buffer OOB write/OOB read
0x8758 NOVEL_CHDRM_Copyordecrypt+65c ION buffer OOB write/OOB read
0x87e8 NOVEL_CHDRM_Copyordecrypt+6ec ION buffer OOB write/OOB read

Missing Length Checks in NOVEL_CHDRM_SetDRMCertData

NOVEL_CHDRM_SetDRMCertData extracts two certificates server_Cert_p and server_CACert_p from a TEE_Param input buffer. Without checking their size, the function will then copy them into two stack-allocated buffers, respectively server_Cert_cpy_len and server_CACert_cpy. This can result in a stack-based buffer overflow.

int NOVEL_CHDRM_SetDRMCertData(int *ibuf0_addr, unsigned int ibuf0_size) {
    char server_CACert_cpy[2048];
    char server_Cert_cpy[2048];
    /* [...] */
    ibuf0_size_cpy = ibuf0_size;
    unpack_tlv_data(0x18, ibuf0_addr, &ibuf0_size_cpy, &server_Cert_p,
        &server_Cert_len);
    unpack_tlv_data(0x19, ibuf0_addr, &ibuf0_size_cpy, &server_CACert_p,
        &server_CACert_len);
    /* [...] */
    if (server_CACert_p)
        NOVEL_CHDRMw_Memcpy(server_CACert_cpy, server_CACert_p, server_CACert_len);
    /* [...] */
    if (server_Cert_p)
        NOVEL_CHDRMw_Memcpy(server_Cert_cpy, server_Cert_p, server_Cert_len);
    /* [...] */
}

Missing Length Checks in NOVEL_CHDRM_SetRegisterResData

The NOVEL_CHDRM_SetRegisterResData contains the same vulnerable pattern twice:

  • it parses a buffer of user-controlled size from the TLV data in the TEE_Param input buffer
  • it calls DRM_AES_Encrypt_cbc with this buffer as input and a fixed-size buffer as the output
uint8_t Root_CA_Cert[0x800]; /* in the BSS */

int NOVEL_CHDRM_SetRegisterResData(uint8_t *ibuf0_addr, uint32_t ibuf0_size) {
    char aes_outbuf[0x800];
    /* [...] */
    unpack_tlv_data(0x21, ibuf0_addr, &ibuf0_size, &aes_inbuf, &aes_inbuf_size);
    /* [...] */
    /* 1st Stack-based buffer overflow */
    DRM_AES_Encrypt_cbc(aes_inbuf, aes_inbuf_size, aes_outbuf, aes_key, aes_iv, 1);

    /* [...] */
    unpack_tlv_data(0x42, ibuf0_addr, &ibuf0_size, &root_ca_inbuf, &root_ca_inbuf_len);
    /* [...] */
    /* 2nd Stack-based buffer overflow */
    DRM_AES_Encrypt_cbc(root_ca_inbuf, root_ca_inbuf_len, Root_CA_Cert, aes_key, aes_iv, 1);
    /* [...] */
}

This pattern can result in a buffer overflow:

  • the first call to DRM_AES_Encrypt_cbc overflows a stack-allocated buffer
  • the second call to DRM_AES_Encrypt_cbc overflows a buffer located in the BSS

Missing / Faulty Length Checks When Calling NOVEL_CHDRMw_MemCompare

The NOVEL_CHDRM_SetRegisterResData function does not check the user-controlled length it passes to NOVEL_CHDRMw_MemCompare, allowing to leak byte by byte the data following the BSS buffer sg_salt.

int NOVEL_CHDRM_SetRegisterResData(int *ibuf0_addr, unsigned int ibuf0_size) {
    /* [...] */
    unpack_tlv_data(0x34, ibuf0_addr, &ibuf0_size, &salt_des, &salt_des_size);
    if (NOVEL_CHDRMw_MemCompare(salt_des, &sg_salt, salt_des_size)) {
        /* [...] */
        return 0xFFFF0000;
    }
    /* [...] */
}

The NOVEL_CHDRM_SetDrmTime function checks the user-controlled length passed to NOVEL_CHDRMw_MemCompare, but only AFTER the call, resulting in an overread of the BSS buffer OTPChipIDHex.

int NOVEL_CHDRM_SetDrmTime(uint8_t *ibuf0_addr, uint32_t ibuf0_size) {
    /* [...] */
    if (!unpack_tlv_data(0x36, ibuf0_addr, &ibuf0_size, &chipid_p, &chipid_size)
          && (NOVEL_CHDRMw_MemCompare(chipid_p, &OTPChipIDHex, chipid_size) || chipid_size != 0x20) ) {
        /* [...] */
    }
    /* [...] */
}

We identified 11 missing / faulty length checks when calling NOVEL_CHDRMw_MemCompare (they might not all be exploitable):

Address Caller Impact
0x2ef8 NOVEL_CHDRM_GetLicenseReqData+5d0 Buffer overread
0x3554 NOVEL_CHDRM_DelLicenseReqData+228 Buffer overread
0x40a0 NOVEL_CHDRM_SetDRMDataLicenseResData+a4c Buffer overread
0x50d8 NOVEL_CHDRM_SetDRMDataLicenseResData+1a84 Buffer overread
0x7464 NOVEL_CHDRM_SetRegisterResData+d4 Buffer overread
0x8c40 NOVEL_CHDRM_SetDrmTime+e4 Buffer overread
0x35c80 DRM_DelLicenseForCEKID+38 Buffer overread
0x35d38 DRM_FindLicenseForCEKID+38 Buffer overread
0x35fd4 DRM_FindLicenseForCEKIDExt+38 Buffer overread
0x36170 DRM_FindLocalLicenseByCEKID+24 Buffer overread
0x36388 DRM_FindMemLicenseByCEKID+24 Buffer overread

Missing Length Checks After Calling unpack_tlv_data

The NOVEL_CHDRM_SetDrmTime function contains one of the instances of a vulnerable pattern:

  • a value is unpacked from the TEE_Param input buffer using unpack_tlv_data
  • the value is used as if it is of size >= 4 (in this example)
int NOVEL_CHDRM_SetDrmTime(uint8_t *ibuf0_addr, uint32_t ibuf0_size) {
    /* [...] */
    unpack_tlv_data(1, ibuf0_addr, &ibuf0_size_, &data_p, &length_p);
    /* [...] */
    FUN(
        "%s %s: drmtime[%x],[%x],[%x]\n\n",
        "[Warning]",
        "NOVEL_CHDRM_SetDrmTime",
        data_p[0],
        data_p[1]),
        data_p[2]));
    DRM_Set_CurTime(*(uint32_t *)data_p);
    /* [...] */
}

Because the actual length of the value read is never checked (for example, a single byte is read when four are expected), this can result in a heap buffer overread (since unpacked values are allocated from the heap).

We identified 6 missing length checks after calls to unpack_tlv_data, all potentially resulting in a heap buffer overread:

Address Caller Impact
0x2bb8 NOVEL_CHDRM_GetLicenseReqData+290 Heap-allocated buffer overread
0x3418 NOVEL_CHDRM_DelLicenseReqData+ec Heap-allocated buffer overread
0x39dc NOVEL_CHDRM_SetDRMDataLicenseResData+388 Heap-allocated buffer overread
0x74ac NOVEL_CHDRM_SetRegisterResData+11c Heap-allocated buffer overread
0x79a8 NOVEL_CHDRM_SetRegisterResData+618 Heap-allocated buffer overread
0x8b98 NOVEL_CHDRM_SetDrmTime+3c Heap-allocated buffer overread

Stack / Heap / BSS Pointer Leaks in DRM_AES_Encrypt_xxx

There are multiple log strings used in the trusted application that leak information about the address space, such as stack pointers, heap pointers, and bss pointers.

Below is an example of one instance of this vulnerability in the function DRM_AES_Encrypt_cbc.

TEE_Result DRM_AES_Encrypt_cbc(
        char *buffer_input,
        int buffer_len,
        char *buffer_output,
        const void *key,
        int iv,
        int mode)
{
    /* [...] */
    tee_print(0,
        "%s %d:buffer_input = %p, buffer_len = %d,buffer_output = %p,key = %p ",
        "[error]",
        0x195,
        buffer_input,
        buffer_len,
        buffer_output,
        key);
    /* [...] */
}

This function leaks the pointers passed as argument through tee_print, which generates a log that can be read from logcat. For an example of the types of pointers that can be leaked, we can have a look at the call to DRM_AES_Encrypt_cbc in NOVEL_CHDRM_SetRegisterResData.

uint8_t Root_CA_Cert[0x800]; /* in the BSS */

int NOVEL_CHDRM_SetRegisterResData(uint8_t *ibuf0_addr, uint32_t ibuf0_size) {
    /* [...] */
    uint8_t aes_key[8];
    /* [...] */
    unpack_tlv_data(0x42, ibuf0_addr, &ibuf0_size, &root_ca_inbuf, &root_ca_inbuf_len);
    /* [...] */
    DRM_AES_Encrypt_cbc(root_ca_inbuf, root_ca_inbuf_len, Root_CA_Cert, aes_key, aes_iv, 1);
    /* [...] */
}

NOVEL_CHDRM_SetRegisterResData passes the following arguments to DRM_AES_Encrypt_cbc:

  • buffer_input => root_ca_inbuf, a pointer to a heap buffer allocated by unpack_tlv_data;
  • buffer_output => Root_CA_Cert, a pointer to a BSS buffer;
  • key => aes_key, a pointer to a stack-allocated buffer.

We identified 3 log strings leaking pointers (they might not all be reachable):

Address Caller Impact
0x33218  DRM_AES_Encrypt_ecb+54 Stack Pointer Leak
0x3333C DRM_AES_Encrypt_cbc+54 Stack / Heap / BSS Pointer Leak
0x33454 DRM_AES_Encrypt_ctr+4C Pointer Leak

Integer Underflow in unpack_tlv_data

unpack_tlv_data extracts a value from an input buffer by iterating over the TLV objects it contains.

For each TLV object, it retrieves the length and a pointer to the data embedded inside it. It then checks the tag associated to the object:

  • if it matches the argument tag, the iteration stops and a heap buffer is allocated before the data extracted gets copied into it;
  • otherwise, it continues with the next TLV object inside the input buffer, until the remaining size is equal or less than 4 bytes.
int unpack_tlv_data(
        uint32_t tag,
        uint8_t *inbuf,
        uint32_t *insize_p,
        void **data_p_p,
        uint32_t *length_p)
{
    /* [...] */
    // Remaining size in the input buffer
    uint32_t remaining_size = *insize_p;
    curr_offset = 0;
    while (1) {
        // TLV object length
        data_length = bswap32(*(uint32_t *)&inbuf[curr_offset + 1]);
        // TLV object data offset
        data_offset = curr_offset + 5;
        // Returns if the tag was found
        if (inbuf[curr_offset] == tag)
            break;
        // Computes the new data offset
        curr_offset = data_length + data_offset;
        /*
         * Computes how many bytes remain in the input buffer
         * The integer underflow occurs here.
         */
        remaining_size += -5 - data_length;
        // Unsigned comparison unable to detect the underflow
        if (remaining_size <= 4)
            return 0x7FFF0001;
    }
    if (data_length) {
        data_p = NOVEL_CHDRMw_Malloc(data_length);
        *data_p_p = data_p;
        NOVEL_CHDRMw_Memcpy(data_p, &inbuf[data_offset], data_length);
    }
    *length_p = data_length;
    return 0;
}

However, the comparison on the remaining size is an unsigned one and does not take into account integer underflows. When the data length and the size of the header are subtracted from the remaining size in remaining_size += -5 - data_length, remaining_size can become negative.

For example, at the beginning of the loop, if:

  • inbuf has a size of 0x100;
  • remaining_size is 0x10;
  • curr_offset is 0xf0;
  • the extracted data_length from the input buffer is 0x1000.

Once these values are updated, we get:

  • curr_offset = 0xf0 + 5 + 0x1000 = 0x10f5;
  • remaining_size = 0x10 - 5 - 0x1000 = 0xfffff00b.

We can see that because the comparison on remaining_size is unsigned, it will pass the check since 0xfffff00b > 4. The loop would then continue its iteration and start extracting values outside the input before. For example, the length would be read at address inbuf + curr_offset + 1 = inbuf + 0x10f6, which is out of bounds of our 0x100-byte input buffer.

Integer Underflow in find_tlv_data

find_tlv_data does the same things as unpack_tlv_data, except that it doesn't allocate a buffer to copy the TLV object into it. Instead it returns the object's offset and length. It is therefore vulnerable to the same type of vulnerability as the one explained in the previous section.

int find_tlv_data(
        uint32_t tag,
        uint8_t *inbuf,
        uint32_t *inbuf_size_p,
        uint32_t *data_offset_p,
        uint32_t *data_length_p)
{
    /* [...] */
    // Remaining size in the input buffer
    uint32_t remaining_size = *inbuf_size_p;
    curr_offset = 0;
    do {
        // TLV object length
        data_length = bswap32(*(uint32_t *)(inbuf + curr_offset + 1));
        // Check the TLV object type
        if (*(uint8_t *)(inbuf + curr_offset) == tag) {
            // Return the TLV object offset and length
            *data_offset_p = curr_offset;
            *data_length_p = data_length + 5;
            return 0;
        }
        /*
         * Computes how many bytes remain in the input buffer
         * The integer underflow occurs here.
         */
        curr_offset += data_length + 5;
        remaining_size += -5 - data_length;
    } while (remaining_size > 4); // Unsigned comparison unable to detect the underflow
    return 0x7FFF0001;
}

OOB Accesses in getvaluewithtypeandindex

In the getvaluewithtypeandindex function, there can be several OOB accesses:

  • At [1], because key_length (16-bit value) is user-controlled, the access can be out of bounds by a maximum of 0xffff bytes
  • At [2], because rule_offset (8-bit value) is user-controlled, the access can be out of bounds by a maximum of 0xff bytes
  • At [3] and [4], because rule_offset (8-bit value) and rule_count (8-bit value) are user-controlled, the access can be out of bounds by a maximum of 0xffff bytes
  • At [5], because some_offset (8-bit value) is user-controlled, the access can be out of bounds by a maximum of 0xff bytes
int getvaluewithtypeandindex(
        int type,
        int key_type,
        int inbuf,
        int insize,
        void *data_buf,
        unsigned int *length_p)
{
    /* [...] */
    offset = 0;
    value_offset = 0;
    value_length = 0;

    while (1) {
        // Outer TLV length
        length = bswap16(*(uint16_t *)(inbuf + offset + 2));

        // Check the outer TLV type
        if (*(uint8_t *)(inbuf + offset) == type) {
            value_offset = offset + 4;

            if (type == 1) {
                value_length = *(uint8_t *)(inbuf + value_offset + 8) + 9;
                goto FOUND_IT;

            } else if (type == 3) {
                key_length = bswap16(*(uint16_t *)(inbuf + value_offset + 1));
                /* [1]: The access below can be OOB by a maximum of 0xffff bytes */
                if (*(uint8_t *)(inbuf + value_offset + key_length + 3) == key_type) {
                    value_offset += 3;
                    value_length = key_length;
                    goto FOUND_IT;
                }

            } else if (type == 4) {
                rule_offset = *(uint8_t *)(inbuf + value_offset + 1);
                /* [2]: The access below can be OOB by a maximum of 0xff bytes */
                rule_count = *(uint8_t *)(inbuf + value_offset + rule_offset + 2);
                for (int i = 0; i /*signed*/< rule_count: i++) {
                    rule = inbuf + value_offset + rule_offset;
                    /* [3]: The access below can be OOB by a maximum of 0xffff bytes */
                    rule_type = *(uint8_t *)(rule + 3);
                    /* [4]: The access below can be OOB by a maximum of 0xffff bytes */
                    rule_length = *(uint8_t *)(rule + 4);
                    if (key_type == rule_type) {
                        value_offset = rule_offset + 5;
                        value_length = rule_length;
                        goto FOUND_IT;
                    }
                    rule_offset += 2 + rule_length;
                }

            } else if (type == 0xff) {
                if (key_type) {
                    some_offset = *(uint8_t *)(inbuf + value_offset + 1);
                    /* [5]: The access below can be OOB by a maximum of 0xff bytes */
                    value_length = *(uint8_t *)(inbuf + value_offset + some_offset + 3);
                    value_offset += 4 + some_offset;
                } else {
                    value_length = offset;
                    value_offset = 0;
                }
                goto FOUND_IT;
            }
        }

        // Increment the current offset
        offset += 4 + length;
        if (offset /*signed*/>= insize) {
            /* [...] */
            return -1;
        }
    }

FOUND_IT:
    // Copy the value into the argument buffer
    if (value_offset + value_length /*signed*/<= insize) {
        NOVEL_CHDRMw_Memcpy(data_buf, inbuf + value_offset, value_length);
        *length_p = value_length;
        return 0;
    } else {
        /* [...] */
        return 0xFFFFFFFD;
    }
}

Unchecked Malloc Return Values

In unpack_tlv_data, the return value of the call to NOVEL_CHDRMw_Malloc (which is a wrapper around TEE_Malloc) is not checked. As a result, if the allocation failed, for example if the system is out of memory, this will result in a null pointer dereference when this pointer is used.

int unpack_tlv_data(
        uint32_t tag,
        uint8_t *inbuf,
        uint32_t *insize_p,
        void **data_p_p,
        uint32_t *length_p)
{
    /* [...] */
    // Remaining size in the input buffer
    uint32_t remaining_size = *insize_p;
    curr_offset = 0;
    while (1) {
        // TLV object length
        data_length = bswap32(*(uint32_t *)&inbuf[curr_offset + 1]);
        // TLV object data offset
        data_offset = curr_offset + 5;
        // Returns if the tag was found
        if (inbuf[curr_offset] == tag)
            break;
        // Computes the new data offset
        curr_offset = data_length + data_offset;
        /*
         * Computes how many bytes remain in the input buffer
         * The integer underflow occurs here.
         */
        remaining_size += -5 - data_length;
        // Unsigned comparison unable to detect the underflow
        if (remaining_size <= 4)
            return 0x7FFF0001;
    }
    if (data_length) {
        data_p = NOVEL_CHDRMw_Malloc(data_length);
        *data_p_p = data_p;
        NOVEL_CHDRMw_Memcpy(data_p, &inbuf[data_offset], data_length);
    }
    *length_p = data_length;
    return 0;
}

Furthermore, it is possible to force the allocation to fail by passing a negative length to NOVEL_CHDRMw_Malloc (the length is user-controlled in this case). data_p_p will then be a pointer to a null pointer and crash the next time it is used. The call to NOVEL_CHDRMw_Memcpy (which is a wrapper around memcpy_s) will also fail because the length is negative, but because its return value is also unchecked, the function will return successfully.

We have found 10 instances where the return value of NOVEL_CHDRMw_Malloc was not checked:

Address Caller Impact
0x1858 NOVEL_CHDRM_GetSecurityReqData+2dc Null pointer dereference
0x6e54 NOVEL_CHDRM_GetRegisterReqData+160 Null pointer dereference
0x1c7c NOVEL_CHDRM_GetSignature+178 Null pointer dereference
0x35ae4 DRM_NewLicense+c Null pointer dereference
0x371c8 Secure_Store_DataDecrypt+60 Null pointer dereference
0x374f8 Secure_Store_DataEncrypt+cc Null pointer dereference
0x37d30 Secure_Store_EncryptWrite+9c Null pointer dereference
0x3849c Secure_Store_PlainWrite+9c Null pointer dereference
0x397c8 unpack_tlv_data+64 Null pointer dereference
0x3999c DRM_Hexstringtobyte+1c Null pointer dereference

Affected Devices

We have verified that the vulnerabilities impacted the following device(s):

  • Kirin 990: P40 Pro (ELS)

Please note that other models might have been affected.

Patch

Name Severity CVE Patch
Missing Length Checks in GetOCSPResponse Critical CVE-2021-46813 June 2022
Missing Length and Offset Checks in NOVEL_CHDRM_Copyordecrypt Critical CVE-2021-46813 June 2022
Missing Length Checks in NOVEL_CHDRM_SetDRMCertData Critical CVE-2021-46813 June 2022
Missing Length Check in DRM_Secure_Store_Read High CVE-2021-40062 March 2022
Missing Length Check in getvaluewithtypeandindex High CVE-2021-40056 March 2022
Missing Length Checks in Secure_Store_EncryptWrite and Secure_Store_PlainWrite High CVE-2021-40057 March 2022
Missing Length Checks in NOVEL_CHDRM_SetRegisterResData High CVE-2021-40058 March 2022
Missing / Faulty Length Checks When Calling NOVEL_CHDRMw_MemCompare High CVE-2021-40060 March 2022
Integer Underflow in find_tlv_data High CVE-2021-46813 June 2022
OOB Accesses in getvaluewithtypeandindex Med CVE-2022-39003 September 2022
Unchecked Malloc Return Values Low N/A Fixed
Missing Length Check in pack_tlv_data Low N/A Fixed
Missing Length Checks After Calling unpack_tlv_data Low N/A Fixed
Stack / Heap / BSS Pointer Leaks in DRM_AES_Encrypt_xxx Low N/A Fixed
Integer Underflow in unpack_tlv_data Low N/A Fixed

Timeline

  • Dec. 02, 2021 - A vulnerability report is sent to Huawei PSIRT.
  • Jan. 17, 2022 - Huawei PSIRT acknowledges the vulnerability report.
  • Sep. 01, 2022 - Huawei PSIRT states that these issues were fixed in the March 2022, June 2022 and September 2022 updates.
  • From Nov. 30, 2022 to Jul, 19 2023 - We exchange regularly about the release of our advisories.