impalabs space base graphics
Huawei TrustZone TEE_Weaver Vulnerability
This advisory contains information about the following vulnerabilities:

Huawei's TEE_Weaver TA conforms to the GlobalPlatform TEE Internal Core API. As such, it receives commands from a client application located in the normal world. These commands are handled by the TA_InvokeCommandEntryPoint function.

This TA implements 5 commands: GetConfig, InterfaceWrite, InterfaceRead, GetStatus and CollectRetryErr.

Information Leaks

There are 3 calls to SLog in InterfaceWrite and InterfaceRead that lead to an information leak in logcat. These primitives can be used to leak stack addresses, buffer addresses, and consequently the ASLR slide of the trusted application.

The first call is in the Read function:

int Read(TEE_Param *params, unsigned int index) {
    // [...]
    int responseLen;
    char response[0x108];
    // [...]
    buffer1 = params[1].memref.buffer;
    length1 = params[1].memref.length;
    // [...]
    SLog("%s: DoRead(%u, %p, %u, %u, %u)#%u\n", "[Trace]", slotId, buffer1, length1, response, responseLen, retry);
    // [...]
}

The parameters of this command are a "value" (at index 0) and two "memrefs" (at index 1 and 2). The log string will leak the address at which the first buffer is mapped, as well as the address of a buffer allocated on the stack:

[TEE_WEAVER-1] [Trace]: DoRead(63, 0x70003000, 16, 59722052, 261)#0

The second call is in the CheckReadResp function, which in our case has been inlined in the DoRead function:

int DoRead(int slotId, int buffer1, int length1, unsigned __int8 *response, int *responseLen) {
    // [...]
    SLog("%s: %s,WEAVER_RSP: response value %p, len=%u, rsp[0]=%02x, sw1,sw2=%02x %02x\n",
         "[Trace]", "CheckReadResp", response, *responseLen, *response, 0x90, 0);
    // [...]
}

The log string will leak the address of the same buffer allocated on the stack:

[TEE_WEAVER-1] [Trace]: CheckReadResp,WEAVER_RSP: response value 0x38f4944, len=23, rsp[0]=00, sw1,sw2=90 00

The third call is in the IsResponseError function:

int IsResponseError(unsigned __int8 *response, unsigned int responseLen) {
    // [...]
    SLog("%s: %s,response value %p, len=%u, %02x %02x\n",
         "[Trace]", "IsResponseError", response, responseLen, *response, response[1]);
    // [...]
}

unsigned int DoWrite(unsigned int slotId, int buffer1, int length1, int buffer2, int length2) {
    // [...]
    unsigned int responseLen;
    // [...]
    unsigned __int8 response[0x130];
    // [...]
    IsResponseError(response, responseLen);
    // [...]
}

The log string will leak the address of another stack allocated buffer:

[TEE_WEAVER-1] [Trace]: IsResponseError,response value 0x38f4904, len=2, 90 00

Missing Input Parameters Check in InterfaceRead

The parameter types validation for the InterfaceRead command is not done properly, which allows writing user-controlled data to arbitrary addresses inside the trusted application's address space.

int cmd_iface_read_check(int paramTypes, TEE_Param *params) {
    // [...]
    if ((paramTypes & 0xF) == TEE_PARAM_TYPE_VALUE_INPUT
            && ((paramTypes >> 4) & 0xF) == TEE_PARAM_TYPE_MEMREF_INPUT) {
        if (!IsValidSlotId(params[0].value.a)
                && params[1].memref.buffer
                && params[1].memref.length <= 0x10) {
            return 0;
        } else {
            SLog("%s: Check interface read input parameter has problem.\n", "[Error]");
            return 2;
        }
    } else {
        SLog("%s: Bad expected parameter types: 0x%X.\n", "[Error]", paramTypes);
        return 3;
    }
}

The validation function does not check the type of the third parameter, which is supposed to be a TEE_PARAM_TYPE_MEMREF_OUTPUT. That means that we can give it a TEE_PARAM_TYPE_VALUE_INPUT instead.

typedef union {
    struct {
        void *buffer;
        uint32_t size;
    } memref;
    struct {
        uint32_t a;
        uint32_t b;
    } value;
} TEE_Param;

Since the fields memref.buffer and memref.size overlap with value.a and value.b respectively, instead of writing to an output buffer, we can make the TA write to the user-controlled address specified by value.a. The output buffer is used by the InterfaceRead command in the ReturnReadResult function:

int ReturnReadResult(TEE_Param *params, unsigned __int8 *response, int responseLen) {
    // [...]
    buffer2 = params[2].memref.buffer; // overlapping with params[2].value.a
    length2 = params[2].memref.length; // overlapping with params[2].value.b
    // [...]
    memcpy_s(buffer2, length2, &response[1], 0x10);
    // [...]
}

