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

Incomplete Caller Verification

Huawei's TEE OS iTrustee implements a whitelist mechanism that only allows specific CAs to talk to a TA. The TA declares which CAs is allowed to talk to it by calling the AddCaller_CA_exec and/or AddCaller_CA_apk functions in its TA_CreateEntryPoint. For native binaries, it specifies the expected binary path and user id, and for APKs, the package name and public key used to verify the APK's signature.

We noticed that the HuaweiWallet TA does not call either of these functions. As a result, we believe that any APK can talk to this TA.

Stack Buffer Overflow in GetCardALLByIndexV2

There is a stack buffer overflow in the GetCardALLByIndexV2 function, which is called from the command CmdWalletGetCardByIndex (ID #0x20010). This buffer overflow is the result of multiple integer overflows/underflows in the function.

unsigned int CmdWalletGetCardByIndex(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    card_list_t cards;
    // ...
    GetCardALLByIndexV2(params[3].value.a, &cards);
    // ...
}

This function takes as argument in integer index denoting which slice of 0x10 cards to copy into its other argument cards.

  • At [1], the index is checked to ensure that it is positive.
  • At [2], the index is multiplied by 0x2300 to calculate the read offset into the file. An integer overflow can happen here.
  • At [3], the index+1 is multiplied by 0x10 to calculate the number of the last card requested. An integer overflow can happen here. If this number is smaller than amount, the total number of cards, some calculations are performed.
  • At [4], the number of cards to return count is calculated by subtracting to amount the index multiplied by 0x10. An integer underflow can happen on the subtraction and an integer overflow can happen on the multiplication.
  • At [5], the size of the data to read from the file is calculated by multiplying the index by 0x230. An integer overflow can happen here.
  • At [6], a buffer of size bytes is allocated from the heap, filled with zeroes, and then populated with the content of the file.
  • At [7], a check is performed on the size expected and the size read and, if they differ, a message is printed but the execution continues.
  • At [8], the buffer is copied (all of its size bytes) into the cards parameter, triggering a stack buffer overflow if size is bigger than 0x2304 (as card is a stack variable from the stack frame of CmdWalletGetCardByIndex).
int GetCardALLByIndexV2(int index, card_list_t *cards) {
    // ...
    if (index < 0 /* [1] */) { /* ... */ return 0xFFFF0006; }
    // ...
    int amount;
    GetCardListV2Length(&amount);
    // ...
    TEE_SeekObjectData(object, 0x2300 * index /* [2] */, TEE_DATA_SEEK_SET);
    // ...
    if (amount <= 0x10 * (index + 1) /* [3] */) {
        count = amount - 0x10 * index /* [4] */;
        size = 0x230 * count /* [5] */;
    } else {
        count = 0x10;
        size = 0x2300;
    }
    // ...
    buffer = TEE_Malloc(/* [6] */ size, 0);
    TEE_MemFill(buffer, 0, size);
    TEE_ReadObjectData(object, buffer, size, &read_size);
    if (/* [7] */ read_size != size) { /* ... */ }
    // ...
    TEE_MemFill(cards, 0, 0x2304);
    TEE_MemMove(cards, buffer, size); /* [8] */
    // ...
}

To find an index value that matches all the requirements, we have used the Z3 solver in a Python script given below. Running this script gives us an index value of 0x4924a3, which results in a size of 0x35c0 bytes. By changing the constraints, it is possible to obtain multiple working values resulting in different sizes.

from z3 import *

x = BitVec('x', 32)
y = BitVec('y', 32)
n = BitVec('n', 32)
s = Solver()
s.add(x >= 0)
s.add(ULT(x * 0x2300, 292 * 0x230))
s.add(y == 0x10 * (x + 1))
s.add(y >= 0)
s.add(UGE(y, 292))
s.add(n == 0x230 * (292 - 0x10 * x))
s.add(UGE(n, 0x3000))
s.add(ULE(n, 0x4000))

print(s.check())
m = s.model()
x_val = None
for d in m.decls():
    if d.name() == 'x':
        x_val = int(str(m[d]))
    print("%s = %s" % (d.name(), m[d]))

Proof of concept code triggering the vulnerability results in a stack buffer overflow of 0x35c0 bytes, reaching the end of the memory allocated for the stack, and triggering a crash:

[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: 0x6804d03, fault_code: 0x92000047
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7392 prefer-ca=7392
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TEE_MemMove+0x54/0x68>
[HM] <GetCardALLByIndexV2>+0x1ac/0x2ec
[HM] Dump task states END
[HM] 

Stack Buffer Overflow in genOffPayCodeSeedParam

There is a stack buffer overflow in the function genOffPayCodeSeedParam called by the command CmdWalletGenPayCodeSeedParam (ID #0x20046).

uint32_t CmdWalletGenPayCodeSeedParam(void *sessionContext, uint32_t paramTypes,
        TEE_Param params[4])
{
    buffer = (params_t *)params[0].memref.buffer;
    size = params[0].memref.size;
    // [...]
    result = genOffPayCodeSeedParam(
        buffer,
        size,
        encAK,
        &encAKLen,
        encVd,
        &encVdLen,
        AI,
        &AILen);
    // [...]
}

In genOffPayCodeSeedParam, the user controls the aidLen (extracted from offset 0xc in params[0].memref.buffer). Since there is no check on aidLen before the call to TEE_MemMove, it's possible to trigger a buffer overflow on seed.aid, which is a buffer of 42 bytes inside the stack allocated structure seed of type pay_code_seed_v2_t.

uint32_t genOffPayCodeSeedParam(
        params_t *buffer,
        uint32_t size,
        void *encAK,
        uint32_t *encAKLen_p,
        void *encVd,
        uint32_t *encVdLen_p,
        void *AI,
        uint32_t *AILen_p)
{
    // [...]
    // Stack buffer overflow
    TEE_MemMove(seed.aid, (char *)buffer + buffer->aidOff, buffer->aidLen);
    // [...]
}

In our proof of concept code to trigger this bug, we try to write 0x10000 bytes which overflows the size of the stack, resulting in an OOB write after the stack, into unmapped memory.

[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: 0x662b000, fault_code: 0x92000047
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7656 prefer-ca=7656
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TEE_MemMove+0x44/0x68>
[HM] <genOffPayCodeSeedParam>+0xa0/0x37c
[HM] Dump task states END
[HM] 

Stack Buffer Overflows in decodeCRSCert

There any multiple stack buffer overflows in the decodeCRSCert function, all resulting from the same vulnerable pattern.

The decodeCRSCert function is called from the CmdWalletInitCRSCert command (ID #0x20025).

unsigned int CmdWalletInitCRSCert(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    certificationReq = params[0].memref.buffer;
    certificationReqLen = params[0].memref.size;
    // ...
    initAndGenerateCRSPin(certificationReq, certificationReqLen, encryptedPin, &encryptedPinLen);
    // ...
}
int initAndGenerateCRSPin(
        void *certificationReq,
        int certificationReqLen,
        void *encryptedPin,
        uint32_t *encryptedPinLen_p)
{
    // ...
    decodeCRSCert(certificationReq, certificationReqLen, crs_cert, &crs_cert_len);
    // ...
}

It extracts various TLVs from the DER-encoded certification request provided in the certificationReq argument (which is params[0].memref.buffer so it is fully user-controlled).

The vulnerable pattern is as follows:

  • a TLV of a specific type, here CRS_PUBLIC_KEY_TAG_MODULUS_REMAINDER, is searched for in the input buffer using ta_bertlv_grp_find
  • the value pointer of the TLV is retrieved using ta_bertlv_get_value
  • the length of the TLV is retrieved using ta_bertlv_get_length and is unchecked
  • the value pointer is used as source and the length as size in a TEE_MemMove call where the destination is the stack buffer buf

This vulnerable pattern results in a textbook stack buffer overflow and is repeated many times throughout the function.

TEE_Result decodeCRSCert(
        void *certificationReq,
        uint32_t certificationReqLen,
        storage_t *crs_cert,
        uint32_t *crs_cert_len_p)
{
    // [...]
    char plain[2048];
    uint8_t buf[2048];
    // [...]
    tag = ta_bertlv_get_tag(&CRS_PUBLIC_KEY_TAG_HEAD_CRS);
    tag_head_crs = ta_bertlv_grp_find(certificationReq, certificationReqLen, tag);
    head_crs = ta_bertlv_get_value(tag_head_crs);
    len_head_crs = ta_bertlv_get_length(tag_head_crs);
    // [...]
    tag = ta_bertlv_get_tag(&CRS_PUBLIC_KEY_TAG_SIGNATURE);
    tag_signature = ta_bertlv_grp_find(head_crs, len_head_crs, tag);
    signature = ta_bertlv_get_value(tag_signature);
    len_signature = ta_bertlv_get_length(tag_signature);
    // [...]
    TEE_MemFill(&pub_key, v10, 0x808);
    convertHexToByte(NXP_CA_PUBLIC_RSA_KEY_MOD, pub_key.mod, 0x100);
    convertHexToByte(NXP_CA_PUBLIC_RSA_KEY_EXP, pub_key.exp, 6);
    pub_key.exp_len = 3;
    pub_key.mod_len = 0x80;
    plainLen = 0x800;
    ret = TA_RsaEncrypt(
        &pub_key,
        signature,
        len_signature,
        TEE_ALG_RSA_NOPAD,
        plain,
        &plainLen);
    if (ret) {
        /* ... */
        // Error path
        return 0xFFFF0006;
    }
    buf_off = 0;
    TEE_MemMove(&buf[buf_off], &plain[1], plainLen - 0x16);
    buf_off += plainLen - 0x16;
    // [...]
    tag = ta_bertlv_get_tag(CRS_PUBLIC_KEY_TAG_MODULUS_REMAINDER);
    tag_modulus_remainder = ta_bertlv_grp_find(head_crs, len_head_crs, tag);
    // User-controlled value
    modulus_remainder = ta_bertlv_get_value(tag_modulus_remainder);
    // User-controlled size
    len_modulus_remainder = ta_bertlv_get_length(tag_modulus_remainder);
    TEE_MemMove(&buf[buf_off], modulus_remainder, len_modulus_remainder);
    buf_off += len_modulus_remainder;
    // [...] similar pattern repeated many times [...]
}

In order to reach the vulnerable code path in decodeCRSCert, we would need to provide a legitimate RSA-encrypted blob in certificationReq to pass the check after the call to TA_RsaEncrypt. We did not develop a proof of concept for this vulnerability, since we did not spend additional time trying to find, or craft, such a blob.

Heap Buffer Overflow in initPayCodeHead

There is a heap-based buffer overflow in the function initPayCodeHead called by the following commands:

  • CmdWalletVerifyPayCodeAuthInfo (ID #0x20051)
  • CmdWalletGetFinancePayCode (ID #0x20052)
  • CmdWalletGetTrafficPayCode (ID #0x20048)
  • CmdWalletGenPayCodeSeedParam (ID #0x20046)
  • CmdWalletCheckPayCodeSeedValid (ID #0x20045)

To illustrate this vulnerability, we will take the execution flow starting from the command CmdWalletGenPayCodeSeedParam.

uint32_t CmdWalletGenPayCodeSeedParam(void *sessionContext, uint32_t paramTypes,
        TEE_Param params[4])
{
    buffer = (params_t *)params[0].memref.buffer;
    size = params[0].memref.size;
    // [...]
    result = genOffPayCodeSeedParam(
        buffer,
        size,
        encAK,
        &encAKLen,
        encVd,
        &encVdLen,
        AI,
        &AILen);
    // [...]
}

The user is able to control the value acctLen, found at offset 0xc in params[0].memref.buffer.

uint32_t genOffPayCodeSeedParam(
        params_t *buffer,
        uint32_t size,
        void *encAK,
        uint32_t *encAKLen_p,
        void *encVd,
        uint32_t *encVdLen_p,
        void *AI,
        uint32_t *AILen_p)
{
    // [...]
    initForUnionSeedList((char *)buffer + buffer->acctOff, buffer->acctLen, &pay_codes, 1);
    // [...]
}

This value is passed to initForUnionSeedList and then to initPayCodeHead without any checks.

unsigned int initForUnionSeedList(
        const void *account,
        uint32_t accountLen,
        pay_code_list_v2_t *pay_codes,
        int a4)
{
    // ...
    initPayCodeHead(account, accountLen, pay_codes);
    // ...
}

initPayCodeHead finally uses this value as the size argument of TEE_MemMove, resulting in a heap-based buffer overflow of head->data.

unsigned int initPayCodeHead_isra_2(const void *account, uint32_t accountLen,
        pay_code_list_v2_t *pay_codes)
{
    // [...]
    TEE_MemMove(head->data, account, accountLen);
    // [...]
}

In our proof of concept code, we try to write 0x10000 bytes which exceeds the size of the stack, resulting in an OOB write after the heap, into unmapped memory.

[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: 0xda6000, fault_code: 0x92000047
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=8210 prefer-ca=8210
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TEE_MemMove+0x44/0x68>
[HM] <initPayCodeHead.isra.2>+0x4c/0xbc
[HM] Dump task states END
[HM] 

Heap Buffer Overread in isSamePayCodeSeed

The isSamePayCodeSeed function is used to check if a pay code has the aid given as argument. The length used in the comparison is the maximum of the pay code's aid length and the aidLen argument. This allows giving an aidLen greater than 42, the maximum aid length, resulting in a buffer overread.

int isSamePayCodeSeed(const void *aid, uint32_t aidLen, int seedType, pay_code_seed_v2_t *seed) {
    // ...
    if (aidLen < seed->aidLen)
        seed_aidLen = seed->aidLen;
    else
        seed_aidLen = aidLen;
    return TEE_MemCompare(aid, seed->aid, seed_aidLen) == 0;
}

For example, this function can be called with user-controlled arguments using the CmdWalletCheckPayCodeSeedValid command (ID #0x20045).

unsigned int CmdWalletCheckPayCodeSeedValid(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    aid = params[1].memref.buffer;
    aidLen = params[1].memref.size;
    seedType = params[2].value.a;
    // ...
    checkOffPayCodeSeedValid(account, accountLen, aid, aidLen, seedType, &flushFlag);
    // ...
}
int checkOffPayCodeSeedValid(
        void *account,
        uint32_t accountLen,
        void *aid,
        uint32_t aidLen,
        uint8_t seedType,
        uint32_t *flushFlag_p)
{
    // ...
    checkSeedInfoValid_constprop_12(aid, aidLen, seedType, &seed, &pay_codes, flushFlag_p);
    // ...
}
unsigned int checkSeedInfoValid_constprop_12(
        void *aid,
        int aidLen,
        int seedType,
        pay_code_seed_v2_t **seed_p,
        pay_code_list_v2_t *pay_codes,
        uint32_t *flushFlag_p)
{
    // ...
    for (int index = 0; index < pay_codes->head->num; index++) {
        if (isSamePayCodeSeed(aid, aidLen, seedType, &pay_codes->seeds[index]))
            break;
    }
    // ...
}

Our proof of concept code, that makes use of this bug to disclose heap memory, bruteforces the bytes following seed->aid one by one and discriminates between correct and incorrect byte values using the command's return code. Running it produces the following output:

00000000: 00 00 00 00 00 00 00 00 00 00 5e ec ed 82 b8 f5
00000010: a3 1d 1f 5c 3b 45 f8 ee 0d e6 1d 52 71 a5 43 75
00000020: fc 57 97 8d 0b 4b 45 94 23 8b ea 80 ce 5a 60 db
00000030: 05 7a 91 ca 1a 50 04 65 53 6f 00 00 00 00 00 00
00000040: 00 00 00 00 00 00 00 00 00 00 1b 0f 01 6b 05 32
00000050: 1c ba 86 e1 91 5d 58 6b a2 e0 00 00 00 00 00 00
00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
00000270: 00 00 00 00 21 04 00 00 c0 2d 00 00 a8 e5 7f 03
00000280: a8 e5 7f 03 1c 24 8f 03 00 00 00 00 00 00 00 00
00000290: 00 00 00 00 21 00 00 00 a1 2d 00 00 a8 e5 7f 03
000002a0: a8 e5 7f 03 00 00 00 00 00 00 00 00 00 00 00 00
000002b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
00000380: 00 00 00 00 64 63 2d 01 00 00 00 00 01 00 00 00
00000390: 00 00 00 00 cd ab cd ab 02 00 00 00 00 00 00 00
000003a0: 10 00 00 a0 00 00 00 00 20 00 00 00 ff ff ff ff
000003b0: 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00
000003c0: 00 00 00 00 51 01 00 00 31 00 00 00 00 00 00 00
000003d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
000003f0: 00 00 00 00 31 00 00 00 41 2c 00 00 a8 e5 7f 03
00000400: a8 e5 7f 03 00 00 00 00 02 00 00 00 20 02 00 10
00000410: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
00000440: 00 00 00 00 00 00 00 00 00 00 00 00 08 f8 8a 03
00000450: 00 00 00 00 61 00 00 00 e1 2b 00 00 a8 e5 7f 03
00000460: a8 e5 7f 03 00 00 00 00 00 00 00 00 00 00 00 00
00000470: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
000004f0: 00 00 00 00 a1 00 00 00 41 2b 00 00 a8 e5 7f 03
00000500: a8 e5 7f 03 00 00 00 00 00 00 00 00 00 00 00 00
00000510: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
00000600: 00 00 00 00 11 01 00 00 31 2a 00 00 a8 e5 7f 03
00000610: a8 e5 7f 03 57 49 1d f0 cb ec 8a 07 d8 07 c2 83
00000620: 8b df cf f0 91 79 84 20 da 22 ef 53 3d 62 56 b5
00000630: 6b 4e 0a 73 c6 9d 59 ba ed 70 9b c0 bb c8 5d 62
00000640: a0 ae 9b 68 6d e2 de 63 42 c7 50 71 f7 84 db 6c
00000650: 90 44 fb 1c bf 0b ff 15 48 db c3 82 6c d4 b5 2c
00000660: f9 e6 3e 69 ba 2a 4f 52 86 58 7f 5d 13 79 9d 0f
00000670: 8e ec 69 94 ed 25 7c 2b 35 1f 42 a7 9a ab a6 3a
00000680: 6f 9f d8 aa 71 f4 61 f3 3e 83 d8 dc 7a 4f f6 1c

Heap Buffer Overread in transferV1ToV2Paycode

There is a heap buffer overread in the transferV1ToV2Paycode function called all the way from the CmdWalletGenPayCodeSeedParam command (ID #0x20046).

unsigned int CmdWalletGenPayCodeSeedParam(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    buffer = (params_t *)params[0].memref.buffer;
    size = params[0].memref.size;
    // ...
    genOffPayCodeSeedParam(buffer, size, encAK, &encAKLen, encVd, &encVdLen, AI, &AILen);
    // ...
}
int genOffPayCodeSeedParam(
        params_t *buffer,
        uint32_t size,
        void *encAK,
        uint32_t *encAKLen_p,
        void *encVd,
        uint32_t *encVdLen_p,
        void *AI,
        uint32_t *AILen_p)
{
    // ...
    initForUnionSeedList((char *)buffer + buffer->acctOff, buffer->acctLen, &pay_codes, 1);
    // ...
}
unsigned int initForUnionSeedList(
        const void *account,
        uint32_t accountLen,
        pay_code_list_v2_t *pay_codes,
        int should_init)
{
    // ...
    ret_v1 = TEE_OpenPersistentObject(1, "sec_storage/huawei_wallet/payment_code", 0x26, 1, &object_v1);
    // ...
    if (ret_v1 == 0 && should_init) {
        transferV1ToV2Paycode(object_v1, account, accountLen, pay_codes);
        // ...
    }
    // ...
}

In transferV1ToV2Paycode, the user controls, among other things, the accountLen value that was extracted from params[0].memref.buffer. The account buffer is compared to pay_codes.head->data, and the size of the comparison is the maximum value between pay_codes.head->acctLen and accountLen. Thus we control the control the size of the comparison, resulting in a heap buffer overread of pay_codes.head->data.

unsigned int transferV1ToV2Paycode(
        TEE_ObjectHandle object,
        const void *account,
        uint32_t accountLen,
        pay_code_list_v2_t *pay_codes_v2)
{
    // ...
    acctLen = pay_codes.head->acctLen;
    if (accountLen >= acctLen)
        acctLen = accountLen;
    if (TEE_MemCompare(pay_codes.head->data, account, acctLen)) { /* ... */ }
}

Unfortunately, it is not possible to easily demonstrate this issue. This is because calling transferV1ToV2Paycode requires a v1 pay code to have been previously saved, and the current version of the TA doesn't allow saving v1 pay codes. But it should be possible to reach this code path by loading an old version of the TA, using it to save a v1 pay code, then loading the current version and triggering a transfer.

OOB Accesses in CmdWalletGenPayCodeSeedParam

There are multiple pointers in the command handler CmdWalletGenPayCodeSeedParam (ID# 0x20046) which can be controlled arbitrarily and result in OOB accesses down the line.

uint32_t CmdWalletGenPayCodeSeedParam(void *sessionContext, uint32_t paramTypes,
        TEE_Param params[4])
{
    buffer = (params_t *)params[0].memref.buffer;
    size = params[0].memref.size;
    // [...]
    result = genOffPayCodeSeedParam(
        buffer,
        size,
        encAK,
        &encAKLen,
        encVd,
        &encVdLen,
        AI,
        &AILen);
    // [...]
}

In genOffPayCodeSeedParam, the user controls, among others, the following values:

  • acct at offset 0x14 in buffer
  • acctLen at offset 0x18 in buffer
  • aidOff at offset 0x1c in buffer
  • aidLen at offset 0x20 in buffer

Using these values, two pointers are computed:

  • acct = buffer + acctOff
  • aid = buffer + aidOff
uint32_t genOffPayCodeSeedParam(
        params_t *buffer,
        uint32_t size,
        void *encAK,
        uint32_t *encAKLen_p,
        void *encVd,
        uint32_t *encVdLen_p,
        void *AI,
        uint32_t *AILen_p)
{
    // [...]
    // OOB Access
    initForUnionSeedList((char *)buffer + buffer->acctOff, buffer->acctLen, &pay_codes, 1);
    // [...]
    // OOB Access
    TEE_MemMove(seed.aid, (char *)buffer + buffer->aidOff, buffer->aidLen);
    // [...]
}

Since the lengths and offsets retrieved from the TEE_Param input buffer are never checked, the pointers can be user-controlled, which results in OOB accesses in subsequent function calls (i.e. initForUnionSeedList and genOffPayCodeSeedParam), as illustrated above.

The proof of concept code to trigger this bug results in a crash caused by a read access at address 0x4eadeeef = 0xdeadbeef + 0x70003000 when the out of bounds value is accessed.

[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: 0x4eadeeef, fault_code: 0x92000006
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7542 prefer-ca=7542
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TEE_MemCompare+0x84/0xbc>
[HM] <initForUnionSeedList>+0x354/0x5f0
[HM] invalid fp. backtrace abort
[HM] Dump task states END
[HM] 

OOB Accesses in CmdWalletSavePayCodeSeed

In the CmdWalletSavePayCodeSeed and saveOffPayCodeSeed functions, user-controlled values are extracted from the first param input buffer.

unsigned int CmdWalletSavePayCodeSeed(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    buffer = (params_t *)params[0].memref.buffer;
    // ...
    if (/* ... */ || buffer->twLen > 0x28) { /* ... */ }
    // ...
    return saveOffPayCodeSeed(buffer);
}

In saveOffPayCodeSeed, the following values are extracted:

  • acct of length buffer->acctLen and starting at buffer->acctOff
  • acct of length buffer->acctLen and starting at buffer->acctOff
  • signText of length buffer->signTextLen and starting at buffer->signTextOff
  • signResult of length buffer->signResultLen and starting at buffer->signResultOff
  • aid of length buffer->aidLen and starting at buffer->aidOff
  • tw of length buffer->twLen and starting at buffer->twOff
TEE_Result saveOffPayCodeSeed(params_t *buffer) {
    // ...
    initForUnionSeedList((char *)buffer + buffer->acctOff, buffer->acctLen, &pay_codes, 0);
    // ...
    verifySignWithRSA(
             buffer->server_type,
             (char *)buffer + buffer->signTextOff,
             buffer->signTextLen,
             (char *)buffer + buffer->signResultOff,
             buffer->signResultLen);
    // ...
    for (int index = 0; index < pay_codes.head->num; index++) {
        isSamePayCodeSeed(
                 (char *)buffer + buffer->aidOff,
                 buffer->aidLen,
                 buffer->seedType,
                 &pay_codes.seeds[index]);
        // ...
    }
    // ...
    TEE_MemMove(&seed->tw, (char *)buffer + buffer->twOff, buffer->twLen);
    // ...
}

Since the various offset and length values (except buffer->twLen) are never checked, acct, signText, signResult, aid and tw can be made to point to anywhere in memory (relatively to params[0].memref.buffer), which results in OOB accesses in subsequent function calls. For example, later in calc_hash (called from verifySignWithRSA):

int verifySignWithRSA(int server_type, void *signValue, int signValueLen, void *signRes, int signResLen) {
    // ...
    calc_hash(signHash, 0x20u, TEE_ALG_SHA256, signValue, signValueLen);
    // ...
}
TEE_Result calc_hash(void *hash, uint32_t a2, uint32_t algo, const void *data, uint32_t data_len) {
    // ...
    TEE_DigestDoFinal(operation, data, data_len, hash, hashLen);
    // ...
}

Proof of concept code triggering this bug results in a read access on the user-controlled address 0x4eadeeef = 0x70003000 + 0xdeadbeef:

[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: 0x4eadeeef, fault_code: 0x92000006
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7421 prefer-ca=7421
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <SHA256_Update>+0xe4/0x114
[HM] invalid fp. backtrace abort
[HM] Dump task states END
[HM] 

OOB Accesses in CmdWalletSetPayCodeAuthInfo

In the CmdWalletSetPayCodeAuthInfo and setPayCodeAuthInfo functions, user-controlled values are extracted from the first param input buffer.

unsigned int CmdWalletSetPayCodeAuthInfo(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    buffer = (params_t *)params[0].memref.buffer;
    if (/* ... */ || buffer->twLen > 0x28 ) { /* ... */ }
    return setPayCodeAuthInfo(buffer);
}

In setPayCodeAuthInfo, the following values are extracted:

  • acct of length buffer->acctLen and starting at buffer->acctOff
  • signResult of length buffer->signResultLen and starting at buffer->signResultOff
  • tw of length buffer->twLen and starting at buffer->twOff
unsigned int setPayCodeAuthInfo(params_t *buffer) {
    // ...
    initForUnionSeedList((char *)buffer + buffer->acctOff, buffer->acctLen, &pay_codes, 1);
    // ...
    verifySignWithRSA(
          buffer->server_type,
          signText,
          signTextLen,
          (char *)buffer + buffer->signResultOff,
          buffer->signResultLen);
    // ...
    TEE_MemMove(&head->data[0x24], (char *)buffer + buffer->twOff, buffer->twLen);
    // ...
    calc_hash(hash, 0x20u, TEE_ALG_SHA256, (char *)buffer + buffer->twOff, buffer->twLen);
    // ...
}

Since the various offset and the length values (except buffer->twLen) are never checked, acct, signResult and tw can be made to point to anywhere in memory (relatively to params[0].memref.buffer), which results in OOB accesses in subsequent function calls. For example, later in initPayCodeHead (called from initForUnionSeedList):

unsigned int initForUnionSeedList(
        const void *account,
        uint32_t accountLen,
        pay_code_list_v2_t *pay_codes,
        int a4)
{
    // ...
    initPayCodeHead_isra_2(account, accountLen, pay_codes);
    // ...
}
unsigned int initPayCodeHead_isra_2(const void *account, uint32_t accountLen, pay_code_list_v2_t *pay_codes) {
    // ...
    TEE_MemMove(head->data, account, accountLen);
    // ...
}

Proof of concept code triggering this bug results in a read access on the user-controlled address 0x4eadeeef = 0x70003000 + 0xdeadbeef:

[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: 0x4eadeeef, fault_code: 0x92000006
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=6258 prefer-ca=6258
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TEE_MemCompare+0x84/0xbc>
[HM] <initForUnionSeedList>+0x354/0x5f0
[HM] invalid fp. backtrace abort
[HM] Dump task states END
[HM]

OOB Accesses in CmdWalletGetTrafficPayCode

In the CmdWalletGetTrafficPayCode and getOffPayCode functions, user-controlled values are extracted from the first param input buffer.

unsigned int CmdWalletGetTrafficPayCode(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    buffer = (params_t *)params[0].memref.buffer;
    size = params[0].memref.size;
    UnionPayCode = params[1].memref.buffer;
    UnionPayCodeLen = params[1].memref.size;
    flushFlag = params[2].value.a;
    // ...
    getOffPayCode(
         buffer,
         size,
         UnionPayCode,
         &UnionPayCodeLen,
         outTK,
         &outTKLen,
         UFK,
         &UFKLen,
         tw,
         &twLen,
         &flushFlag);
    // ...
}

In getOffPayCode, the following values are extracted:

  • acct of length buffer->acctLen and starting at buffer->acctOff
  • tw of length buffer->twLen and starting at buffer->twOff
  • aid of length buffer->aidLen and starting at buffer->aidLen
unsigned int getOffPayCode(
        params_t *buffer,
        int size,
        void *UnionPayCode,
        uint32_t *UnionPayCodeLen_p,
        void *outTK,
        uint32_t *outTKlen_p,
        void *UFK,
        uint32_t *UFKLen_p,
        void *tw,
        uint32_t *twLen_p,
        uint32_t *flushFlag_p)
{
    // ...
    initForUnionSeedList((char *)buffer + buffer->acctOff, buffer->acctLen, &pay_codes, 0);
    // ...
    checkAuthValid_constprop_13(
          buffer->AuthType,
          (char *)buffer + buffer->twOff,
          buffer->twLen,
          0,
          0,
          outTK,
          outTKlen_p,
          &pay_codes);
    // ...
    checkSeedInfoValid_constprop_12((char *)buffer + buffer->aidOff, buffer->aidLen, buffer->seedType, &seed, &pay_codes, flushFlag_p);
    // ...
}

Since the various offset and the length values are never checked, acct, tw and aid can be made to point to anywhere in memory (relatively to params[0].memref.buffer), which results in OOB accesses in subsequent function calls. For example, later in initPayCodeHead (called from initForUnionSeedList), as seen previously.

Proof of concept code triggering this bug results in a read access on the user-controlled address 0x4eadeeef = 0x70003000 + 0xdeadbeef:

[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: 0x4eadeeef, fault_code: 0x92000006
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7679 prefer-ca=7679
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TEE_MemCompare+0x84/0xbc>
[HM] <initForUnionSeedList>+0x354/0x5f0
[HM] Dump task states END
[HM] 

OOB Accesses in CmdWalletGetFinancePayCode

In the CmdWalletGetFinancePayCode and getOffPayCode functions, user-controlled values are extracted from the first param input buffer.

unsigned int CmdWalletGetFinancePayCode(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    buffer = (params_t *)params[0].memref.buffer;
    size = params[0].memref.size;
    UnionPayCode = params[1].memref.buffer;
    UnionPayCodeLen = params[1].memref.size;
    outTK = params[2].memref.buffer;
    outTKlen = params[2].memref.size;
    flushFlag = params[3].value.a;
    // ...
    getOffPayCode(
         buffer,
         size,
         UnionPayCode,
         &UnionPayCodeLen,
         outTK,
         &outTKlen,
         UFK,
         &UFKLen,
         tw,
         &twLen,
         &flushFlag);
    // ...
}

In getOffPayCode, the following values are extracted:

  • acct of length buffer->acctLen and starting at buffer->acctOff
  • tw of length buffer->twLen and starting at buffer->twOff
  • aid of length buffer->aidLen and starting at buffer->aidLen
unsigned int getOffPayCode(
        params_t *buffer,
        int size,
        void *UnionPayCode,
        uint32_t *UnionPayCodeLen_p,
        void *outTK,
        uint32_t *outTKlen_p,
        void *UFK,
        uint32_t *UFKLen_p,
        void *tw,
        uint32_t *twLen_p,
        uint32_t *flushFlag_p)
{
    // ...
    initForUnionSeedList((char *)buffer + buffer->acctOff, buffer->acctLen, &pay_codes, 0);
    // ...
    checkAuthValid_constprop_13(
          buffer->AuthType,
          (char *)buffer + buffer->twOff,
          buffer->twLen,
          0,
          0,
          outTK,
          outTKlen_p,
          &pay_codes);
    // ...
    checkSeedInfoValid_constprop_12((char *)buffer + buffer->aidOff, buffer->aidLen, buffer->seedType, &seed, &pay_codes, flushFlag_p);
    // ...
}

Since the various offset and the length values are never checked, acct, tw and aid can be made to point to anywhere in memory (relatively to params[0].memref.buffer), which results in OOB accesses in subsequent function calls. For example, later in initPayCodeHead (called from initForUnionSeedList), as seen previously.

Proof of concept code triggering this bug results in a read access on the user-controlled address 0x4eadeeef = 0x70003000 + 0xdeadbeef:

[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: 0x4eadeeef, fault_code: 0x92000006
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7373 prefer-ca=7373
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TEE_MemCompare+0x84/0xbc>
[HM] <initForUnionSeedList>+0x354/0x5f0
[HM] Dump task states END
[HM] 

OOB Accesses in CmdWalletVerifyPayCodeAuthInfo

In the CmdWalletVerifyPayCodeAuthInfo and verifyPayCodeAuthInfo functions, user-controlled values are extracted from the first param input buffer.

unsigned int CmdWalletVerifyPayCodeAuthInfo(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    buffer = (params_t *)params[0].memref.buffer;
    size = params[0].memref.size;
    tkOut = params[1].memref.buffer;
    tkOutLen = params[1].memref.size;
    acct = (char *)buffer + buffer->acctOff;
    acctLen = buffer->acctLen;
    AV = (char *)buffer + buffer->signTextOff;
    AVLen = buffer->signTextLen;
    SignRes = (char *)buffer + buffer->signResultOff;
    SignResLen = buffer->signResultLen;
    authType = buffer->AuthType;
    // ...
    verifyPayCodeAuthInfo(acct, acctLen, authType, AV, AVLen, SignRes, SignResLen, tkOut, &tkOutLen);
    // ...
}

In verifyPayCodeAuthInfo, the following values are extracted:

  • acct of length buffer->acctLen and starting at buffer->acctOff
  • AV of length buffer->AVLen and starting at buffer->AVOff
  • SignRes of length buffer->SignResLen and starting at buffer->SignResLen
unsigned int verifyPayCodeAuthInfo(
        void *acct,
        uint32_t acctLen,
        int authType,
        void *AuthValue,
        uint32_t AuthValueLen,
        void *signResult,
        uint32_t signResultLen,
        void *tkOut,
        uint32_t *tkOutLen_p)
{
    // ...
    initForUnionSeedList(acct, acctLen, pay_codes, 1);
    // ...
    checkAuthValid_constprop_13(
          authType,
          AuthValue,
          AuthValueLen,
          signResult,
          signResultLen,
          tkOut,
          tkOutLen_p,
          pay_codes);
    // ...
}

Since the various offset and the length values are never checked, acct, AV and SignRes can be made to point to anywhere in memory (relatively to params[0].memref.buffer), which results in OOB accesses in subsequent function calls. For example, later in initPayCodeHead (called from initForUnionSeedList), as seen previously.

Proof of concept code triggering this bug results in a read access on the user-controlled address 0x4eadeeef = 0x70003000 + 0xdeadbeef:

OOB Access in SendSetStatusCmd

There is an out of bounds access in the function SendSetStatusCmd called by the commands CmdWalletActivateCardByBiometricsId (ID #0x20023) and CmdWalletDisablePosTrade (ID #0x20008). As can be seen below, the third TEE_Param value is used as an index into seReaderHandleList. However, there is no bounds checking on this value, making it possible to specify an arbitrary value that results in an OOB access.

uint32_t CmdWalletDisablePosTrade(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // [...]
    ret = DisablePosTrade(aid, aid_len, rsp, &rsp_len, params[2].value.a);
    // [...]
}
uint32_t DisablePosTrade(void *aid, uint32_t aid_len, void *rsp, uint32_t *rsp_len_p, int nameIndex) {
    // [...]
    ret = SendSetStatusCmd_constprop_10(aid, aid_len, 0, rsp, rsp_len_p, &failed, nameIndex);
    // [...]
}
uint32_t SendSetStatusCmd(void *cmd, int cmd_len,
        int status_type, void *rsp, uint32_t *rsp_len_p, char *failed,
        int nameIndex)
{
    // [...]
    ret = TEE_SEReaderOpenSession(seReaderHandleList[4 * nameIndex], &se_sess_handle);
    // [...]
}

This vulnerability can be triggered using a proof of concept code that results in a crash at address 0x6d608e8 when the out of bounds value is accessed.

[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: 0x6d608e8, fault_code: 0x92000005
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7766 prefer-ca=7766
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <SendSetStatusCmd.constprop.10+0x1f0/0x54c>
[HM] <SendSetStatusCmd.constprop.10>+0x1e4/0x54c
[HM] Dump task states END
[HM] 

Param Buffer Overflow in CmdWalletGetCardByIndex

There is a param buffer overflow in the CmdWalletGetCardByIndex (ID #0x20010) function. In the success case, the cards stack variable is copied into the output param buffer params[2]. The size of the copy is 0x2304 bytes, but the function never checked if the output param buffer was big enough.

unsigned int CmdWalletGetCardByIndex(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    TEE_MemMove(params[2].memref.buffer, &cards, 0x2304);
    params[2].memref.size = 0x2304;
    // ...
}

A proof of concept code can be used to trigger this bug. It results in an access at an invalid address located after the param output buffer:

[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: 0x70006303, fault_code: 0x92000047
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7862 prefer-ca=7862
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TEE_MemMove+0x54/0x68>
[HM] <CmdWalletGetCardByIndex>+0xac/0x1d8
[HM] Dump task states END
[HM] 

Param Buffer Overreads in CmdWalletApplyEnableAndDisableCardToI2C

There are multiple pointers in the command handler CmdWalletApplyEnableAndDisableCardToI2C (ID #0x20022) which can be controlled arbitrarily and result in buffer overreads down the line.

In CmdWalletApplyEnableAndDisableCardToI2C, the user controls, among others, the following values:

  • aid_len at offset 0xc in inbuf
  • signValueLen at offset 0x10 in inbuf
  • signResultLen at offset 0x14 in inbuf
  • challengeLen at offset 0x18 in inbuf

Using these values, four pointers are computed:

  • aid = inbuf + 0x20
  • signValue = aid + aid_len + 1
  • signResult = signValue + signValueLen + 1
  • challenge = signResult + signResultLen + 1
uint32_t CmdWalletApplyEnableAndDisableCardToI2C(
        void *sessionContext, uint32_t paramTypes, TEE_Param params[4])
{
    inbufLen = params[0].memref.size;
    inbuf = params[0].memref.buffer;
    tokenLen = params[2].memref.size;
    token = params[2].memref.buffer;

    // Values taken directly from the input param buffer.
    server_type = inbuf[0];
    auth_type = inbuf[1];
    activate = inbuf[2];
    aid = (char *)inbuf + 0x20;
    aid_len = inbuf[3];
    signValue = aid + aid_len + 1; /* Arbitrary pointer */
    if (auth_type == 2 && activate == 1) {
        signValueLen = inbuf[4];
        signResult = signValue + signValueLen + 1; /* Arbitrary pointer */
        signResultLen = inbuf[5];
        challenge = signResult + signResultLen + 1;  /* Arbitrary pointer */
        challengeLen = inbuf[6];
    } else {
        // [...]
    }
    // [...]
    ret = checkValidforActivateCard(
              server_type,
              auth_type,
              activate,
              aid,
              aid_len,
              signValue,
              signValueLen,
              signResult,
              signResultLen,
              1,
              &valid);
    // [...]
    ret = encryptPinAndChallenge(challenge, challengeLen, hash, &hashLen);
    // [...]
}

Since the signValueLen, signResultLen and challengeLen values are never checked, signResult and challenge can be made to point to anywhere in memory (relatively to params[0].memref.buffer), which results in buffer overreads in subsequent function calls. For example, later in verifySignWithRSA (called by checkValidOfTransSrvReq and checkValidforActivateCard):

int checkValidforActivateCard_part_4(
        int server_type,
        int auth_type,
        int no_sign,
        void *aid,
        int aid_len,
        void *signValue,
        int signValue_len,
        void *signResult,
        int signResult_len,
        char should_identify,
        _BYTE *valid_p)
{
    // ...
    if (auth_type == 2) {
        // ...
        checkValidOfTransSrvReq(
            server_type,
            signValue,
            signValue_len,
            signResult,
            signResult_len,
            1);
        // ...
    }
    // ...
}
int checkValidOfTransSrvReq(
        int server_type,
        void *signValue,
        int signValueLen,
        void *signResult,
        unsigned int signResLen,
        char needDelRandom)
{
    // ...
    verifySignWithRSA(server_type, signValue, signValueLen, signResult, signResLen);
    // ...
}
int verifySignWithRSA(int server_type, void *signValue, int signValueLen, void *signRes, int signResLen) {
    // ...
    TA_RsaVerifyDigest(&key, signHash, 0x20, signRes, signResLen);
    // ...
}

This vulnerability can be triggered using a proof of concept code that results in a crash caused by a read access at address 0x70006000 when the out of bounds value is accessed.

[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: 0x70006000, fault_code: 0x92000007
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7908 prefer-ca=7908
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] Dump task states END
[HM] 

Param Buffer Overreads in CmdWalletActivateCardByBiometricsId

In the CmdWalletActivateCardByBiometricsId function, user-controlled values are extracted from the first param input buffer, in particular:

  • the aid of length aid_len = inbuf[3] and starting at inbuf + 0x20
  • the signValue of length signValueLen = inbuf[4] and starting at aid + aid_len + 1
  • the signResult of length signResultLen = inbuf[5] and starting at signValue + signValueLen + 1
unsigned int CmdWalletActivateCardByBiometricsId(
        void *sessionContext,
        uint32_t paramTypes,
        TEE_Param params[4])
{
    // ...
    inbuf = params[0].memref.buffer;
    inbufLen = params[0].memref.size;
    rsp = params[1].memref.buffer;
    rspLen = params[1].memref.size;
    token = params[2].memref.buffer;
    tokenLen = params[2].memref.size;
    // ...
    server_type = inbuf[0];
    auth_type = inbuf[1];
    activate = inbuf[2];
    aid = (char *)inbuf + 0x20;
    aid_len = inbuf[3];
    signValue = aid + aid_len + 1;
    signValueLen = inbuf[4];
    signResult = signValue + signValueLen + 1;
    signResultLen = inbuf[5];
    nameIndex = inbuf[7];
    // ...
    checkValidforActivateCard(
        server_type,
        auth_type,
        1,
        aid,
        aid_len,
        signValue,
        signValueLen,
        signResult,
        signResultLen,
        1,
        &valid);
    // ...
    SendSetStatusCmd_constprop_10(aid, aid_len, 1, rsp, &rspLen, &failed, nameIndex);
    // ...
}

Since the signValueLen and signResultLen values are never checked, signResult can be made to point to anywhere in memory (relatively to params[0].memref.buffer), which results in OOB accesses in subsequent function calls. For example, later in verifySignWithRSA (called by checkValidOfTransSrvReq and checkValidforActivateCard), as seen previously.

The proof of concept code to trigger this bug results in a crash caused by a read access at address 0x70007000 when the out of bounds value is accessed.

[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: 0x70007000, fault_code: 0x92000007
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7991 prefer-ca=7991
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] Dump task states END
[HM] 

Param Buffer Overreads in CmdWalletVerifySwipeCard

In the CmdWalletVerifySwipeCard function, user-controlled values are extracted from the first param input buffer, in particular:

  • the signValue of length signValueLen = inbuf[4] and starting at inbuf + 0x20
  • the signResult of length signResultLen = inbuf[5] and starting at signValue + signValueLen + 1
unsigned int CmdWalletVerifySwipeCard(void *sessionContext, uint32_t paramTypes, TEE_Param params[4]) {
    // ...
    inbuf = params[0].memref.buffer;
    inbufLen = params[0].memref.size;
    token = params[1].memref.buffer;
    tokenLen = params[1].memref.size;
    server_type = inbuf[0];
    auth_type = inbuf[1];
    signValue = (char *)inbuf + 0x20;
    signValueLen = inbuf[4];
    signResult = signValue + signValueLen + 1;
    signResultLen = inbuf[5];
    // ...
    checkValidforActivateCard_part_4(
          server_type,
          auth_type,
          1,
          should_identify,
          should_identify,
          signValue,
          signValueLen,
          signResult,
          signResultLen,
          should_identify,
          &valid);
    // ...
}

Since the signValueLen and signResultLen values are never checked, signResult can be made to point to anywhere in memory (relatively to params[0].memref.buffer), which results in OOB accesses in subsequent function calls. For example, later in verifySignWithRSA (called by checkValidOfTransSrvReq and checkValidforActivateCard), as seen previously.

The proof of concept code to trigger this bug results in a crash caused by a read access at address 0x70006000 when the out of bounds value is accessed.

[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: 0x70006000, fault_code: 0x92000007
[HM] 
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TA_HuaweiWallet] 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=7601 prefer-ca=7601
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] Dump task state

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
Heap Buffer Overflow in initPayCodeHead Critical N/A (*) Fixed
Stack Buffer Overflows in decodeCRSCert Critical N/A (*) Fixed
Stack Buffer Overflow in genOffPayCodeSeedParam Critical N/A (*) Fixed
Stack Buffer Overflow in GetCardALLByIndexV2 Critical N/A (*) Fixed
Incomplete Caller Verification Critical N/A (*) Fixed
OOB Access in SendSetStatusCmd Medium N/A (*) Fixed
OOB Accesses in CmdWalletVerifyPayCodeAuthInfo Medium N/A (*) Fixed
OOB Accesses in CmdWalletGetFinancePayCode Medium N/A (*) Fixed
OOB Accesses in CmdWalletGetTrafficPayCode Medium N/A (*) Fixed
OOB Accesses in CmdWalletSetPayCodeAuthInfo Medium N/A (*) Fixed
OOB Accesses in CmdWalletSavePayCodeSeed Medium N/A (*) Fixed
OOB Accesses in CmdWalletGenPayCodeSeedParam Medium N/A (*) Fixed
Heap Buffer Overread in transferV1ToV2Paycode Medium N/A (*) Fixed
Heap Buffer Overread in isSamePayCodeSeed Medium N/A (*) Fixed
Param Buffer Overreads in CmdWalletVerifySwipeCard Low N/A Fixed
Param Buffer Overreads in CmdWalletActivateCardByBiometricsId Low N/A Fixed
Param Buffer Overreads in CmdWalletApplyEnableAndDisableCardToI2C Low N/A Fixed
Param Buffer Overflow in CmdWalletGetCardByIndex 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. 31, 2022 - A vulnerability report is sent to Huawei PSIRT.
  • Apr. 07, 2022 - Huawei PSIRT acknowledges the vulnerability report.
  • From Nov. 30, 2022 to Jul, 19 2023 - We exchange regularly about the release of our advisories.