- HWPSIRT-2022-46490 Limited Arbitrary Function Call in TA_InvokeCommandEntryPoint
- HWPSIRT-2022-09056 Integer Overflows in VSIM_CmdSaveAllMaincard
- HWPSIRT-2022-21738 Stack Buffer Overflows in VsimSaveOpiMainParam, VsimSaveOpiSlaveParam and VsimModemSendDhVsimData
- HWPSIRT-2022-87812 Param Buffer Overread in GenerateMasterMsg
- HWPSIRT-2022-67695 Param Buffer Overflow in VsimEncryptoString
Limited Arbitrary Function Call in TA_InvokeCommandEntryPoint
¶
In TA_InvokeCommandEntryPoint
, when a request is received from the Normal World, the function will use the command ID to retrieve the corresponding handler. However, the command ID is not bounded. If an attacker is able to leak the address of g_cmds
, they will be able to retrieve arbitrary function pointers from the trustlet application address space and therefore execute arbitrary code.
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
handler_fptr = g_cmds[commandID];
if (handler_fptr) {
ret = handler_fptr(sessionContext, paramTypes, params);
// [...]
}
// [...]
}
To get complete control over which function pointers can be used, they can be placed, for example, in the TEE_Param
buffers which are usually mapped at addresses starting from 0x70003000
.
A proof of concept triggering this bug results in a crash at 0xa3cf69c
:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1d00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0xa3cf69c, fault_code: 0x92000006
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[vsim_sw] 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=8463 prefer-ca=8463
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TA_InvokeCommandEntryPoint+0x100/0x1d8>
[HM] <TA_InvokeCommandEntryPoint>+0xf4/0x1d8
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
Stack Buffer Overflows in VsimSaveOpiMainParam
, VsimSaveOpiSlaveParam
and VsimModemSendDhVsimData
¶
The command #0x32 is handled by the function VSIM_CmdSaveOptimisedMainParam
, which is a wrapper around VSIM_CmdSaveOptimisedMainParam
called with the first two TEE_Param
input buffers and their respective sizes.
uint32_t VSIM_CmdSaveOptimisedMainParam(
void *sessionContext,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
params[3].value.a = VsimSaveOpiMainParam(
params[0].memref.buffer,
params[0].memref.size,
params[1].memref.buffer,
params[1].memref.size);
return 0;
// [...]
}
VsimSaveOpiMainParam
starts by processing the global buffer g_main_card
which can be set by the user by sending the command #0x6 VSIM_CmdSaveAllMaincard
.
unsigned int VsimSaveOpiMainParam(
void *ibuf0_addr,
uint32_t ibuf0_size,
void *ibuf1_addr,
uint32_t ibuf1_size)
{
char* card_elems_array[22];
uint32_t card_elems_nb = 0;
// [...]
main_card_data = vsim_malloc("vsim_card.c", 0xA1D, 0x6128, 0);
// [...]
vsim_memmove_s(main_card_data, 0x6128, &g_main_card, 0x6128);
uint32_t card_elems_nb = vsim_strsplinum(main_card_data + 0x5818, "|");
vsim_split(card_elems_array, main_card_data + 0x5818, "|");
// [...]
VsimSaveOpiMainParam
calls vsim_split
to split the main card data buffer every |
character and stores the resulting chunks into the string array card_elems_array
.
void vsim_split(char **bufs_array, char *inbuf, char *separator)
{
char* buf = NULL;
while (buf = vsim_strtok(inbuf, separator))
*bufs_array++ = buf;
}
The issue is that vsim_split
never checks the size of the bufs_array
parameter and, as long as separator
is encountered in the inbuf
string, a new entry will be added to bufs_array
. Since card_elems_array
is of size 22, if we specify an entry into g_main_card
that has more than 22 |
characters, we will start overflowing the stack frame of VsimSaveOpiMainParam
. This could lead to code execution by replacing, for example, the stack frame pointer of the calling function as we did in the Huawei_TSS_TA
advisory.
A second occurrence of this vulnerability can be found in the VsimSaveOpiSlaveParam
function:
unsigned int VsimSaveOpiSlaveParam(
void *ibuf0_addr,
uint32_t ibuf0_size,
void *ibuf1_addr,
uint32_t ibuf1_size)
{
char* card_elems_array[22];
// ...
memset(card_elems_array, 0, sizeof(card_elems_array));
// ...
card_0x6128 = vsim_malloc("vsim_card.c", 0xA1D, 0x6128u, v9);
// ...
vsim_memmove_s(card_0x6128, 0x6128u, &g_slave_card, 0x6128u);
card_elems_nb = vsim_strsplinum(card_0x6128 + 0x5818, "|");
vsim_split(card_elems_array, card_0x6128 + 0x5818, "|");
// ...
}
This one requires the g_slave_card
to be set, which can also be done by the user.
A third occurrence of this vulnerability can be found in the VsimModemSendDhVsimData
function:
unsigned int VsimModemSendDhVsimData(int card_type, int a2) {
// ...
char *card_array[30];
// ...
memset(card_array, 0, sizeof(card_array));
// ...
card_data = vsim_malloc("vsim_modem_chicago.c", 0x1FD, 0x6128, 0);
ReadCard(card_data, card_type);
// ...
card_array_str = card_data + 0x5018;
vsim_strncat(card_array_str, 0x800, "|", 1);
card_unkn_str = card_data + 0x5818;
vsim_strncat(card_array_str, 0x800, card_unkn_str, strlen(card_unkn_str));
// ...
card_array_len = vsim_strsplinum(card_array_str, "|");
vsim_split(card_array, card_array_str, "|");
// ...
}
This one requires either the g_master_card
or the g_slave_card
to be set by the user.
A proof of concept triggering this bug results in a crash at 0x6778000
:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1d00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x6778000, fault_code: 0x92000047
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[vsim_sw] 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=9110 prefer-ca=9110
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <vsim_split+0x40/0x80>
[HM] <vsim_split>+0x4c/0x80
[HM] <VsimSaveOpiMainParam>+0x208/0x400
[HM] invalid fp. backtrace abort
[HM] Dump task states END
[HM]
[HM] [ERROR][2519]process 1d00000022 (tid: 34) instruction fault:
[HM] [ERROR][2520]Bad addr: 0xffffff8797e01c94
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[vsim_sw] tid=34 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=49 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=0 prefer-ca=0
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <pthread_join+0x28/0x14c>
[HM] <?>+0x0/0x0
[HM] invalid fp. backtrace abort
[HM] Dump task states END
[HM]
Param Buffer Overread in GenerateMasterMsg
¶
There is a TEE_Param
buffer overread in the function GenerateMasterMsg
called by the command VSIM_CmdSaveAllMaincard
(ID #0x6).
uint32_t VSIM_CmdSaveAllMaincard(
void *sessionContext,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
void* ibuf1_addr = params[1].memref.buffer;
uint32_t ibuf1_size = params[1].memref.size;
void* ibuf2_addr = params[2].memref.buffer;
uint32_t ibuf2_size = params[2].memref.size;
// [...]
if (ibuf1_size <= 0x591B || ibuf2_size <= 0x591B)
goto ERROR;
// [...]
GenerateAllMasterMsg(
ibuf1_addr, ibuf1_size,
ibuf2_addr, ibuf2_size,
hashSimLen_2, smid, hash_buf);
// [...]
}
The handler for this command starts by a call to GenerateAllMasterMsg
with the second and third TEE_Param
input buffer as arguments among other user controlled values. The handler also makes sure that the sizes of the input buffers is greater than 0x591C.
uint32_t GenerateAllMasterMsg(
const void *mainEm,
uint32_t mainEmLen,
const void *mainSm,
uint32_t mainSmLen,
uint32_t hashSimLen,
const char *smid,
char *hash_buf)
{
// [...]
GenerateMasterMsg(mainEm, mainEmLen, 1, emMsg, &emMsgLen);
// [...]
}
GenerateAllMasterMsg
then passes mainEm
, which is params[1].memref.buffer
, to GenerateMasterMsg
.
uint32_t GenerateMasterMsg(
char *mainEm,
uint32_t mainEmLen,
uint32_t msg_hash_count,
char *msg_hashes_out,
uint32_t *msg_hashes_out_len)
{
while ( 1 )
{
// [...]
diffLen = *(uint32_t)(mainEm + 0x5918);
diff = mainEm + 0x591C;
// [...]
mainEm = diff + 0x410 * diffLen;
}
}
Finally, GenerateMasterMsg
will iterate over mainEM
and retrieve different values such as diffLen
located at mainEm + 0x5918
. However since VSIM_CmdSaveAllMaincard
only checks that the size is greater than 0x591C
and because mainEm
is updated using the user-controlled value diffLen
, it is possible to make mainEm
point out of bounds of the TEE_Param
input buffer.
A proof of concept triggering this bug results in a crash at 0x70015234
, an address located after the param input buffer:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1d00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x70015234, fault_code: 0x92000007
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[vsim_sw] 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=9152 prefer-ca=9152
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <GenerateMasterMsg.constprop.1+0x1d4/0x700>
[HM] <?>+0x0/0x0
[HM] <GenerateAllMasterMsg>+0x15c/0x2bc
[HM] <VSIM_CmdSaveAllMaincard>+0x27c/0xcc0
[HM] <TA_InvokeCommandEntryPoint>+0x11c/0x1d8
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
Integer Overflows in VSIM_CmdSaveAllMaincard
¶
There are integer overflows in the VSIM_CmdSaveAllMaincard
command handler (ID #6).
This function first calculates a digest computed over parts of the params[1]
and params[2]
input buffers (among other things). It then checks if the signature of this digest, contained in the param[0]
input buffer, is valid.
If the signature is correct, it parses the raw "card data" contained in params[1]
and saves it. It then parses the raw "hash card data" contained in params[2]
and also saves it. The vulnerability is in the calculation of the size of the temporary buffer that will be allocated to hold the "hash card data".
unsigned int VSIM_CmdSaveAllMaincard(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
// ...
hashSimLen = *(uint32_t *)(params[0].memref.buffer + 0x44);
allDiffLen = *(uint32_t *)(params[0].memref.buffer + 0x48);
smid = ibuf0_addr + 0x4C;
hash_buf = vsim_malloc("vsim_card.c", 0x6E9, 0x100, 0);
// ...
GenerateAllMasterMsg(params[1].memref.buffer,
params[1].memref.size,
params[2].memref.buffer,
params[2].memref.size,
hashSimLen, smid, hash_buf);
// ...
VsimVerifyServerSign(6, hash_buf, params[0].memref.buffer);
vsim_free(hash_buf_1);
// ...
if ( ibuf1_size_1 != 1 ) { /* ... */ }
if ( ibuf2_size_1 != 1 ) {
hash_card_len = 0x5918 * hashSimLen + 0x410 * allDiffLen + 0x48;
hash_card = vsim_malloc("vsim_card.c", 0x750, hash_card_len, 0);
// ...
vsim_memset(hash_card, hash_card_len, 0, hash_card_len);
vsim_memmove_s(hash_card_, 0x41, smid, strlen(smid));
// ...
}
// ...
}
This size of this buffer is 0x5918 * hashSimLen + 0x410 * allDiffLen + 0x48
, where each multiplication can result in an integer overflow as the hashSimLen
and allDiffLen
values are user-controlled. It is easier to trigger the overflow with allDiffLen
rather than hashSimLen
because the latter is used in the call to GenerateAllMasterMsg
. For example, by specifying a value of 0 for hashSimLen
and 0xfc0fc0fc for allDiffLen
, we get 0x5918 * 0 + 0x410 * 0xfc0fc0fc + 0x48 = 0x8
. This way hash_card
will be a heap-allocated buffer of size 8.
Since the vsim_memmove_s
call that follows the allocation uses a fixed destination size of 0x41 bytes, and has a fully user-controlled source, we have a fully controlled heap buffer overflow. Traditional heap exploitation techniques can then be used to gain arbitrary memory read/write.
A proof of concept triggering this bug results in a corruption of the footer detected when TEE_Free
is called later:
[vsim_sw-1] [Trace] VsimSaveHashCard: save hash card, smLen=22812
[vsim_sw-1] [Trace] vsim_memmove_s: dstLen=65
[vsim_sw-1] [Trace] vsim_memmove_s: srcLen=64
[vsim_sw-1] [Trace] VsimSaveFile: save file begin.
[vsim_sw-1] [Trace] VsimStorageMode: use cache, g_vsimStorageMode=2
[vsim_sw-1] [Trace] vsim_rpmb_save_file: rpmb save file begin.
[vsim_sw-1] [Trace] vsim_rpmb_save_file: rpmb save file success.
[vsim_sw-1] [Trace] VsimCacheHashNum: VsimCacheHashNum start
[vsim_sw-1] [Trace] VsimCacheHashNum: hashNum->hashSimLen=0
[vsim_sw-1] [Trace] VsimCacheHashNum: hashNum->allDiffLen=-66076420
[vsim_sw-1] [Trace] vsim_memmove_s: dstLen=8
[vsim_sw-1] [Trace] vsim_memmove_s: srcLen=8
[vsim_sw-1] [Trace] VsimCacheHashNum: VsimCacheHashNum end
[vsim_sw-1] [Trace] VsimSaveHashCard: save hash card success!
[HM] ERROR: free: corrupted footer
[vsim_sw-1] [Trace] VsimSaveMainSimData: save maincard ret=0x0
Param Buffer Overflow in VsimEncryptoString
¶
The VsimEncryptoString
function, which is called by the VSIM_CmdCrypto
command handler (ID #0x15), is used to encrypt the content of the param input buffer. The ciphertext is then placed into the param output buffer. This is where a buffer overflow is possible.
unsigned int VSIM_CmdCrypto(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
// ...
VsimEncryptoString(
5,
params[1].memref.buffer,
params[1].memref.size,
params[2].memref.buffer,
¶ms[2].memref.size);
// ...
}
unsigned int VsimEncryptoString(
uint32_t type,
uint8_t *ibuf1_addr,
uint32_t ibuf1_size,
uint8_t *obuf2_addr,
uint32_t *obuf2_size_p)
{
// ...
len = 0;
buf = vsim_malloc("vsim_crypto.c", 0x2C, ibuf1_size + 0x20, 0);
// ...
VsimAesCbcPkcs5paddingEncryptIv(output, 0x20, ibuf1_addr, ibuf1_size, buf, &len);
// ...
vsim_memmove_s(obuf2_addr, len, buf, len);
*obuf2_size_p = len;
vsim_free(buf);
return 0;
}
A temporary buffer of a size big enough to hold the IV and ciphertext is allocated on the heap. The VsimAesCbcPkcs5paddingEncryptIv
function uses the param input buffer as input, and the temporary buffer as output. The temporary buffer is then copied into the param output buffer using vsim_memmove_s
, but the destination size is the same as the source size, resulting in a buffer overflow if the param output buffer is smaller than the input buffer.
A proof of concept triggering this bug results in the following crash at an address located after the param output buffer:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1d00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x7002301f, fault_code: 0x92000047
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[vsim_sw] 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=7060 prefer-ca=7060
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <memset+0x14/0xe0>
[HM] <memset_s>+0x28/0x38
[HM] invalid fp. backtrace abort
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=44 exit_status=130
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 |
---|---|---|---|
Limited Arbitrary Function Call in TA_InvokeCommandEntryPoint |
Critical | N/A (*) | Fixed |
Integer Overflows in VSIM_CmdSaveAllMaincard |
Critical | N/A (*) | Fixed |
Stack Buffer Overflows in VsimSaveOpiMainParam , VsimSaveOpiSlaveParam and VsimModemSendDhVsimData |
Low | N/A | Fixed |
Param Buffer Overread in GenerateMasterMsg |
Low | N/A | Fixed |
Param Buffer Overflow in VsimEncryptoString |
Low | N/A | Fixed |
(*) Huawei's statement about the medium, high, and critical severity APP vulnerabilities:
They are resolved through the AppGallery upgrade. Generally, CVE numbers are not assigned for this type [of] vulnerabilities.
Timeline¶
- Jan. 06, 2022 - A vulnerability report is sent to Huawei PSIRT.
- Mar. 22, 2022 - Huawei PSIRT acknowledges the vulnerability report.
- From Nov. 30, 2022 to Jul, 19 2023 - We exchange regularly about the release of our advisories.
Copyright © Impalabs 2021-2023