- HWPSIRT-2022-12799 Incomplete Caller Verification
- HWPSIRT-2022-38244 Stack Buffer Overflow in GetCardALLByIndexV2
- HWPSIRT-2022-13974 Stack Buffer Overflow in genOffPayCodeSeedParam
- HWPSIRT-2022-57851 Stack Buffer Overflows in decodeCRSCert
- HWPSIRT-2022-20808 Heap Buffer Overflow in initPayCodeHead
- HWPSIRT-2022-94156 Heap Buffer Overread in isSamePayCodeSeed
- HWPSIRT-2022-46681 Heap Buffer Overread in transferV1ToV2Paycode
- HWPSIRT-2022-67754 OOB Accesses in CmdWalletGenPayCodeSeedParam
- HWPSIRT-2022-31335 OOB Accesses in CmdWalletSavePayCodeSeed
- HWPSIRT-2022-39460 OOB Accesses in CmdWalletSetPayCodeAuthInfo
- HWPSIRT-2022-45266 OOB Accesses in CmdWalletGetTrafficPayCode
- HWPSIRT-2022-28524 OOB Accesses in CmdWalletGetFinancePayCode
- HWPSIRT-2022-82607 OOB Accesses in CmdWalletVerifyPayCodeAuthInfo
- HWPSIRT-2022-61804 OOB Access in SendSetStatusCmd
- HWPSIRT-2022-31800 Param Buffer Overflow in CmdWalletGetCardByIndex
- HWPSIRT-2022-70865 Param Buffer Overreads in CmdWalletApplyEnableAndDisableCardToI2C
- HWPSIRT-2022-85843 Param Buffer Overreads in CmdWalletActivateCardByBiometricsId
- HWPSIRT-2022-55550 Param Buffer Overreads in CmdWalletVerifySwipeCard
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], theindexis checked to ensure that it is positive.
- At [2], theindexis multiplied by 0x2300 to calculate the read offset into the file. An integer overflow can happen here.
- At [3], theindex+1is multiplied by 0x10 to calculate the number of the last card requested. An integer overflow can happen here. If this number is smaller thanamount, the total number of cards, some calculations are performed.
- At [4], the number of cards to returncountis calculated by subtracting toamounttheindexmultiplied 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 theindexby 0x230. An integer overflow can happen here.
- At [6], abufferofsizebytes 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], thebufferis copied (all of itssizebytes) into thecardsparameter, triggering a stack buffer overflow ifsizeis bigger than 0x2304 (ascardis a stack variable from the stack frame ofCmdWalletGetCardByIndex).
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 usingta_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_lengthand is unchecked
- the value pointer is used as source and the length as size in a TEE_MemMovecall where the destination is the stack bufferbuf
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:
- acctat offset 0x14 in- buffer
- acctLenat offset 0x18 in- buffer
- aidOffat offset 0x1c in- buffer
- aidLenat 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:
- acctof length- buffer->acctLenand starting at- buffer->acctOff
- acctof length- buffer->acctLenand starting at- buffer->acctOff
- signTextof length- buffer->signTextLenand starting at- buffer->signTextOff
- signResultof length- buffer->signResultLenand starting at- buffer->signResultOff
- aidof length- buffer->aidLenand starting at- buffer->aidOff
- twof length- buffer->twLenand 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:
- acctof length- buffer->acctLenand starting at- buffer->acctOff
- signResultof length- buffer->signResultLenand starting at- buffer->signResultOff
- twof length- buffer->twLenand 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:
- acctof length- buffer->acctLenand starting at- buffer->acctOff
- twof length- buffer->twLenand starting at- buffer->twOff
- aidof length- buffer->aidLenand 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:
- acctof length- buffer->acctLenand starting at- buffer->acctOff
- twof length- buffer->twLenand starting at- buffer->twOff
- aidof length- buffer->aidLenand 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:
- acctof length- buffer->acctLenand starting at- buffer->acctOff
- AVof length- buffer->AVLenand starting at- buffer->AVOff
- SignResof length- buffer->SignResLenand 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_lenat offset 0xc in- inbuf
- signValueLenat offset 0x10 in- inbuf
- signResultLenat offset 0x14 in- inbuf
- challengeLenat 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 aidof lengthaid_len = inbuf[3]and starting atinbuf + 0x20
- the signValueof lengthsignValueLen = inbuf[4]and starting ataid + aid_len + 1
- the signResultof lengthsignResultLen = inbuf[5]and starting atsignValue + 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 signValueof lengthsignValueLen = inbuf[4]and starting atinbuf + 0x20
- the signResultof lengthsignResultLen = inbuf[5]and starting atsignValue + 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.
Copyright © Impalabs 2021-2023