Summary

The NPU device’s kernel driver implements a set of ioctl handlers one of which uses unsanitized user data as an index into a function pointer array. The user provided values can exceed the boundaries of the legitimate array and might cause user controlled values to be called as function pointers. A malicious actor can use this vulnerability to hijack the control flow of the kernel and call arbitrary functions with controlled parameters.

The function pointer callsite is protected by clang’s CFI, which reduces the number of function that can be called through this primitive.

The mmap handler is exposed through the /dev/davinci0 character device. Due to the applied selinux policy, access to this device is restricted to the hiaiserver system process. Because of these limitations a practical attack would need to target the hiaiserver first.

The vulnerability is exposed through the devdrv_ioctl_load_stream_buff function. This handler is defined in the drivers/hisi/npu/device/core/npu_ioctl_services.c source file. This function reads input from the user, retrieves a series of structures based on the provided id-s and eventually calls devdrv_format_hwts_sqe. The vulnerability is within this function as it uses an arbitrary, 16 bit, user-provided value to index a function pointer array and call the retrieved function.

// ts_sq_addr is data read from the user
int devdrv_format_hwts_sqe(void *hwts_sq_addr, void *ts_sq_addr, u64 ts_sq_len)
{
  u64 ts_sqe_num = ts_sq_len / sizeof(devdrv_ts_task_t);
  format_func func = NULL;
  u8 *hwts_sq_base = hwts_sq_addr;
  u8 *hwts_sqe = NULL;
  u64 i;
  int ret;
  devdrv_ts_task_t *ts_sqe = NULL;

  //...
  for (i = 0; i < ts_sqe_num; i++) {
    // 1. This is just an offset into the user provided data
    ts_sqe = (devdrv_ts_task_t *)TS_SQE_ENTRY(ts_sq_addr, i);
    hwts_sqe = HWTS_SQE_ENTRY(hwts_sq_base, i);

    // 2. ts_sqe->type is 16 bit unsigned value
    func = formate_hwts_sqe_map[ts_sqe->type];
    if (func == NULL) {
      NPU_DRV_ERR("invalid task:%d, type:%d\n", ts_sqe->task_id, ts_sqe->type);
      func = format_ph_sqe;
    }

    // 3. the function is executed
    ret = func((void *)hwts_sqe, ts_sqe);
    if (ret != 0)
      NPU_DRV_ERR("formate_hwts_sqe failed, ret:%d, task:%d, type:%d\n",
        ts_sqe->task_id, ts_sqe->type, ret);
  }

The formate_hwts_sqe_map array holds 68 function pointers, the type argument can index 65535. Normally, this function should not be reachable on the tested device as it relies on the hwts feature which is disabled in the device tree. However, the check for this feature is missing in devdrv_ioctl_load_stream_buff. Most other places that require hwts employ the following check:

if (DEVDRV_PLAT_GET_FEAUTRE_SWITCH(plat_info, DEVDRV_FEATURE_HWTS) == 1)}

Due to this oversight the devdrv_ioctl_load_stream_buff function operates on uninitialized data. The data however, resides in the shared memory that can be mapped and controlled by user space (see this advisory for details). If hwts is not enabled an attacker can craft the necessary structures to pass validation and reach the vulnerable code. If the feature is enabled no such action is required the vulnerable code should be reachable by design.

The function pointer array is located in the .data section of the kernel. The 16 bit arbitrary offset can address most of the .data and .bss sections, including areas that hold user controlled data. This allows an attacker to execute arbitrary functions in the kernel context, that is permitted by the CFI. Furthermore, when the function pointer is executed the first argument points into the shared memory and the second argument points into the user provided buffer. The content of both of these areas can be controlled by an attacker to hold arbitrary values.

Affected Devices (Verified)

  • Kirin 990
    • Huawei Mate 30 Pro (LIO)
    • Huawei P40 Pro (ELS)
    • Huawei P40 (ANA)

Fix

Huawei OTA images, released after February 2021, contain the fix for the vulnerability.

Timeline

  • 2020.10.30. Bug reported to Huawei PSIRT
  • 2020.11.25. Huawei PSIRT confirms vulnerability, confirms fix plans
  • 2021.01.31. OTA distribution of the fix, mitigating the vulnerability, starts
  • 2021.06.09. Huawei assigns CVEs
  • 2021.06.30. Huawei releases security bulletin