Triggering the type confusion will result in a write of 0x10 bytes (coming from the Secure Element's response) to the address of our choice. We also need to make sure that b is greater than 0x10 otherwise memcpy_s will return early.

This is the crash report we get when specifying an invalid write address (for example 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: 0xdeadbeef, fault_code: 0x92000045
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TEE_WEAVER] 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=5855 prefer-ca=5855
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <memcpy_s>+0x60/0x6c
[HM] invalid fp. backtrace abort
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=39 exit_status=130

Exploitation

Our Device Setup

The device we have developed an exploit for is a P40 Pro running the firmware update ELS-LGRP4-OVS_11.0.0.196. The trustlet binary MD5 checksum is as follows:

HWELS:/ # md5sum /vendor/bin/42abc5f0-2d2e-4c3d-8c3f-3499783ca973.sec
b1500b3c77250dc1c1e2e9b829b0de5b  /vendor/bin/42abc5f0-2d2e-4c3d-8c3f-3499783ca973.sec

Huawei's TEE OS iTrustee implements a whitelist mechanism that only allows specific client applications (native binaries or APKs) to talk to a trusted application.

In our case, the TEE_Weaver TA can only be called by 3 native binaries:

  • /vendor/bin/hw/vendor.huawei.hardware.hwsecurity-service (uid 1000);
  • /vendor/bin/atcmdserver (uid 0);
  • /vendor/bin/hw/vendor.huawei.hardware.hwfactoryinterface@1.1-service (uid 1000).

The authentication mechanism is implemented in 3 parts:
- the teecd daemon, that implements the TEE Client API, checks which native binary/APK is talking to it and sends that information to the kernel driver;
- the kernel driver ensures that it is talking to teecd, and forwards the information it received to the TEE OS;
- the TEE OS verifies that the client application is in the TA's whitelist.

Since we did not want to bother with injecting code in one of these binaries, we chose to circumvent the authentication by patching the kernel driver to add the ability to impersonate any native binary/APK.

Leaking The ASLR Slide

To leak the base address of the trustlet, we used the log string in the CheckReadResp command. We can trigger this code path by sending a write command to set the value of the weaver token in an unused slot (here slot #63), and then sending a read command of the same slot to read it back.

All that is left to do is parse the output of the logcat command. We execute it, filtering out messages not coming from teeos-TEE_WEAVER, and then look for the response value string.

Writing/Reading Trustlet Memory

Triggering the arbitrary write is done by sending a read command. But the value written to the user-controlled address is the weaver token. We can get control of it by first sending a write command to set its value. To not interfere with the system, we again target an unused slot, slot 63.

We can create an arbitrary read from our arbitrary write by disabling the parameter types checking of the write command. This is done by overwriting the function pointer in the commands array.

cmd_t cmds[] = {
    {0, cmd_get_config_init,  cmd_get_config_check,  cmd_get_config_proc},
    {1, cmd_iface_write_init, cmd_iface_write_check, cmd_iface_write_proc},
    {2, cmd_iface_read_init,  cmd_iface_read_check,  cmd_iface_read_proc},
    {3, cmd_get_status_init,  cmd_get_status_check,  cmd_get_status_proc},
    {8, cmd_collect_err_init, cmd_collect_err_check, cmd_collect_err_proc},
};

The pointer to cmd_iface_write_check is replaced by a pointer to cmd_collect_err_check. This command does not do any verification:

int cmd_collect_error_check() {
    return 0;
}

Now if we use a "value" instead of a "memref" for the parameter the weaver token is read from, the trustlet will copy data from a user-controlled address. To retrieve the data that was copied, we can send a read command on the same slot.

In the exploit, we use the arbitrary read primitive we just created to dump the trustlet memory:

adb wait-for-device shell su root sh -c "/data/local/tmp/tee_weaver"
Base address: 03c1a000
TA memory dump:
0x03c1a000: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............
0x03c1a010: 03 00 28 00 01 00 00 00 74 0f 00 00 34 00 00 00 ..(.....t...4...
0x03c1a020: 80 91 00 00 00 02 00 05 34 00 20 00 05 00 28 00 ........4. ...(.
0x03c1a030: 10 00 0f 00 01 00 00 00 00 00 00 00 00 00 00 00 ................
0x03c1a040: 00 00 00 00 91 7f 00 00 91 7f 00 00 05 00 00 00 ................
...

Getting Code Execution

While it is not detailed in the exploit above, it is possible to get code execution by replacing a function pointer/overwriting a return address, and chaining ROP gadgets. There is plenty of gadgets to choose from as the shared libraries (libc_shared_a32.so, libtee_shared_a32.so, libvendor_shared_a32.so, etc.) are mapped in the trustlet's address space.

For example, this could be used to make arbitrary syscalls and potentially further escalate privileges.

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
Missing Input Parameters Check in InterfaceRead Critical CVE-2021-40022 January 2022

Timeline

  • Oct. 04, 2021 - A vulnerability report is sent to Huawei PSIRT.
  • Oct. 04, 2021 - An erratum to the report, correcting the trusted application hash, is sent to Huawei PSIRT.
  • Oct. 25, 2021 - Huawei PSIRT acknowledges the vulnerability report.
  • Jan. 01, 2022 - Huawei PSIRT states that these issues were fixed in the January 2022 update.
  • From Nov. 30, 2022 to Jul, 19 2023 - We exchange regularly about the release of our advisories.