- 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_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
andSecure_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, ¶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_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 thersp_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 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 byunpack_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 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_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.
Copyright © Impalabs 2021-2023