- CVE-2021-46813 Missing Length Checks in GetOCSPResponse
- CVE-2021-46813 Missing Length and Offset Checks in NOVEL_CHDRM_Copyordecrypt
- CVE-2021-46813 Missing Length Checks in NOVEL_CHDRM_SetDRMCertData
- CVE-2021-40062 Missing Length Check in DRM_Secure_Store_Read
- CVE-2021-40056 Missing Length Check in getvaluewithtypeandindex
- CVE-2021-40057 Missing Length Checks in Secure_Store_EncryptWrite and Secure_Store_PlainWrite
- CVE-2021-40058 Missing Length Checks in NOVEL_CHDRM_SetRegisterResData
- CVE-2021-40060 Missing / Faulty Length Checks When Calling NOVEL_CHDRMw_MemCompare
- CVE-2021-46813 Integer Underflow in find_tlv_data
- CVE-2022-39003 OOB Accesses in getvaluewithtypeandindex
- HWPSIRT-2022-77114 Unchecked Malloc Return Values
- HWPSIRT-2021-84851 Missing Length Check in pack_tlv_data
- HWPSIRT-2021-40855 Missing Length Checks After Calling unpack_tlv_data
- HWPSIRT-2021-36582 Stack / Heap / BSS Pointer Leaks in DRM_AES_Encrypt_xxx
- HWPSIRT-2021-78954 Integer Underflow in unpack_tlv_data
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_dataresulting in 39 buffer overflows of the TEE_Param output buffer
- Missing length check in DRM_Secure_Store_Readresulting in 25 buffer overflows (23 stack-allocated, 2 heap-allocated)
- Missing length check in getvaluewithtypeandindexresulting in 13 stack buffer overflows
- Missing length checks in GetOCSPResponseresulting in 14 stack buffer overflows
- Missing length checks in Secure_Store_EncryptWriteandSecure_Store_PlainWriteresulting in 12 stack buffer overflows
- Missing length and offset checks in NOVEL_CHDRM_Copyordecryptresulting in:- 1 stack buffer overflow
- 6 ION buffer OOB writes
- 3 ION buffer OOB reads
 
- Missing length checks in NOVEL_CHDRM_SetDRMCertDataresulting in 2 stack buffer overflows
- Missing length checks in NOVEL_CHDRM_SetRegisterResDataresulting in:- 1 stack buffer overflow
- 1 BSS buffer overflow
 
- Missing length checks after calling unpack_tlv_dataresulting in 6 heap buffer overflows
- Stack / heap / bss pointer leaks in DRM_AES_Encrypt_xxx
- Integer underflow in unpack_tlv_dataresulting in OOB read/buffer overread
- Integer underflow in find_tlv_dataresulting 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, ¶ms[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,
            ¶ms[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_tagwhich increments the data pointer and retrieves the length (unbounded) of the current tag
- a call to NOVEL_CHDRMw_Memcpythat copies the tag data from the input buffer into thersp_status_poutput 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.dataas the destination;
- buffer_tlvas the source;
- buffer_tlv_sizeas 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_Paraminput buffer
- it calls DRM_AES_Encrypt_cbcwith 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_cbcoverflows a stack-allocated buffer
- the second call to DRM_AES_Encrypt_cbcoverflows 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_Paraminput buffer usingunpack_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:
- inbufhas a size of 0x100;
- remaining_sizeis 0x10;
- curr_offsetis 0xf0;
- the extracted data_lengthfrom the input buffer is0x1000.
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], becausekey_length(16-bit value) is user-controlled, the access can be out of bounds by a maximum of 0xffff bytes
- At [2], becauserule_offset(8-bit value) is user-controlled, the access can be out of bounds by a maximum of 0xff bytes
- At [3]and[4], becauserule_offset(8-bit value) andrule_count(8-bit value) are user-controlled, the access can be out of bounds by a maximum of 0xffff bytes
- At [5], becausesome_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_EncryptWriteandSecure_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.
Copyright © Impalabs 2021-2023