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
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.)