In this advisory we are disclosing a vulnerability in the Huawei log device that allows any unprivileged process to learn the value of randomized kernel pointers. The vulnerability can be used to defeat KASLR mitigation.

Huawei kernels are shipped with custom log devices (/dev/hwlog_dubai, /dev/hwlog_exception and /dev/hwlog_jank) that facilitate better system diagnostics through a series of ioctl calls. One of these diagnostics module is referred to as memcheck, and it provides detailed statistics about the system memory usage. These statistics can disclose randomized kernel pointers to an attacker, enabling them to defeat the KASLR security mitigation. Due to an access control configuration error, these ioctls are exposed to untrusted and isolated application contexts, as a result any unprivileged process can exploit this vulnerability.

Vulnerability Details

The memcheck utility, exposed by the hwlog devices, provides a series of different ways to query and trace system memory usage. The LOGGER_MEMCHECK_STACK_READ ioctl call can be used to retrieve this data from user space. There are various memory regions and allocators that can be monitored this way and they all return vastly different data. The MTYPE_KERN_VMALLOC query returns a list of symbolized kernel addresses that are calling the different vmalloc allocator functions. This list might contain raw kernel text addresses if the print function fails to symbolize them.

  • logger_ioctl()
  • memcheck_ioctl()
  • process_stack_read()
  • memcheck_stack_read()
  • memcheck_kernel_stack_read()
  • memcheck_get_stack_items()
  • memcheck_get_vmalloc_stack()

The data is generated by the memcheck_get_vmalloc_stack() function (see the call chain above). This function calls hisi_page_trace_read() to retrieve the list of call sites to vmalloc. Then it proceeds to format the trace into a c string in memcheck_stack_to_str().

static size_t memcheck_stack_to_str(struct hisi_stack_info *list, size_t num,
            char *buf, size_t total, size_t used)
{
  int i;
  int tmp;

  sort(list, num, sizeof(*list), stack_cmp, NULL);

  tmp = snprintf(buf + used, total - used, "PC  hits\n");
  if (tmp < 0)
    goto buf_done;
  used += min((size_t) tmp, total - used - 1);
  for (i = 0; i < num; i++) {
    tmp = snprintf(buf + used, total - used, "%pS %zu\n",
      list[i].caller, list[i].ref);
    if (tmp < 0)
      goto buf_done;
    used += min((size_t)tmp, total - used - 1);
    if (used >= (total - 1))
      break;
  }
buf_done:
  return used;
}

The kernel addresses are formatted with %pS format string that attempts to symbolize the address, however if it fails to find an appropriate symbol, it falls back to printing the raw pointer value. For regular kernel addresses the symbol lookup should not fail, however dynamically loaded modules (for example the balong modem driver) might contain functions without kallsyms values. This is exactly what happens on Huawei devices, below is the output of the command, containing the leak of kernel virtual addresses belonging to the modem kernel driver.

[...]
VM_USERMAP
PC      hits
[...]
maa_cpu_ipipe_init+0x184/0x594 [balong_modem] 1
scm_init_cnf_src_buff+0x104/0x1a0 [balong_modem] 1
0xffffff800dd01e0c 1
bsp_espe_set_ipfproperty+0x15c/0x334 [balong_modem] 1
ipf_probe+0x6e4/0xed0 [balong_modem] 1
0xffffff800dd01ec8 1
ipf_probe+0x628/0xed0 [balong_modem] 1
ipf_probe+0x540/0xed0 [balong_modem] 1
[...]

The leak can be used by an attacker to learn the kernel virtual address of the modem driver, including its text and data sections. This is an effective way to bypass KASLR, since the text section contains gadgets to complete ROP chains and the data section contains further pointers into kernel memory, including the virtual address of the kernel text section.

Access Control

The Linux DAC permission allows any process to open the hwlog devices for writing, which is sufficient for issuing ioctl commands. Each device has a different Selinux label (see the listing below), however their ioctl interface is identical. The domain type attribute, which includes a series of unprivileged context such as isolated and untrusted app, is allowed to open, write and issue ioctls on these devices.

ls -lZ /dev/hwlog_*
crw-rw--w- 1 root   system u:object_r:dubai_log_device:s0  /dev/hwlog_dubai
crw-rw--w- 1 root   log    u:object_r:exception_device:s0  /dev/hwlog_exception
crw-rw--w- 1 root   system u:object_r:jank_device:s0       /dev/hwlog_jank

Selinux extended permissions are used to create a whitelist of ioctls on the /dev/hwlog_exception and /dev/hwlog_jank devices, that only allow a small set of ioctls to be called by the domain type attribute. However, these extended permissions are not applied for the dubai_log_device context, consequently, through the /dev/hwlog_dubai device every hwlog ioctl command is exposed to all the unprivileged processes.

Affected Devices (Verified)

  • Kirin 990

    • Huawei Mate 30 Pro (LIO)
    • Huawei P40 Pro (ELS)
    • Huawei P40 (ANA)
  • Kirin 9000/9000E

    • Huawei Mate40 Pro (NOH) EMUI
    • Huawei MatePad Pro 12 (WGH) HarmonyOS

Fix

Huawei OTA images, released after April 2022, contain the fix for the vulnerability.

Timeline

  • 2022.01.21. Bug reported to Huawei PSIRT via Harmony Bug Bounty Program
  • 2022.03.23. Huawei confirms vulnerability, assigns CVE and Medium severity
  • 2022.04.01. Huawei releases security bulletin