- 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]
, theindex
is checked to ensure that it is positive. - At
[2]
, theindex
is multiplied by 0x2300 to calculate the read offset into the file. An integer overflow can happen here. - At
[3]
, theindex+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 thanamount
, the total number of cards, some calculations are performed. - At
[4]
, the number of cards to returncount
is calculated by subtracting toamount
theindex
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 theindex
by 0x230. An integer overflow can happen here. - At
[6]
, abuffer
ofsize
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]
, thebuffer
is copied (all of itssize
bytes) into thecards
parameter, triggering a stack buffer overflow ifsize
is bigger than 0x2304 (ascard
is 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_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 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:
acct
at offset 0x14 inbuffer
acctLen
at offset 0x18 inbuffer
aidOff
at offset 0x1c inbuffer
aidLen
at offset 0x20 inbuffer
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 lengthbuffer->acctLen
and starting atbuffer->acctOff
acct
of lengthbuffer->acctLen
and starting atbuffer->acctOff
signText
of lengthbuffer->signTextLen
and starting atbuffer->signTextOff
signResult
of lengthbuffer->signResultLen
and starting atbuffer->signResultOff
aid
of lengthbuffer->aidLen
and starting atbuffer->aidOff
tw
of lengthbuffer->twLen
and starting atbuffer->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 lengthbuffer->acctLen
and starting atbuffer->acctOff
signResult
of lengthbuffer->signResultLen
and starting atbuffer->signResultOff
tw
of lengthbuffer->twLen
and starting atbuffer->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 lengthbuffer->acctLen
and starting atbuffer->acctOff
tw
of lengthbuffer->twLen
and starting atbuffer->twOff
aid
of lengthbuffer->aidLen
and starting atbuffer->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 lengthbuffer->acctLen
and starting atbuffer->acctOff
tw
of lengthbuffer->twLen
and starting atbuffer->twOff
aid
of lengthbuffer->aidLen
and starting atbuffer->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 lengthbuffer->acctLen
and starting atbuffer->acctOff
AV
of lengthbuffer->AVLen
and starting atbuffer->AVOff
SignRes
of lengthbuffer->SignResLen
and starting atbuffer->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 ininbuf
signValueLen
at offset 0x10 ininbuf
signResultLen
at offset 0x14 ininbuf
challengeLen
at offset 0x18 ininbuf
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 lengthaid_len = inbuf[3]
and starting atinbuf + 0x20
- the
signValue
of lengthsignValueLen = inbuf[4]
and starting ataid + aid_len + 1
- the
signResult
of 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
signValue
of lengthsignValueLen = inbuf[4]
and starting atinbuf + 0x20
- the
signResult
of 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