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

Param Buffer Overread in TA_GetPayload

There is a buffer overread in TA_GetPayload called by the command CMD_TSS_GET_ROOT_STATUS (ID #0x40018).

TEE_Result TA_InvokeCommandEntryPoint(
        void *sessionContext,
        uint32_t commandID,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    // [...]
    switch (commandID) {
        // [...]
        case 0x40018:
            uint32_t payload_p = 0;
            uint32_t payload_len = 0;
            // Retrieves the header from the TEE_Param input buffer
            TA_GetPayload(params[1].memref.buffer, &payload_p, &payload_len);
            // [...]
            break;
    }
    // [...]
}

The function reads the length result_len from the second TEE_Param input buffer inbuf at offset 0xA22. This user-controlled value is then used to allocate the result buffer into which result_len bytes are copied from the input buffer. If result_len is larger than the size of inbuf, it will result in an arbitrary buffer overread.

int TA_GetPayload(uint8_t *inbuf, char **payload_p, uint32_t *result_p) {
    // ...
    result_len = *(uint32_t *)(inbuf + 0xA22);
    result = tss_mallocObject(result_len);
    if (result_len > 2)
        memcpy_s(result, result_len - 2, inbuf + 0x50F, result_len - 2);
    // ...
}

The proof of concept code demonstrates that triggering the input buffer overread results in a bad read memory access at address 0x70007000:

[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 2300000028 (tid: 40) data abort: 
[HM] [ERROR][2498]Bad memory access on address: 0x70007000, fault_code: 0x92000007
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[Huawei_TSS_TA] 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=7216 prefer-ca=7216
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] sp > fpDump task states END
[HM] 
[HM] [TRACE][1212]pid=44 exit_status=130

Stack Buffer Overflow in TA_GetPayload

There is a buffer overflow in the function TA_GetPayload called by the command CMD_TSS_GET_ROOT_STATUS (ID #0x40018).

TEE_Result TA_InvokeCommandEntryPoint(
        void *sessionContext,
        uint32_t commandID,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    // [...]
    switch (commandID) {
        // [...]
        case 0x40018:
            uint32_t payload_p = 0;
            uint32_t payload_len = 0;
            // Retrieves the header from the TEE_Param input buffer
            TA_GetPayload(params[1].memref.buffer, &payload_p, &payload_len);
            // [...]
            break;
    }
    // [...]
}

This function generates a JSON object using information parsed from a TEE_Param input buffer. One of the user-provided values is digests_count. TA_GetPayload then uses this value to fill the array digests_array, of size 10, with pointers to the digests embedded inside the TEE_Param input buffer inbuf:

int TA_GetPayload(uint8_t *inbuf, char **payload_p, uint32_t *result_p) {
    // ...
    uint8_t *digests_array[10];
    // ...
    digests_count = *(uint32_t *)(inbuf + 0xC);
    for (i = 0; i < digests_count; ++i)
        digests_array[i] = &inbuf[0x32 * i + 0x31A];
    apkCertificateDigestSha256_str = json_format_array("apkCertificateDigestSha256",
                                                       digests_array, digests_count);
    // ...
}

However, since we can control digests_count, it is possible to specify a number of digests such that we start overflowing values on the stack, outside of the digests_array, and replace them with pointers to user-controlled data.

digests_array[i] = input + 0x31A + 0x32 * i;

By triggering a stack buffer overflow big enough to overwrite the saved registers {R4-R11,LR}, we end up with pointers to memory inside the TEE_Param input buffer being put in these registers when exiting TA_GetPayload.

A first proof of concept demonstrates that by overwriting the LR register we get an instruction abort on an address inside the TEE_Param input buffer:

[HM] ESR_EL1: 8200000f, ELR_EL1: 70004928, FAR is not valid
[HM] Huawei_TSS_TA vm fault prefetch abort: 70004928
[HM] fault: 8200000f tcb cref 1e00000028
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] [ERROR][2496]process 1e00000028 (tid: 40) data abort: 
[HM] [ERROR][2498]Bad memory access on address: 0x70004928, fault_code: 0x8200000f
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[Huawei_TSS_TA] 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=7305 prefer-ca=7305
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] Dump task states END
[HM] 
[HM] [TRACE][1212]pid=44 exit_status=130

