- CVE-2021-40052 Wrong memcpy_s Destination Sizes in CencDecrypt
- HWPSIRT-2021-27669 Lack of Locking when Accessing Global Variables
- HWPSIRT-2021-11381 Opening Sessions Before Initialization
- HWPSIRT-2021-11309 Session IDs Are Pointers
We identified 3 vulnerabilities affecting Huawei's CHINADRM_COMMON_TA that can lead to compromise of the trusted application executing as S-EL0:
- lack of locking when accessing global variables, leading to double-free and use-after-free issues
- opening sessions is possible prior to initialization, leading to null pointer dereferences
- session IDs are pointers, leading to information leak of heap pointers
We also identified an additional bug affecting Huawei's CHINADRM_COMMON_TA trusted application:
- wrong
memcpy_s
destination sizes inCencDecrypt
, leading to potential ION buffer overflow
Lack of Locking when Accessing Global Variables¶
As visible in the logs when the trustlet is loaded, it is multi sessions. As a result, proper care must be taken when accessing or modifying global variables. Unfortunately, this trustlet is not doing any kind of locking, so many race conditions issues arise.
[GTask] TA name: CHINADRM_COMMON_TA, UUID: 95b9ad1e, ELF: 361867, stack: 300000, heap: 1179648, multi session: True, keepalive: False, singleInstance: True
As evidenced in the snippets of code below, no lock is taken when accessing the sessions list.
int g_sessions_count; /* in the BSS */
list_head g_sessions_list; /* in the BSS */
int CDRMC_FindSession(container_t *container, session_t **session_p) {
// ...
if (g_sessions_count && container) {
if (list_contains(&container->list, &g_sessions_list)) {
*session_p = container->session;
return 0;
} else { /* ... */ }
} else {
// ...
return -1;
}
// ...
}
As a result, it possible to have a race condition where two cores are in the same instructions window in CDRMC_CloseSession
, resulting in a session being freed twice.
Note: It is also possible to trigger use-after-frees as well, by having one core make use of the session (reading or writing to it), while another core is freeing it.
int CDRMC_CloseSession(container_t *container) {
// ...
if (CDRMC_FindSession(container, &session)) { /* ... */ }
/* --- start of the race window --- */
if (container->session) {
LicenseExtractInfoCleanup(&container->session->inner);
CDRMR_SecureMemory_Free(container->session);
container->session = NULL;
}
/* --- end of the race window --- */
list_del(&container->list);
CDRMR_SecureMemory_Free(container);
--g_sessions_count;
// ...
}
A proof of concept triggering this double-free results in the following crash:
[HM] [ERROR][228]unmap errno = 22
[HM] ERROR: free: __munmap return code= -1
[HM] ERROR: free: double free
[HM] Dump SPI notification entries:
[HM] ----------
[HM] Stats: SHADOW.tx=0 SHADOW.rx=0 WAKEUP.tx=467 WAKEUP.rx=467
[HM] Stats: SET_AFFINITY.tx=0 SET_AFFINITY.rx=0
[HM] Dump current threads:
[HM] ---------
[HM] CPU0: name=/teesmcmgr.elf
[HM] ctx_map[ta=0/0 ca=0/0 target=0/0 exit=0/0]
...
This lack of locking is common to all global variables. For example, it could also be exploited via the g_cipherHandle
, g_cencCipherHandle
, the various certificate caches, etc. instead of the sessions list.
Opening Sessions Before Initialization¶
The global sessions list is initialized in the CDRMC_Initialize
function (command ID #1). If this command is not called explicitly, the list will be left in an uninitialized state.
int g_sessions_count; /* in the BSS */
list_head g_sessions_list; /* in the BSS */
int CDRMC_Initialize() {
// ...
INIT_LIST_HEAD(&g_sessions_list);
g_sessions_count = 0;
// ...
}
The function CDRMC_OpenSession
, as its name suggests, can be used to open a session. It first calls CDRMC_FindSession
to make sure the value stored in container_p
is not already a valid session. If the session is not found or if the list is empty (i.e. g_sessions_count == 0
), this function will return -1. Since the error path is taken only when CDRMC_FindSession
returns 0, CDRMC_OpenSession
will continue executing even if the list is uninitialized. As a result, the next
pointer of the newly allocated session container will be NULL and g_sessions_count
will be incremented. This can later cause a null pointer dereference.
int CDRMC_OpenSession(container_t **container_p) {
// ...
if (!CDRMC_FindSession(*container_p, &session)) {
/* ...error... */
}
session = CDRMR_SecureMemory_Malloc(0x2CA8);
container = CDRMR_SecureMemory_Malloc(0xC);
memset_s(session, 0x2CA8, 0, 0x2CA8);
memset_s(container, 0xC, 0, 0xC);
// ...
session->container = container;
container->session = session;
list_add(&container->list, &g_sessions_list);
++g_sessions_count;
*container_p = container;
// ...
}
For example, it is possible to trigger the null pointer dereference using the CDRMC_FindSession
call in the CDRMS_SetPolicy
function (command ID #0x29).
int CDRMS_SetPolicy(uint32_t paramTypes, TEE_Param params[4]) {
// ...
container_t *container = params[3].memref.buffer;
CDRMC_FindSession(container, &session);
// ...
}
A proof of concept triggering this null pointer dereference results in the following crash:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1e00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x0, fault_code: 0x92000006
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[CHINADRM_COMMON] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7849 prefer-ca=7849
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <CDRMC_FindSession+0x3c/0xdc>
[HM] <CDRMS_SetPolicy>+0xd0/0x1fc
[HM] <CDRMS_SetPolicy>+0xd0/0x1fc
[HM] <TA_InvokeCommandEntryPoint>+0x31c/0x1358
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
Session IDs Are Pointers¶
The session IDs, used by the CA to refer to a session, are pointers to heap-allocated instances of a structure. This structure is allocated in CDRMC_OpenSession
, and the pointer is returned to the CA in the first value of the first TEE_Param
.
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4])
{
switch (commandID) {
case 0x20u:
// ...checking of the paramTypes...
container_t *container = NULL;
retval = CDRMC_OpenSession(&container);
params->value.a = container;
params->value.b = retval;
break;
// ...
}
}
When running the first proof of concept code, we can indeed observe the pointers returned to the CA:
Session = 1014db0 (retval = 0)
This information leak can come in handy, for example when exploiting the UAF on the sessions list.
Wrong memcpy_s
Destination Sizes in CencDecrypt
¶
The CencDecrypt
function contains multiple memcpy_s
calls. Some of them have an incorrect destination size argument. For example, in the incorrect case showed in the code snippet above, the destination size should be *decrypt->outbuf_len_p - offset
to account for the offset
at which the value will be copied into decrypt->outbuf
.
int CencDecrypt(asym_decrypt_t *decrypt, int algorithm) {
// CORRECT memcpy_s arguments
if (memcpy_s(
&decrypt->outbuf[offset],
*decrypt->outbuf_len_p - offset,
&decrypt->inbuf[offset],
params_->subSamples[idx].sample_dword_0)) {
tee_print(0, "%s %d:copy clear Header data failed\n ", "[error]", 0x50B);
// ...
}
// ...
// INCORRECT memcpy_s arguments
if (memcpy_s(
&decrypt->outbuf[offset],
*decrypt->outbuf_len_p, // <-- the destination size is incorrect
&decrypt->inbuf[offset],
drmInfo_dword_8_times_0x10)) {
tee_print(0, "%s %d:copy payload pattern clear data failed\n ", "[error]", 0x40A);
// ...
}
// ...
}
We did not attempt to trigger this bug, but it could lead to a buffer overflow in a mapped ION buffer.
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 |
---|---|---|---|
Lack of Locking when Accessing Global Variables | Low | N/A | Fixed |
Opening Sessions Before Initialization | Low | N/A | Fixed |
Session IDs Are Pointers | Low | N/A | Fixed |
Wrong memcpy_s Destination Sizes in CencDecrypt |
High | CVE-2021-40052 | August 2022 |
Timeline¶
- Dec. 09, 2021 - A vulnerability report is sent to Huawei PSIRT.
- Jan. 12, 2022 - Huawei PSIRT acknowledges the vulnerability report.
- Aug. 01, 2022 - Huawei PSIRT states that this issue was fixed in the August 2022 update.
- From Nov. 30, 2022 to Jul, 19 2023 - We exchange regularly about the release of our advisories.
Copyright © Impalabs 2021-2023