- HWPSIRT-2021-49134 Stack Buffer Overflow in TA_GetPayload
- HWPSIRT-2021-68415 Heap Buffer Overflows and Stack Buffer Overreads in TA_DecryptSKWithCBC and TA_DecryptSKWithGCM
- HWPSIRT-2021-53459 Heap Buffer Overflow in TA_Gen_Sysintegrity_Jws
- HWPSIRT-2021-45148 Param Buffer Overread in TA_GetPayload
- HWPSIRT-2021-18937 Param Buffer Overread in TA_GetSysintegritySignStr
- HWPSIRT-2021-61962 Param Buffer Overread in TA_DecryptKEK
- HWPSIRT-2021-22378 Param Buffer Overread in hkdf_expand
- HWPSIRT-2021-18804 Limited Out of Bounds Accesses in CMD_TSS_GET_PKI_CERT and CmdVerifySignature
Param Buffer Overread in TA_GetPayload
¶
There is a buffer overread in TA_GetPayload
called by the command CMD_TSS_GET_ROOT_STATUS
(ID #0x40018).
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
switch (commandID) {
// [...]
case 0x40018:
uint32_t payload_p = 0;
uint32_t payload_len = 0;
// Retrieves the header from the TEE_Param input buffer
TA_GetPayload(params[1].memref.buffer, &payload_p, &payload_len);
// [...]
break;
}
// [...]
}
The function reads the length result_len
from the second TEE_Param
input buffer inbuf
at offset 0xA22
. This user-controlled value is then used to allocate the result
buffer into which result_len
bytes are copied from the input buffer. If result_len
is larger than the size of inbuf
, it will result in an arbitrary buffer overread.
int TA_GetPayload(uint8_t *inbuf, char **payload_p, uint32_t *result_p) {
// ...
result_len = *(uint32_t *)(inbuf + 0xA22);
result = tss_mallocObject(result_len);
if (result_len > 2)
memcpy_s(result, result_len - 2, inbuf + 0x50F, result_len - 2);
// ...
}
The proof of concept code demonstrates that triggering the input buffer overread results in a bad read memory access at address 0x70007000
:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 2300000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x70007000, fault_code: 0x92000007
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[Huawei_TSS_TA] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7216 prefer-ca=7216
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] sp > fpDump task states END
[HM]
[HM] [TRACE][1212]pid=44 exit_status=130
Stack Buffer Overflow in TA_GetPayload
¶
There is a buffer overflow in the function TA_GetPayload
called by the command CMD_TSS_GET_ROOT_STATUS
(ID #0x40018).
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
switch (commandID) {
// [...]
case 0x40018:
uint32_t payload_p = 0;
uint32_t payload_len = 0;
// Retrieves the header from the TEE_Param input buffer
TA_GetPayload(params[1].memref.buffer, &payload_p, &payload_len);
// [...]
break;
}
// [...]
}
This function generates a JSON object using information parsed from a TEE_Param
input buffer. One of the user-provided values is digests_count
. TA_GetPayload
then uses this value to fill the array digests_array
, of size 10, with pointers to the digests embedded inside the TEE_Param
input buffer inbuf
:
int TA_GetPayload(uint8_t *inbuf, char **payload_p, uint32_t *result_p) {
// ...
uint8_t *digests_array[10];
// ...
digests_count = *(uint32_t *)(inbuf + 0xC);
for (i = 0; i < digests_count; ++i)
digests_array[i] = &inbuf[0x32 * i + 0x31A];
apkCertificateDigestSha256_str = json_format_array("apkCertificateDigestSha256",
digests_array, digests_count);
// ...
}
However, since we can control digests_count
, it is possible to specify a number of digests such that we start overflowing values on the stack, outside of the digests_array
, and replace them with pointers to user-controlled data.
digests_array[i] = input + 0x31A + 0x32 * i;
By triggering a stack buffer overflow big enough to overwrite the saved registers {R4-R11,LR}
, we end up with pointers to memory inside the TEE_Param
input buffer being put in these registers when exiting TA_GetPayload
.
A first proof of concept demonstrates that by overwriting the LR
register we get an instruction abort on an address inside the TEE_Param
input buffer:
[HM] ESR_EL1: 8200000f, ELR_EL1: 70004928, FAR is not valid
[HM] Huawei_TSS_TA vm fault prefetch abort: 70004928
[HM] fault: 8200000f tcb cref 1e00000028
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] [ERROR][2496]process 1e00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x70004928, fault_code: 0x8200000f
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[Huawei_TSS_TA] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7305 prefer-ca=7305
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=44 exit_status=130
A second proof of concept overwrites the saved registers up to R11
, the frame pointer. When exiting TA_GetPayload
, the frame pointer will point to user-controlled memory, thus any value loaded from the frame is now user-controlled. In it, we make payload
point to 0x41414141, resulting in a crash when this pointer is accessed by strlen
:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1e00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x41414141, fault_code: 0x92000006
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[Huawei_TSS_TA] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7588 prefer-ca=7588
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <strlen+0x8/0x74>
[HM] <TA_InvokeCommandEntryPoint>+0x1900/0x1e1c
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=46 exit_status=130
A third proof of concept overwrites the frame pointer and the payload
, but makes it point to user-controlled memory instead of 0x41414141. We make this memory look like a legitimate heap-allocated buffer, so that TEE_Free
can be called on this pointer. We use the classic "heap unlinking" technique to trigger an arbitrary write to address 0x41414141+8:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1e00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x41414149, fault_code: 0x92000046
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[Huawei_TSS_TA] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7620 prefer-ca=7620
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] invalid fp. backtrace abort
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=50 exit_status=130
Param Buffer Overread in TA_GetSysintegritySignStr
¶
There is a buffer overread in TA_GetSysintegritySignStr
called by the command CMD_TSS_GET_SYSINTEGRITY_JWS
(ID #0x40020).
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
switch (commandID) {
// [...]
case 0x40020:
sig_str = 0;
sig_str_len = 0;
decrpypted_sk_len = 0;
TA_GetSysintegritySignStr(
params[1].memref.buffer, &sig_str, &sig_str_len);
// [...]
break;
}
// [...]
}
The function reads the length result_len
from the second TEE_Param
input buffer inbuf
at offset 0xB00
. This user-controlled value is then used to allocate the result
buffer into which result_len
bytes are copied from the input buffer. If result_len
is larger than the size of inbuf
, it will result in an arbitrary buffer overread.
uint32_t TA_GetSysintegritySignStr(
uint8_t *inbuf, uint8_t **sig_str_p, uint32_t *sig_str_len_p)
{
// [...]
result_len = *(uint32_t *)(inbuf + 0xB00);
result = tss_mallocObject(result_len);
if (result_len > 2)
memcpy_s(result, result_len - 2, inbuf + 0x5ED, result_len - 2);
// [...]
}
A proof of concept demonstrates that triggering the input buffer overread results in a bad read memory access at address 0x70007000
:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 2300000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x70007000, fault_code: 0x92000007
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[Huawei_TSS_TA] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7593 prefer-ca=7593
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] sp > fpDump task states END
[HM]
[HM] [TRACE][1212]pid=51 exit_status=130
Param Buffer Overread in TA_DecryptKEK
¶
There is a buffer overread in TA_DecryptKEK
called by the command CMD_TSS_GET_SYSINTEGRITY_JWS
(ID #0x40020).
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
switch (commandID) {
// [...]
case 0x40020:
// Call to TA_GetSysintegritySignStr
decrypted_sk_len = 0;
decrypted_sk = tss_mallocObject(0x400);
TA_DecryptSK(
params[0].memref.buffer, decrypted_sk, &decrypted_sk_len);
// [...]
break;
}
// [...]
}
This command passes the first TEE_Param
input buffer to the function TA_DecryptSK
before passing it to TA_DecryptKEK
.
TEE_Result TA_DecryptSK(uint8_t *inbuf, uint8_t *key, int *key_len_p) {
// [...]
kek_buf_len = 0;
kek_buf = tss_mallocObject(0x400);
TA_DecryptKEK(inbuf, kek_buf, &kek_buf_len);
// [...]
}
TA_DecryptKEK
then decodes a base64-encoded string from the input buffer at offset 0 using the function b64_decode_ex
.
TEE_Result TA_DecryptKEK(
uint8_t *inbuf, uint8_t *outbuf, uint32_t *outbuf_len_p)
{
// [...]
uint32_t b64_encoded_len = *(uint32_t *)(inbuf + 0x400);
uint32_t b64_decoded_len = 0;
b64_decode_ex(inbuf, b64_encoded_len, &b64_decoded_len);
// [...]
}
b64_decode_ex
takes as parameters:
- an input buffer containing a base64-encoded string;
- the size of the encoded string;
- a pointer to an integer that will contain the size of the decoded string.
In our case, the size of the encoded string b64_encoded_len
is user-controlled and is read from the input buffer at offset 0x400
. However, the function never checks if b64_encoded_len
smaller than the size of the input buffer, resulting in a buffer overread if the user-provided value is large enough.
A proof of concept demonstrates that triggering the input buffer overread results in a bad read memory access at address 0x70007000
:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 2300000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x70007000, fault_code: 0x92000007
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[Huawei_TSS_TA] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7638 prefer-ca=7638
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <b64_decode_ex+0x74/0x294>
[HM] <memset_s>+0x28/0x38
[HM] <TA_DecryptKEK>+0x58/0x148
[HM] <TA_DecryptSK>+0x6c/0x2f4
[HM] <TA_InvokeCommandEntryPoint>+0x1cb0/0x1e1c
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=56 exit_status=130
Heap Buffer Overflows and Stack Buffer Overreads in TA_DecryptSKWithCBC
and TA_DecryptSKWithGCM
¶
There is a heap buffer overflow and a stack buffer overread in the function TA_DecryptSKWithCBC
.
It is called by the command CMD_TSS_GET_SYSINTEGRITY_JWS
(ID #0x40020).
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
switch (commandID) {
// [...]
case 0x40020:
// Call to TA_GetSysintegritySignStr
decrypted_sk_len = 0;
decrypted_sk = tss_mallocObject(0x400);
TA_DecryptSK(
params[0].memref.buffer, decrypted_sk, &decrypted_sk_len);
// [...]
break;
}
// [...]
}
TA_DecryptSKWithCBC
is called by the function TA_DecryptSK
.
TEE_Result TA_DecryptSK(uint8_t *inbuf, uint8_t *key, uint32_t *key_len_p) {
// ...
aes_cbc_args_t args;
// ...
kek_buf_len = 0;
kek_buf = tss_mallocObject(0x400);
TA_DecryptKEK(inbuf, kek_buf, &kek_buf_len);
object = TSS_ImportKey(kek_buf, kek_buf_len);
// ...
if (*(uint32_t *)(inbuf + 0xA58) == 0) {
*key_len_p = *(uint32_t *)(inbuf + 0x880);
args.dest = key;
args.dest_len_p = key_len_p;
// ...fill the args structure...
memcpy(&args, inbuf + 0x48C, 0x4C4);
TA_DecryptSKWithCBC(args);
}
// ...
}
TA_DecryptSKWithCBC
is used to decrypt the secret key using AES-CBC. It is given its arguments via the stack, using a structure we have called aes_cbc_args_t
:
- the source buffer is a copy into the stack of data in the TEE_Param input buffer (user-controlled)
- the source length is also a copy into the stack of a value in the TEE_Param input buffer (user-controlled)
- the destination buffer
decrypted_sk
of size 0x400 is allocated on the heap inTA_DecryptSK
's caller - the destination length is unspecified (
decrypted_sk_len
is set to 0 inTA_DecryptSK
's caller)
TEE_Result TA_DecryptSKWithCBC(aes_cbc_args_t args) {
// ...
TSS_AES_DES_Enc_Dec(
args.key, args.iv, args.iv_len,
TEE_ALG_AES_CBC_PKCS5, 1,
args.src, args.src_len,
args.dest, args.dest_len_p);
// ...
}
The source length will also be used as the destination length in the AES-CBC operations. If we specify a source length, and thus a destination length, bigger than 0x400, we will overflow the heap-allocated buffer. If we specify an even bigger source length, we will also overread the stack buffer.
Similarly, taking the path that leads to TA_DecryptSKWithGCM
can also result in a heap buffer overflow and a stack buffer overread.
TEE_Result TA_DecryptSK(uint8_t *inbuf, uint8_t *key, uint32_t *key_len_p) {
// ...
aes_cbc_args_t args;
// ...
kek_buf_len = 0;
kek_buf = tss_mallocObject(0x400);
TA_DecryptKEK(inbuf, kek_buf, &kek_buf_len);
object = TSS_ImportKey(kek_buf, kek_buf_len);
// ...
if (*(uint32_t *)(inbuf + 0xA58) == 1) {
*key_len_p = *(uint32_t *)(inbuf + 0x880);
args.dest = key;
args.dest_len_p = key_len_p;
// ...fill the args structure...
memcpy(&args, inbuf + 0x48C, 0x5CC);
TA_DecryptSKWithGCM(args);
}
// ...
}
A first proof of concept code can be used to trigger the heap buffer overflow. It uses a source length of 0x480, resulting in a corruption detected when TEE_Free
is called later:
[Huawei_TSS_TA-1] hmstss-TA [Trace] TA_DecryptSKWithCBC: start TA_DecryptSKWithCBC
[Huawei_TSS_TA-1] hmstss-TA [Trace] TSS_AES_DES_Enc_Dec: begin to TSS_AES_DES_Enc_Dec
[Huawei_TSS_TA-1] hmstss-TA [Trace] TSS_AES_DES_Enc_Dec: begin to TEE_AllocateOperation
[Huawei_TSS_TA-1] hmstss-TA [Trace] TSS_AES_DES_Enc_Dec: begin to TEE_SetOperationKey
[Huawei_TSS_TA-1] hmstss-TA [Trace] TSS_AES_DES_Enc_Dec: begin to cipher
[Huawei_TSS_TA-1] [error] 458:Cipher dofinal failed, ret=-3
[Huawei_TSS_TA-1] hmstss-TA [Error] TSS_AES_DES_Enc_Dec: TEE_CipherDofinal, fail ret=ffff0005, srclen=480,dst_len=7d000
[Huawei_TSS_TA-1] hmstss-TA [Error] TA_DecryptSKWithCBC: TA decrypt sk with kek failed: 0xFFFD0019
[Huawei_TSS_TA-1] hmstss-TA [Error] TA_DecryptSK: Decrypt sk with kek failed: ret = fffd0019
[Huawei_TSS_TA-1] hmstss-TA [Trace] TA_DecryptSK: free tmpEncryptKey
[HM] ERROR: free: double free
[Huawei_TSS_TA-1] hmstss-TA [Error] TA_InvokeCommandEntryPoint: TA_GetSysintegritySignStr decrypt sk failed
[HM] ERROR: free: corrupted footer
It is possible to overflow with controlled data (the decrypted secret key), allowing the use of classic heap exploitation techniques, such as "heap unlinking" that results in an arbitrary write.
A second proof of concept code can be used to trigger the stack buffer overread. It uses a source length of 0x8000, resulting in a data abort when the end of the stack is reached:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1e00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0xd04000, fault_code: 0x92000007
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[Huawei_TSS_TA] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7954 prefer-ca=7954
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] sp > fpDump task states END
[HM]
[HM] [TRACE][1212]pid=50 exit_status=130
Heap Buffer Overflow in TA_Gen_Sysintegrity_Jws
¶
There is a heap buffer overflow in the function TA_Gen_Sysintegrity_Jws
called by the command CMD_TSS_GET_SYSINTEGRITY_JWS
(ID #0x40020).
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
switch (commandID) {
// [...]
case 0x40020:
// [...]
sig_str = 0;
sig_str_len = 0;
TA_GetSysintegritySignStr(
params[1].memref.buffer,
&sig_str, &sig_str_len);
// [...]
decrypted_sk_len = 0;
decrypted_sk = tss_mallocObject(0x400);
TA_DecryptSK(
params[0].memref.buffer,
decrypted_sk, &decrypted_sk_len);
// [...]
outbuf_len = 0;
outbuf = tss_mallocObject(0x2800);
TA_Gen_Sysintegrity_Jws(
sig_str, sig_str_len,
decrypted_sk, decrypted_sk_len,
outbuf, &outbuf_len);
// [...]
}
// [...]
}
This command starts by calling TA_GetSysintegritySignStr
which generates a string sig_str
from information retrieved in the second TEE_Param
input buffer. Because some elements of this string are user-controlled (but not all), its length sig_str_len
can be arbitrarily large.
int TA_GetSysintegritySignStr(uint8_t *inbuf, char **sig_str_p, size_t *sig_str_len_p) {
// ...
alg_str = json_format_key_string("alg", inbuf + 0xB43);
// ...
payload_len = /* ... + */ strlen(alg_str);
payload = tss_mallocObject(payload_len);
// ...
strncat_s(payload, payload_len, alg_str, strlen(alg_str));
// ...
payload_b64enc = b64url_encode(payload, payload_len);
// ...
sig_str_len = /* ... + */ strlen(payload_b64enc);
sig_str = tss_mallocObject(sig_str_len);
// ...
strncat_s(sig_str, sig_str_len, payload_b64enc, strlen(payload_b64enc));
// ...
*sig_str_len_p = strlen(sig_str);
*sig_str_p = sig_str;
// ...
}
This string and its length are then passed to TA_Gen_Sysintegrity_Jws
(in the parameters sig
and sig_len
respectively). sig
is then copied into outbuf
, a heap-allocated buffer of size 0x2800. Since the size of the sig
buffer is arbitrary, and because *outbuf_len_p
is computed using sig_len
, it is possible to overflow outbuf
by specifying a sig_len
that will result in a *outbuf_len_p
larger than 0x2800 bytes.
int TA_Gen_Sysintegrity_Jws(
uint8_t *sig,
uint32_t sig_len,
uint8_t *sk,
uint32_t sk_len,
uint8_t *outbuf,
uint32_t *outbuf_len_p)
{
// ...
mac = tss_mallocObject(0x80);
CmdHMAC(sk, sk_len, TEE_ALG_HMAC_SHA256, sig, sig_len, mac, &mac_len);
tmpbuf = b64url_encode(mac, mac_len);
// ...
*outbuf_len_p = sig_len + 2 + strlen(tmp_buf);
strncpy_s(outbuf, *outbuf_len_p, sig, sig_len);
// ...
}
A proof of concept code triggers this heap buffer overflow. In the code, a size of around 0xA000 is used, resulting in a crash when reaching the heap end address. Nevertheless, it is possible to use smaller sizes, allowing the use of classic heap exploitation techniques, such as the "heap unlinking" that results in an arbitrary write.
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 2200000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0xf79000, fault_code: 0x92000047
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[Huawei_TSS_TA] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7841 prefer-ca=7841
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <strncpy_s>+0xf4/0x104
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=75 exit_status=130
Param Buffer Overread in hkdf_expand
¶
There is a buffer overread in the function hkdf_expand
called by the command CMD_TSS_SYMMETRIC_CRYPTO
(ID #0x40004).
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
switch (commandID) {
// [...]
case 0x40020:
// [...]
ibuf0_addr = params[0].memref.buffer;
outbuf_len = params[1].memref.size + 0x20;
outbuf = tss_mallocObject(outbuf_len);
CmdEncryptDecryptData(
*(uint32_t *)(ibuf0_addr + 0x00), /* alg */
*(uint32_t *)(ibuf0_addr + 0x04), /* mode */
*(uint32_t *)(ibuf0_addr + 0x08), /* okm_len */
ibuf0_addr + 0x30, /* key_path */
*(uint32_t *)(ibuf0_addr + 0x10), /* key_path_len */
ibuf0_addr + 0x62, /* hmac_key */
*(uint32_t *)(ibuf0_addr + 0x18), /* hmac_key_len */
ibuf0_addr + 0x122, /* info */
*(uint32_t *)(ibuf0_addr + 0x20), /* info_len */
ibuf0_addr + 0x1E2, /* iv */
*(uint32_t *)(ibuf0_addr + 0x28), /* iv_len */
params[1].memref.buffer, /* inbuf */
params[1].memref.size, /* inbuf_len */
outbuf, /* outbuf */
&outbuf_len); /* outbuf_len */
// [...]
break;
}
// [...]
}
int CmdEncryptDecryptData(...) {
// ...
TSS_DeriveWorkKey(rootKeyPath, hmac_key, hmac_key_len, info, info_len, okm, okm_len);
// ...
}
int TSS_DeriveWorkKey(...) {
// ...
hkdf(hmac_key, hmac_key_len, rootKey, rootKey_len, info, info_len, okm, okm_len);
// ...
}
int hkdf(...) {
// ...
hkdf_expand(prk, prk_len, info, info_len, okm, okm_len);
// ...
}
Most of the lengths, okm_len
, key_path_len
, hmac_key_len
, info_len
, iv_len
, are extracted from the TEE_Param
input buffer and are unchecked. In particular, by specifying a info_len
value greater than the size of the TEE_Param
input buffer, a buffer overread will happen in the hkdf_expand
function, where the info
data is copied into a heap-allocated buffer.
int hkdf_expand(char *prk, int prk_len, char *info, int info_len, char *okm, int okm_len) {
// ...
tmpbuf_len = info_len + 1;
tmpbuf = TEE_Malloc(tmpbuf_len, 0);
tmpbuf_off = 0;
while (1) {
memcpy_s(&tmpbuf[tmpbuf_off], tmpbuf_len - tmpbuf_off, info, info_len);
memcpy_s(&tmpbuf[tmpbuf_off + info_len], tmpbuf_len - tmpbuf_off - info_len, &c, 1);
// ...compute MAC and copy it to okm...
tmpbuf_off = macLen;
TEE_Free(tmpbuf);
// ...exit if expansion is finished...
tmpbuf_len = info_len + tmpbuf_off + 1;
tmpbuf = TEE_Malloc(tmpbuf_len, 0);
memcpy_s(tmpbuf, info_len + tmpbuf_off + 1, mac, tmpbuf_off);
}
// ...
}
A proof of concept code can be used to trigger this buffer overread:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1e00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x70007000, fault_code: 0x92000007
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[Huawei_TSS_TA] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7934 prefer-ca=7934
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] sp > fpDump task states END
[HM]
[HM] [TRACE][1212]pid=57 exit_status=130
Limited Out of Bounds Accesses in CMD_TSS_GET_PKI_CERT
and CmdVerifySignature
¶
There is a limited out of bounds access in the command CMD_TSS_GET_PKI_CERT
(ID #0x40017).
/*** in the .data section ***/
char *CERT_FACTOR[1] = { /* ... */ };
/****************************/
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4])
{
// [...]
switch ( commandID )
{
// [...]
case 0x40017:
cert_idx = params->value.a;
if (cert_idx + 1 <= 1)
{
// [...]
factor = CERT_FACTOR[cert_idx];
factor_strlen = strlen(factor);
convert_hex_to_byte(factor, outbuf, factor_strlen);
// [...]
}
// [...]
break;
}
// [...]
}
A TEE_Param
input value cert_idx
is used as an index in the global variable CERT_FACTOR
. However there is an issue in the condition that makes sure cert_idx
is bound to a given range: cert_idx + 1 <= 1
. This condition allows cert_idx
to be equal to -1
, which would result in an out of bounds access to the first integer before CERT_FACTOR
.
/*** in the .data section ***/
struct public_key {
char *mod;
char *exp;
} KEY_FACTOR[1] = { /* ... */ };
/****************************/
TEE_Result CmdVerifySignature(
uint32_t type,
uint32_t alg,
uint32_t index,
char *inbuf,
uint32_t inbuf_len,
char *signature,
uint32_t signature_len)
{
// ...
if (index + 1 <= 1) {
// ...
convert_hex_to_byte(KEY_FACTOR[index].mod, pub_mod, 0x200);
convert_hex_to_byte(KEY_FACTOR[index].exp, pub_exp, 6);
// ...
}
// ...
}
There is a similar out of bounds access in the CmdVerifySignature
function called by the command with ID #0x40008.
A proof of concept demonstrates that triggering the out of bounds access in CMD_TSS_GET_PKI_CERT
results in a bad read memory access at address 0x5b32ffc
:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1e00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x5b32ffc, fault_code: 0x92000007
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[Huawei_TSS_TA] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=8980 prefer-ca=8980
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <TA_InvokeCommandEntryPoint+0x1750/0x1e1c>
[HM] <memset_s>+0x28/0x38
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
Affected Devices¶
We have verified that the vulnerabilities impacted the following device(s):
- Kirin 990: P40 Pro (ELS)
Please note that other models might have been affected.
Patch¶
Name | Severity | CVE | Patch |
---|---|---|---|
Stack Buffer Overflow in TA_GetPayload |
High | N/A (*) | Fixed |
Heap Buffer Overflows and Stack Buffer Overreads in TA_DecryptSKWithCBC and TA_DecryptSKWithGCM |
High | N/A (*) | Fixed |
Heap Buffer Overflow in TA_Gen_Sysintegrity_Jws |
High | N/A (*) | Fixed |
Param Buffer Overread in TA_GetPayload |
Low | N/A | Fixed |
Param Buffer Overread in TA_GetSysintegritySignStr |
Low | N/A | Fixed |
Param Buffer Overread in TA_DecryptKEK |
Low | N/A | Fixed |
Param Buffer Overread in hkdf_expand |
Low | N/A | Fixed |
Limited Out of Bounds Accesses in CMD_TSS_GET_PKI_CERT and CmdVerifySignature |
Low | Fixed |
(*) Huawei's statement about the medium, high, and critical severity APP vulnerabilities:
They are resolved through the AppGallery upgrade. Generally, CVE numbers are not assigned for this type [of] vulnerabilities.
Timeline¶
- Dec. 21, 2021 - A vulnerability report is sent to Huawei PSIRT.
- Jan. 12, 2022 - Huawei PSIRT acknowledges the vulnerability report.
- From Nov. 30, 2022 to Jul, 19 2023 - We exchange regularly about the release of our advisories.
Copyright © Impalabs 2021-2023