A second proof of concept overwrites the saved registers up to R11, the frame pointer. When exiting TA_GetPayload, the frame pointer will point to user-controlled memory, thus any value loaded from the frame is now user-controlled. In it, we make payload point to 0x41414141, resulting in a crash when this pointer is accessed by strlen:

[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: 0x41414141, fault_code: 0x92000006
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[Huawei_TSS_TA] 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=7588 prefer-ca=7588
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <strlen+0x8/0x74>
[HM] <TA_InvokeCommandEntryPoint>+0x1900/0x1e1c
[HM] Dump task states END
[HM] 
[HM] [TRACE][1212]pid=46 exit_status=130

A third proof of concept overwrites the frame pointer and the payload, but makes it point to user-controlled memory instead of 0x41414141. We make this memory look like a legitimate heap-allocated buffer, so that TEE_Free can be called on this pointer. We use the classic "heap unlinking" technique to trigger an arbitrary write to address 0x41414141+8:

[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: 0x41414149, fault_code: 0x92000046
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[Huawei_TSS_TA] 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=7620 prefer-ca=7620
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] invalid fp. backtrace abort
[HM] Dump task states END
[HM] 
[HM] [TRACE][1212]pid=50 exit_status=130

Param Buffer Overread in TA_GetSysintegritySignStr

There is a buffer overread in TA_GetSysintegritySignStr called by the command CMD_TSS_GET_SYSINTEGRITY_JWS (ID #0x40020).

TEE_Result TA_InvokeCommandEntryPoint(
        void *sessionContext,
        uint32_t commandID,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    // [...]
    switch (commandID) {
        // [...]
        case 0x40020:
            sig_str = 0;
            sig_str_len = 0;
            decrpypted_sk_len = 0;
            TA_GetSysintegritySignStr(
                params[1].memref.buffer, &sig_str, &sig_str_len);
            // [...]
            break;
    }
    // [...]
}

The function reads the length result_len from the second TEE_Param input buffer inbuf at offset 0xB00. This user-controlled value is then used to allocate the result buffer into which result_len bytes are copied from the input buffer. If result_len is larger than the size of inbuf, it will result in an arbitrary buffer overread.

uint32_t TA_GetSysintegritySignStr(
        uint8_t *inbuf, uint8_t **sig_str_p, uint32_t *sig_str_len_p)
{
    // [...]
    result_len = *(uint32_t *)(inbuf + 0xB00);
    result = tss_mallocObject(result_len);
    if (result_len > 2)
        memcpy_s(result, result_len - 2, inbuf + 0x5ED, result_len - 2);
    // [...]
}

A proof of concept demonstrates that triggering the input buffer overread results in a bad read memory access at address 0x70007000:

[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 2300000028 (tid: 40) data abort: 
[HM] [ERROR][2498]Bad memory access on address: 0x70007000, fault_code: 0x92000007
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[Huawei_TSS_TA] 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=7593 prefer-ca=7593
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] sp > fpDump task states END
[HM] 
[HM] [TRACE][1212]pid=51 exit_status=130

Param Buffer Overread in TA_DecryptKEK

There is a buffer overread in TA_DecryptKEK called by the command CMD_TSS_GET_SYSINTEGRITY_JWS (ID #0x40020).

TEE_Result TA_InvokeCommandEntryPoint(
        void *sessionContext,
        uint32_t commandID,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    // [...]
    switch (commandID) {
        // [...]
        case 0x40020:
            // Call to TA_GetSysintegritySignStr
            decrypted_sk_len = 0;
            decrypted_sk = tss_mallocObject(0x400);
            TA_DecryptSK(
                params[0].memref.buffer, decrypted_sk, &decrypted_sk_len);
            // [...]
            break;
    }
    // [...]
}

This command passes the first TEE_Param input buffer to the function TA_DecryptSK before passing it to TA_DecryptKEK.

TEE_Result TA_DecryptSK(uint8_t *inbuf, uint8_t *key, int *key_len_p) {
    // [...]
    kek_buf_len = 0;
    kek_buf = tss_mallocObject(0x400);
    TA_DecryptKEK(inbuf, kek_buf, &kek_buf_len);
    // [...]
}

TA_DecryptKEK then decodes a base64-encoded string from the input buffer at offset 0 using the function b64_decode_ex.

TEE_Result TA_DecryptKEK(
        uint8_t *inbuf, uint8_t *outbuf, uint32_t *outbuf_len_p)
{
    // [...]
    uint32_t b64_encoded_len = *(uint32_t *)(inbuf + 0x400);
    uint32_t b64_decoded_len = 0;
    b64_decode_ex(inbuf, b64_encoded_len, &b64_decoded_len);
    // [...]
}

b64_decode_ex takes as parameters:

  • an input buffer containing a base64-encoded string;
  • the size of the encoded string;
  • a pointer to an integer that will contain the size of the decoded string.

In our case, the size of the encoded string b64_encoded_len is user-controlled and is read from the input buffer at offset 0x400. However, the function never checks if b64_encoded_len smaller than the size of the input buffer, resulting in a buffer overread if the user-provided value is large enough.

A proof of concept demonstrates that triggering the input buffer overread results in a bad read memory access at address 0x70007000:

[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 2300000028 (tid: 40) data abort: 
[HM] [ERROR][2498]Bad memory access on address: 0x70007000, fault_code: 0x92000007
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[Huawei_TSS_TA] 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=7638 prefer-ca=7638
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <b64_decode_ex+0x74/0x294>
[HM] <memset_s>+0x28/0x38
[HM] <TA_DecryptKEK>+0x58/0x148
[HM] <TA_DecryptSK>+0x6c/0x2f4
[HM] <TA_InvokeCommandEntryPoint>+0x1cb0/0x1e1c
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM] 
[HM] [TRACE][1212]pid=56 exit_status=130

Heap Buffer Overflows and Stack Buffer Overreads in TA_DecryptSKWithCBC and TA_DecryptSKWithGCM

There is a heap buffer overflow and a stack buffer overread in the function TA_DecryptSKWithCBC.

It is called by the command CMD_TSS_GET_SYSINTEGRITY_JWS (ID #0x40020).

TEE_Result TA_InvokeCommandEntryPoint(
        void *sessionContext,
        uint32_t commandID,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    // [...]
    switch (commandID) {
        // [...]
        case 0x40020:
            // Call to TA_GetSysintegritySignStr
            decrypted_sk_len = 0;
            decrypted_sk = tss_mallocObject(0x400);
            TA_DecryptSK(
                params[0].memref.buffer, decrypted_sk, &decrypted_sk_len);
            // [...]
            break;
    }
    // [...]
}

TA_DecryptSKWithCBC is called by the function TA_DecryptSK.

TEE_Result TA_DecryptSK(uint8_t *inbuf, uint8_t *key, uint32_t *key_len_p) {
    // ...
    aes_cbc_args_t args;
    // ...
    kek_buf_len = 0;
    kek_buf = tss_mallocObject(0x400);
    TA_DecryptKEK(inbuf, kek_buf, &kek_buf_len);
    object = TSS_ImportKey(kek_buf, kek_buf_len);
    // ...
    if (*(uint32_t *)(inbuf + 0xA58) == 0) {
        *key_len_p = *(uint32_t *)(inbuf + 0x880);
        args.dest = key;
        args.dest_len_p = key_len_p;
        // ...fill the args structure...
        memcpy(&args, inbuf + 0x48C, 0x4C4);
        TA_DecryptSKWithCBC(args);
    }
    // ...
}

TA_DecryptSKWithCBC is used to decrypt the secret key using AES-CBC. It is given its arguments via the stack, using a structure we have called aes_cbc_args_t:

  • the source buffer is a copy into the stack of data in the TEE_Param input buffer (user-controlled)
  • the source length is also a copy into the stack of a value in the TEE_Param input buffer (user-controlled)
  • the destination buffer decrypted_sk of size 0x400 is allocated on the heap in TA_DecryptSK's caller
  • the destination length is unspecified (decrypted_sk_len is set to 0 in TA_DecryptSK's caller)
TEE_Result TA_DecryptSKWithCBC(aes_cbc_args_t args) {
    // ...
    TSS_AES_DES_Enc_Dec(
        args.key, args.iv, args.iv_len,
        TEE_ALG_AES_CBC_PKCS5, 1,
        args.src, args.src_len,
        args.dest, args.dest_len_p);
    // ...
}

The source length will also be used as the destination length in the AES-CBC operations. If we specify a source length, and thus a destination length, bigger than 0x400, we will overflow the heap-allocated buffer. If we specify an even bigger source length, we will also overread the stack buffer.

Similarly, taking the path that leads to TA_DecryptSKWithGCM can also result in a heap buffer overflow and a stack buffer overread.

TEE_Result TA_DecryptSK(uint8_t *inbuf, uint8_t *key, uint32_t *key_len_p) {
    // ...
    aes_cbc_args_t args;
    // ...
    kek_buf_len = 0;
    kek_buf = tss_mallocObject(0x400);
    TA_DecryptKEK(inbuf, kek_buf, &kek_buf_len);
    object = TSS_ImportKey(kek_buf, kek_buf_len);
    // ...
    if (*(uint32_t *)(inbuf + 0xA58) == 1) {
        *key_len_p = *(uint32_t *)(inbuf + 0x880);
        args.dest = key;
        args.dest_len_p = key_len_p;
        // ...fill the args structure...
        memcpy(&args, inbuf + 0x48C, 0x5CC);
        TA_DecryptSKWithGCM(args);
    }
    // ...
}

A first proof of concept code can be used to trigger the heap buffer overflow. It uses a source length of 0x480, resulting in a corruption detected when TEE_Free is called later:

[Huawei_TSS_TA-1] hmstss-TA [Trace] TA_DecryptSKWithCBC: start TA_DecryptSKWithCBC
[Huawei_TSS_TA-1] hmstss-TA [Trace] TSS_AES_DES_Enc_Dec: begin to TSS_AES_DES_Enc_Dec
[Huawei_TSS_TA-1] hmstss-TA [Trace] TSS_AES_DES_Enc_Dec: begin to TEE_AllocateOperation
[Huawei_TSS_TA-1] hmstss-TA [Trace] TSS_AES_DES_Enc_Dec: begin to TEE_SetOperationKey
[Huawei_TSS_TA-1] hmstss-TA [Trace] TSS_AES_DES_Enc_Dec: begin to cipher
[Huawei_TSS_TA-1] [error] 458:Cipher dofinal failed, ret=-3
[Huawei_TSS_TA-1] hmstss-TA [Error] TSS_AES_DES_Enc_Dec: TEE_CipherDofinal, fail ret=ffff0005, srclen=480,dst_len=7d000
[Huawei_TSS_TA-1] hmstss-TA [Error] TA_DecryptSKWithCBC: TA  decrypt sk with kek failed: 0xFFFD0019
[Huawei_TSS_TA-1] hmstss-TA [Error] TA_DecryptSK: Decrypt sk with kek failed: ret = fffd0019
[Huawei_TSS_TA-1] hmstss-TA [Trace] TA_DecryptSK: free tmpEncryptKey
[HM] ERROR: free: double free
[Huawei_TSS_TA-1] hmstss-TA [Error] TA_InvokeCommandEntryPoint: TA_GetSysintegritySignStr decrypt sk failed
[HM] ERROR: free: corrupted footer

It is possible to overflow with controlled data (the decrypted secret key), allowing the use of classic heap exploitation techniques, such as "heap unlinking" that results in an arbitrary write.

A second proof of concept code can be used to trigger the stack buffer overread. It uses a source length of 0x8000, resulting in a data abort when the end of the stack is reached:

[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: 0xd04000, fault_code: 0x92000007
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[Huawei_TSS_TA] 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=7954 prefer-ca=7954
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] sp > fpDump task states END
[HM] 
[HM] [TRACE][1212]pid=50 exit_status=130

Heap Buffer Overflow in TA_Gen_Sysintegrity_Jws

There is a heap buffer overflow in the function TA_Gen_Sysintegrity_Jws called by the command CMD_TSS_GET_SYSINTEGRITY_JWS (ID #0x40020).

TEE_Result TA_InvokeCommandEntryPoint(
        void *sessionContext,
        uint32_t commandID,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    // [...]
    switch (commandID) {
        // [...]
        case 0x40020:
            // [...]
            sig_str = 0;
            sig_str_len = 0;
            TA_GetSysintegritySignStr(
                params[1].memref.buffer,
                &sig_str, &sig_str_len);
            // [...]
            decrypted_sk_len = 0;
            decrypted_sk = tss_mallocObject(0x400);
            TA_DecryptSK(
                params[0].memref.buffer,
                decrypted_sk, &decrypted_sk_len);
            // [...]
            outbuf_len = 0;
            outbuf = tss_mallocObject(0x2800);
            TA_Gen_Sysintegrity_Jws(
                sig_str, sig_str_len,
                decrypted_sk, decrypted_sk_len,
                outbuf, &outbuf_len);
            // [...]
    }
    // [...]
}

This command starts by calling TA_GetSysintegritySignStr which generates a string sig_str from information retrieved in the second TEE_Param input buffer. Because some elements of this string are user-controlled (but not all), its length sig_str_len can be arbitrarily large.

int TA_GetSysintegritySignStr(uint8_t *inbuf, char **sig_str_p, size_t *sig_str_len_p) {
    // ...
    alg_str = json_format_key_string("alg", inbuf + 0xB43);
    // ...
    payload_len = /* ... + */ strlen(alg_str);
    payload = tss_mallocObject(payload_len);
    // ...
    strncat_s(payload, payload_len, alg_str, strlen(alg_str));
    // ...
    payload_b64enc = b64url_encode(payload, payload_len);
    // ...
    sig_str_len = /* ... + */ strlen(payload_b64enc);
    sig_str = tss_mallocObject(sig_str_len);
    // ...
    strncat_s(sig_str, sig_str_len, payload_b64enc, strlen(payload_b64enc));
    // ...
    *sig_str_len_p = strlen(sig_str);
    *sig_str_p = sig_str;
    // ...
}

This string and its length are then passed to TA_Gen_Sysintegrity_Jws (in the parameters sig and sig_len respectively). sig is then copied into outbuf, a heap-allocated buffer of size 0x2800. Since the size of the sig buffer is arbitrary, and because *outbuf_len_p is computed using sig_len, it is possible to overflow outbuf by specifying a sig_len that will result in a *outbuf_len_p larger than 0x2800 bytes.

int TA_Gen_Sysintegrity_Jws(
        uint8_t *sig,
        uint32_t sig_len,
        uint8_t *sk,
        uint32_t sk_len,
        uint8_t *outbuf,
        uint32_t *outbuf_len_p)
{
    // ...
    mac = tss_mallocObject(0x80);
    CmdHMAC(sk, sk_len, TEE_ALG_HMAC_SHA256, sig, sig_len, mac, &mac_len);
    tmpbuf = b64url_encode(mac, mac_len);
    // ...
    *outbuf_len_p = sig_len + 2 + strlen(tmp_buf);
    strncpy_s(outbuf, *outbuf_len_p, sig, sig_len);
    // ...
}

A proof of concept code triggers this heap buffer overflow. In the code, a size of around 0xA000 is used, resulting in a crash when reaching the heap end address. Nevertheless, it is possible to use smaller sizes, allowing the use of classic heap exploitation techniques, such as the "heap unlinking" that results in an arbitrary write.

[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 2200000028 (tid: 40) data abort: 
[HM] [ERROR][2498]Bad memory access on address: 0xf79000, fault_code: 0x92000047
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[Huawei_TSS_TA] 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=7841 prefer-ca=7841
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <strncpy_s>+0xf4/0x104
[HM] Dump task states END
[HM] 
[HM] [TRACE][1212]pid=75 exit_status=130

Param Buffer Overread in hkdf_expand

There is a buffer overread in the function hkdf_expand called by the command CMD_TSS_SYMMETRIC_CRYPTO (ID #0x40004).

TEE_Result TA_InvokeCommandEntryPoint(
        void *sessionContext,
        uint32_t commandID,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    // [...]
    switch (commandID) {
        // [...]
        case 0x40020:
            // [...]
            ibuf0_addr = params[0].memref.buffer;
            outbuf_len = params[1].memref.size + 0x20;
            outbuf = tss_mallocObject(outbuf_len);
            CmdEncryptDecryptData(
                *(uint32_t *)(ibuf0_addr + 0x00), /* alg */
                *(uint32_t *)(ibuf0_addr + 0x04), /* mode */
                *(uint32_t *)(ibuf0_addr + 0x08), /* okm_len */
                ibuf0_addr + 0x30,                /* key_path */
                *(uint32_t *)(ibuf0_addr + 0x10), /* key_path_len */
                ibuf0_addr + 0x62,                /* hmac_key */
                *(uint32_t *)(ibuf0_addr + 0x18), /* hmac_key_len */
                ibuf0_addr + 0x122,               /* info */
                *(uint32_t *)(ibuf0_addr + 0x20), /* info_len */
                ibuf0_addr + 0x1E2,               /* iv */
                *(uint32_t *)(ibuf0_addr + 0x28), /* iv_len */
                params[1].memref.buffer,          /* inbuf */
                params[1].memref.size,            /* inbuf_len */
                outbuf,                           /* outbuf */
                &outbuf_len);                     /* outbuf_len */
            // [...]
            break;
    }
    // [...]
}
int CmdEncryptDecryptData(...) {
    // ...
    TSS_DeriveWorkKey(rootKeyPath, hmac_key, hmac_key_len, info, info_len, okm, okm_len);
    // ...
}
int TSS_DeriveWorkKey(...) {
    // ...
    hkdf(hmac_key, hmac_key_len, rootKey, rootKey_len, info, info_len, okm, okm_len);
    // ...
}
int hkdf(...) {
    // ...
    hkdf_expand(prk, prk_len, info, info_len, okm, okm_len);
    // ...
}

Most of the lengths, okm_len, key_path_len, hmac_key_len, info_len, iv_len, are extracted from the TEE_Param input buffer and are unchecked. In particular, by specifying a info_len value greater than the size of the TEE_Param input buffer, a buffer overread will happen in the hkdf_expand function, where the info data is copied into a heap-allocated buffer.

int hkdf_expand(char *prk, int prk_len, char *info, int info_len, char *okm, int okm_len) {
    // ...
    tmpbuf_len = info_len + 1;
    tmpbuf = TEE_Malloc(tmpbuf_len, 0);
    tmpbuf_off = 0;
    while (1) {
        memcpy_s(&tmpbuf[tmpbuf_off], tmpbuf_len - tmpbuf_off, info, info_len);
        memcpy_s(&tmpbuf[tmpbuf_off + info_len], tmpbuf_len - tmpbuf_off - info_len, &c, 1);
        // ...compute MAC and copy it to okm...
        tmpbuf_off = macLen;
        TEE_Free(tmpbuf);
        // ...exit if expansion is finished...
        tmpbuf_len = info_len + tmpbuf_off + 1;
        tmpbuf = TEE_Malloc(tmpbuf_len, 0);
        memcpy_s(tmpbuf, info_len + tmpbuf_off + 1, mac, tmpbuf_off);
    }
    // ...
}

A proof of concept code can be used to trigger this buffer overread:

[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: 0x70007000, fault_code: 0x92000007
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[Huawei_TSS_TA] 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=7934 prefer-ca=7934
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] sp > fpDump task states END
[HM] 
[HM] [TRACE][1212]pid=57 exit_status=130

Limited Out of Bounds Accesses in CMD_TSS_GET_PKI_CERT and CmdVerifySignature

There is a limited out of bounds access in the command CMD_TSS_GET_PKI_CERT (ID #0x40017).

/*** in the .data section ***/
char *CERT_FACTOR[1] = { /* ... */ };
/****************************/

TEE_Result TA_InvokeCommandEntryPoint(
        void *sessionContext,
        uint32_t commandID,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    // [...]
    switch ( commandID )
    {
        // [...]
        case 0x40017:
            cert_idx = params->value.a;
            if (cert_idx + 1 <= 1)
            {
                // [...]
                factor = CERT_FACTOR[cert_idx];
                factor_strlen = strlen(factor);
                convert_hex_to_byte(factor, outbuf, factor_strlen);
                // [...]
            }
            // [...]
            break;
    }
    // [...]
}

A TEE_Param input value cert_idx is used as an index in the global variable CERT_FACTOR. However there is an issue in the condition that makes sure cert_idx is bound to a given range: cert_idx + 1 <= 1. This condition allows cert_idx to be equal to -1, which would result in an out of bounds access to the first integer before CERT_FACTOR.

/*** in the .data section ***/
struct public_key {
    char *mod;
    char *exp;
} KEY_FACTOR[1] = { /* ... */ };
/****************************/

TEE_Result CmdVerifySignature(
        uint32_t type,
        uint32_t alg,
        uint32_t index,
        char *inbuf,
        uint32_t inbuf_len,
        char *signature,
        uint32_t signature_len)
{
    // ...
    if (index + 1 <= 1) {
        // ...
        convert_hex_to_byte(KEY_FACTOR[index].mod, pub_mod, 0x200);
        convert_hex_to_byte(KEY_FACTOR[index].exp, pub_exp, 6);
        // ...
    }
    // ...
}

There is a similar out of bounds access in the CmdVerifySignature function called by the command with ID #0x40008.

A proof of concept demonstrates that triggering the out of bounds access in CMD_TSS_GET_PKI_CERT results in a bad read memory access at address 0x5b32ffc:

[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: 0x5b32ffc, fault_code: 0x92000007
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[Huawei_TSS_TA] 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=8980 prefer-ca=8980
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TA_InvokeCommandEntryPoint+0x1750/0x1e1c>
[HM] <memset_s>+0x28/0x38
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM] 

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
Stack Buffer Overflow in TA_GetPayload High N/A (*) Fixed
Heap Buffer Overflows and Stack Buffer Overreads in TA_DecryptSKWithCBC and TA_DecryptSKWithGCM High N/A (*) Fixed
Heap Buffer Overflow in TA_Gen_Sysintegrity_Jws High N/A (*) Fixed
Param Buffer Overread in TA_GetPayload Low N/A Fixed
Param Buffer Overread in TA_GetSysintegritySignStr Low N/A Fixed
Param Buffer Overread in TA_DecryptKEK Low N/A Fixed
Param Buffer Overread in hkdf_expand Low N/A Fixed
Limited Out of Bounds Accesses in CMD_TSS_GET_PKI_CERT and CmdVerifySignature Low 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

  • Dec. 21, 2021 - A vulnerability report is sent to Huawei PSIRT.
  • Jan. 12, 2022 - Huawei PSIRT acknowledges the vulnerability report.
  • From Nov. 30, 2022 to Jul, 19 2023 - We exchange regularly about the release of our advisories.