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

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,
            &params[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.