Zimperium Blog

NDAY-2017-0106: Elevation of Privilege in NVIDIA nvhost-vic driver - Zimperium

Written by zLabs | May 25, 2017
Share this blog

  • zNID: NDAY-2017-0106
  • CVE: CVE-2016-2434
  • Type: Elevation of Privileges
  • Platform: Android 6.0.1
  • Device type: Nexus 9
  • Zimperium protection: Detected the exploit without an update. Zimperium partners and customers do not need to take any action to detect this exploit on all affected devices.
  • Android bulletin: https://source.android.com/security/bulletin/2016-05-01.html
  • Public release date: 25th of May, 2017
  • Credit: Jianqiang Zhao (@jianqiangzhao) and pjf (weibo.com/jfpan) of IceSword Lab, Qihoo 360

Download Exploit (password zimperium_ndays)

Vulnerability Details

Vulnerable file drivers/video/tegra/host/bus_client.c
The function nvhost_init_error_notifier does not validate args->offset which is from userland, so it can lead to arbitrary kernel write.

Exploitation

  1. mmap a memory in userland, and set `args->offset` a number to let `va + args->offset` overflow to this range of memory in userland. So we can calculate the value of `va`.
  2. set `va + args->offset` to the address of `ptmx_fops`, so we can set the value of `ptmx_cdev->ops` from `0xffffffc0010aa420` to `0x00000000010aa420`. `0x00000000010aa420` is a user space address. So we can set `ptmx_cdev->ops` to a fake ops which can be controlled in userland.
  3. set `ptmx_cdev->ops->ioctl` to a rop read or write kernel gadget which can read a 8 bytes from arbitrary kernel address or write 4 bytes to arbitrary kernel address.
  4. when we get the capability of reading and writing arbitrary kernel address leading to elevation of privileges to the context of the kernel.

static int nvhost_init_error_notifier(struct nvhost_channel *ch,
struct nvhost_set_error_notifier *args) {
void *va;
struct dma_buf *dmabuf;
if (!args->mem) {
dev_err(&ch->dev->dev, "invalid memory handle\n");
return -EINVAL;
}
dmabuf = dma_buf_get(args->mem);
if (ch->error_notifier_ref)
nvhost_free_error_notifiers(ch);
if (IS_ERR(dmabuf)) {
dev_err(&ch->dev->dev, "Invalid handle: %d\n", args->mem);
return -EINVAL;
}
/* map handle */
va = dma_buf_vmap(dmabuf);
if (!va) {
dma_buf_put(dmabuf);
dev_err(&ch->dev->dev, "Cannot map notifier handle\n");
return -ENOMEM;
}
/* set channel notifiers pointer */
ch->error_notifier_ref = dmabuf;
ch->error_notifier = va + args->offset;
ch->error_notifier_va = va;
memset(ch->error_notifier, 0, sizeof(struct nvhost_notification));
return 0;
}