Search code examples
c++macosiokitkernel-extensionxnu

Prevent IOKit driver down in "kextunload" unless some clients remains connected


I've got IOKit base Driver/Deamons project with relationship of one to many (meaning multiple clients attached to single driver).

The core driver is an IOKit object derived from IOService and is also the provider of driver clients derived from IOUserClient. Those are the agents of user-space clients (one per user-space client)

My goal was to prevent driver from unload in case of kextunload command with root privileges, So that only when the last client disconnect (process terminate or call IOServiceClose), the driver will be closed automatically (without the need to do kextunload manually)

In order to check what IOKit commands i need to disable/postphone, I've wrapped the basid IOService callbacks with the relevant print message and observed the logs in a scenario of running kextunload where 2 clients are connected:

kernel: (driver) virtual bool com_osxkernel_driver::terminate(IOOptionBits)
kernel: (driver) virtual bool com_osxkernel_driverClient::terminate(IOOptionBits)
kernel: (driver) virtual bool com_osxkernel_driverClient::terminate(IOOptionBits)
kernel: (driver) virtual void com_osxkernel_driverClient::stop(IOService *)
kernel: (driver) virtual void com_osxkernel_driverClient::stop(IOService *)
kernel: (driver) virtual void com_osxkernel_driver::stop(IOService *)

--

It seems that even if I take reference from each IOUserClient on its provider, and releasing those reference on IOUserClient::clientClose , the kextunload command still succeed.

The only way I've found to achieve my goal, is in postponing the ::terminate command and call it explicitly from ::clientClose.

So i removed the call to IOService::terminate and instead called it from IOUserClient::clientClose(). Here the relevant code :

IOReturn com_osxkernel_driverClient::clientClose() 
{
    myProvider->release();
    return super::terminate(kIOServiceSynchronous) ? kIOReturnSuccess : kIOReturnError;
}


bool com_osxkernel_driverClient::terminate(IOOptionBits options) {
    os_log_info(g_logger,"%s", __PRETTY_FUNCTION__);
    return true;
}

I'd like to know if there are any, less hacky ways of preventing kextunload from succeeding and controlling the timing of driver unload.

thanks


Solution

  • If you look at the kextunload source code, you'll see it calls down into IOCatalogueTerminate(). In the kernel, this eventually makes its way into the IOCatalogue::_terminateDrivers() function. (IOCatalogue.cpp in the xnu sources) There you'll see that the following is run for every IOKit class instance belonging to the kext being unloaded:

    if ( !service->terminate(kIOServiceRequired|kIOServiceSynchronous) ) 
    {
        ret = kIOReturnUnsupported;
        break;
    }
    

    This indicates that the only way to stop your kext's IORegistry entries is indeed by returning false from terminate().

    Whether hard-blocking it like this is a good idea is of course another question entirely. I would recommend you only do this if unloading would endanger the stability of the operating system. Consider that kextunload requires root privileges, so in most cases it would be preferable to simply handle the termination of services and user clients cleanly in your userspace code.

    If you do return false from terminate(), you will also need to be careful about not blocking termination for legitimate reasons. (For example if it's a device driver, the nub your driver matched, e.g. IOUSBInterface may try to terminate() your driver instance if the device is hot-unplugged.)