How to pass user setting to Driver Extension (MacOS)? - macos

I am writing a driverkit extension whose goal is to block some categories of USB devices, such as flash drives. The driver should block (match to) any device of the relevant device classes, except those, which are whitelisted (based on their vendor and product ID). The whitelist can be set dynamically by user of the application.
The question is, how to pass these data to the driver as reading from a file or something like Windows registry is not available in the DriverKit. The tricky part is that the driver requires the whitelist data before the device is matched.
From what I understood, rejection of device is possible by returning an error from Start() method and returning from it prematurely. I got an idea to send the data while the driver is running this function, however this is not possible as the communication via IOUserClass is not available until the Start method returns.
Is this somehow doable?

As far as I'm aware, communicating with user space apps from the initial Start() method is not possible from DriverKit extensions. As you say, IOUserClients are the mechanism to use for user space communication, and those aren't available until the service is started and registered. You can have your driver match IOResources/IOUserResources so it is always loaded, but each matched service starts up an independed process of your dext, and I'm not aware of a way to directly communicate between these instances.
If I understand you correctly, you're trying to block other drivers from acquiring the device. I don't think the solution you have in mind will help you with this. If you return success from Start(), your dext will drive the device. If you return failure, no driver is loaded for the device, because matching has already concluded. So other drivers would never get a chance anyway, regardless of whether the device is on your allow-list or deny-list.
It's new in DriverKit 21 (i.e. macOS Monterey), and I've not had a chance to try it yet, but there is an API for reading files, OSMappedFile. I would imagine that the DriverKit sandbox will have something to say about which files a dext can open, but this seems like an avenue worth exploring whether you can open configuration files this way.
Note that none of this will help you during early boot, as your dext will never be considered for matching at that time. And you may not be able to get required entitlements from Apple to build a dext which matches USB device classes rather than specific product/vendor ID patterns. (Apologies for repeating myself, but other users may come across this answer and not be aware of this issue.)

Related

macOS NetworkingDriverKit - How can I register multiple ethernet interfaces?

I have tried extending IOUserNetworkEthernet and calling RegisterEthernetInterface(). This works perfectly for one ethernet interface, though the driver crashes when RegisterEthernetInterface is called a second time (doesn't return an error code). I have tried registering with separate queues.
Another approach was extending IOUserClient instead, and calling IOService::Create to create child IOUserNetworkEthernet instances. Everything about this approach works (the children appear within ioreg). However, once I call RegisterEthernetInterface on just one of the children, macOS crashes.
How would I go about creating a dext with multiple ethernet interfaces? Have I been approaching it the right way?
Appreciate any help.
I haven't yet implemented an ethernet dext myself, but based on my experience with using DriverKit for other types of drivers and knowing its design goals, I have an idea what the solution might be.
First off, let's clarify: you're implementing either a virtual ethernet device, or you're building a driver for hardware that unites multiple independent ports in one physical device. In the former case (I'm guessing this is what you're doing based on your IOUserClient comment), your driver will be matching IOUserResources and you create the ethernet driver instance when you receive the corresponding message from your user space component.
Now, DriverKit design: DriverKit is built around a goal of keeping the driver instance for each device in its own separate process. Sort of circling back to the microkernel idea. Specifically, this means that multiple devices that use the same driver will each create an independent instance of that driver in its own process. This very likely means that NetworkingDriverKit was never designed to support more than one instance of IOUserNetworkEthernet, because they are seen as separate devices. Hence the crashes.
OK, what do we do about it? Use DriverKit the way it was intended. Put each virtual ethernet adapter in its own driver instance. This gets a bit tricky. I think this should work:
Your dext has a "control" instance. This is the thing that matches IOUserResources. It's a simple IOService class which listens for the signal (presumably IOUserClient) to create or destroy a virtual ethernet device. When creating a virtual ethernet device, you need that to run in its own driver instance though. (For an actual device with multiple ports, this would match the USB/PCI device nub, manage bus communication with the device, and enumerate the ports.)
Instead of creating an instance of a IOUserNetworkEthernet subclass, create an instance of a "nub" class and attach it to your central control class instance. Call RegisterService() in its startup code, so it's considered for IOKit matching.
Set up a second IOKit matching dictionary in your Info.plist, which matches your new "nub" object. Here, use your IOUserNetworkEthernet-derived class to "drive" the nub. Once the virtual ethernet device is ready to use, call RegisterEthernetInterface().
2 extra things to note:
The virtual device will run in a separate process from the control object, so they can only communicate via DriverKit inter-process calls, i.e. user clients. Hopefully, they don't really need to communicate much though, and the control client can pass all the information required via properties on the nub. If you're implementing support for multi-port hardware, you probably won't be able to avoid this part though.
Your user space component (app, daemon) will need to open a new communication channel with the virtual device, so you'll probably need to implement user client support there too.
I'm not sure how you'd go about shutting down an individual virtual ethernet device, you could try calling Terminate() on either it or the nub it's matched on and see what happens.

DirectInput, multiple joysticks, multiple users

My situation is that I have multiple joystick input devices, from the same manufacturer. The idea is that you set them up once, and then any user could log in and play without having to set them up again. The problem is how to uniquely identify each stick so at runtime you associate each stick to the correct saved configuration. I'm using DirectInput since these are joysticks, not gamepads.
The problem is that despite what is claimed here as well as other MSDN pages, the InstanceGUID is only unique per user, not per machine! At least this is what I am seeing, which seems to be corroberated by one other poster here. Ideally I want a new user to be able to log in and just run with the existing setup, but I don't see any way to associate the correct joystick to the correct saved button mapping without a reliable GUID that doesn't change (and that isn't the same for both sticks, like the product GUID).
All these sticks are USB joysticks if that helps.
Maybe there's a way to reliably enumerate the sticks? But without understanding the underlying architecture of how the USB ports are scanned for Joysticks, I'm not sure if I can always assume the "first" stick will actually be the first for every user, or if I unplug a stick and plug it back in, even to the same port.
UPDATE: This article contains the claim that InstanceId is per computer, not per user.

Accessing Platform Device from Userpace

From a general standpoint, I am trying to figure out how to access a platform device from userspace. To be more specific, I have a EMIF controller on and SoC of which I have added to my device tree and I believe it is correctly bound to a pre-written EMIF platform device driver. Now I am trying to figure out how I can access this EMIF device from a userspace application. I have come accross a couple different topics that seem to have some connection to this issue but I cannot quite find out how they relate.
1) As I read it seems like most I/O is done through the use of device nodes which are created by mknod(), do I need to create a device node in order to access this device?
2) I have read a couple threads that talk about writting a Kernel module (Character?, Block?) that can interface with both userspace and the platform device driver, and use it as an intermediary.
3) I have read about the possibility of using mmap() to map the memory of my platform device into my virtual memory space. Is this possible?
4) It seems that when the EMIF driver is instantiated, it calls the probe() fucntion. What functions would a userpace application call in the driver?
It's not completely clear what you're needing to do (and I should caveat that I have no experience with EMIF or with "platform devices" specifically), but here's some overview to help you get started:
Yes, the usual way of providing access to a device is via a device node. Usually this access is provided by a character device driver unless there's some more specific way of providing it. Most of the time if an application is talking "directly" to your driver, it's a character device. Most other types of devices are used in interfacing with other kernel subsystems: for example, a block device is typically used to provide access from a file system driver (say) to an underlying disk drive; a network driver provides access to the network from the in-kernel TCP/IP stack, etc.
There are several char device methods or entry points that can be supported by your driver, but the most common are "read" (i.e. if a user-space program opens your device and does a read(2) from it), "write" (analogous for write(2)) and "ioctl" (often used for configuration/administrative tasks that don't fall naturally into either a read or write). Note that mknod(2) only creates the user-space side of the device. There needs to be a corresponding device driver in the kernel (the "major device number" given in the mknod call links the user-space node with the driver).
For actually creating the device node in the file system, this can be automated (i.e. the node will automatically show up in /dev) if you call the right kernel functions while setting up your device. There's a special daemon that gets notifications from the kernel and responds by executing the mknod(2) system call.
A kernel module is merely a dynamically loadable way of creating a driver or other kernel extension. It can create a character, block or network device (et al.), but then so can a statically linked module. There are some differences in capability mostly because not all kernel functions you might want to use are "exported" to (i.e. visible to) dynamically loaded modules.
It's possible to support mapping of the device memory into user virtual memory space. This would be implemented by yet another driver entry point (mmap). See struct file_operations for all the entry points a char driver can support.
This is pretty much up to you: it depends on what the application needs to be able to do. There are many drivers in the kernel that provide no direct function to user-space, only to other kernel code. As to "probe", there are many probe functions defined in various interfaces. In most cases, these are called by the kernel (or perhaps by a 'higher level "class" driver') to allow the specific driver to discover, identify and "claim" individual devices. They (probe functions) don't usually have anything directly to do with providing access from user-space but I might well be missing something in a particular interface.
You need to create a device node in order to access the device.
The probe function is called when the driver finds a matching device.
For information on platform device API, the following articles could be useful.
The platform device API
Platform devices and device trees

Who enforces dwShareMode on CreateFile? The OS or the Driver?

I have a Windows application that interacts with some hardware. A handle to the hardware is opened with CreateFile, and we control the hardware using DeviceIoControl.
I'm attempting to update an application which uses this hardware to open the hardware in an exclusive mode, so that other programs can't access the hardware at the same time (the hardware has mutable state that I can't have changed out from under me). I do this by passing 0 as the dwShareMode parameter to CreateFile. After making this change, I am still able to run two separate instances of my application. Both calls to CreateFile in both processes are successful. Neither returns INVALID_HANDLE_VALUE.
I believe one of several things is happening, and I'm asking for help narrowing the problem down.
I badly misunderstand the dwShareMode parameter
dwShareMode doesn't have any effect on DeviceIoControl - only ReadFile or WriteFile
The driver itself is somehow responsible for respecting the dwShareMode parameter and our driver is written badly. This, sadly, isn't totally unheard of.
Edit Option 2 is nonsense. dwShareMode should prevent the 2nd CreateFile from happening, DeviceIoControl has nothing to do with it. It must be option #1 or option #3
The Question:
Is the device driver responsible for looking at the dwShareMode parameter, and rejecting requests if someone has already opened a handle without sharing, or is the OS responsible?
If the device driver is responsible, then I'm going to assume #3 is happening. If the OS is responsible, then it must be #1.
Some additional Clues:
IRP_MJ_CREATE documentation suggests that the sharing mode does indeed get passed down to the device driver
I believe that sharing rules are only enforced on some devices. In many (most?) cases enforcing sharing rules on the device object itself (as opposed to on objects within the device namespace) would make no sense.
Therefore, it must be the responsibility of the device driver to enforce these rules in those rare cases where they are required. (Either that or the device driver sets a flag to instruct the operating system to do so, but there doesn't seem to be a flag of this sort.)
In the case of a volume device, for example, you can open the device with a sharing mode of 0 even though the volume is mounted. [The documentation for CreateFile says you must use FILE_SHARE_WRITE but this does not appear to be true.]
In order to gain exclusive access to the volume, you use the FSCTL_LOCK_VOLUME control code.
[That's a file system driver, so it might not be a typical case. But I don't think it makes a difference in this context.]
Serial port and LPT drivers would be an example of a device that should probably enforce sharing rules. I think there may be some applicable sample code, perhaps this would shed light on things?
Edited to add:
I've had a look through the Windows Research Kernel source (this is essentially the same as the Windows Server 2003 kernel) and:
1) The code that opens a device object (by sending IRP_MJ_CREATE to the driver) does not appear to make any attempt to enforce the sharing mode parameter, though it does check access permissions and enforces the Exclusive flag for the driver;
2) I've also searched the code for references to the structure member that holds the requested dwShareMode. As far as I can see it is written into the relevant structure by the internal function that implements CreateFile, and later passed to the device driver, but otherwise ignored.
So, my conclusion remains the same: enforcing the sharing mode rules, or providing an alternative mechanism if appropriate, is the responsibility of the device driver.
(The kernel does, however, provide functions such as IoCheckShareAccess to assist file system drivers in enforcing sharing rules.)
In cases where we open a COM port with :
CreateFile(devname,GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
0,
0);
It doesnt allow another application to open the same COM port until the previous handle is closed. I would suggest walking through serenum.sys to check if it has a role here.

Best way to communicate from KEXT to Daemon and block until result is returned from Daemon

In KEXT, I am listening for file close via vnode or file scope listener. For certain (very few) files, I need to send file path to my system daemon which does some processing (this has to happen in daemon) and returns the result back to KEXT. The file close call needs to be blocked until I get response from daemon. Based on result I need to some operation in close call and return close call successfully. There is lot of discussion on KEXT communication related topic on the forum. But they are not conclusive and appears be very old (year 2002 around). This requirement can be handled by FtlSendMessage(...) Win32 API. I am looking for equivalent of that on Mac
Here is what I have looked at and want to summarize my understanding:
Mach message: Provides very good way of bidirectional communication using sender and reply ports with queueing mechansim. However, the mach message APIs (e.g. mach_msg, mach_port_allocate, bootstrap_look_up) don't appear to be KPIs. The mach API mach_msg_send_from_kernel can be used, but that alone will not help in bidirectional communication. Is my understanding right?
IOUserClient: This appears be more to do with communicating from User space to KEXT and then having some callbacks from KEXT. I did not find a way to initiate communication from KEXT to daemon and then wait for result from daemon. Am I missing something?
Sockets: This could be last option since I would have to implement entire bidirectional communication channel from KEXT to Daemon.
ioctl/sysctl: I don't know much about them. From what I have read, its not recommended option especially for bidirectional communication
RPC-Mig: Again I don't know much about them. Looks complicated from what I have seen. Not sure if this is recommended way.
KUNCUserNotification: This appears to be just providing notification to the user from KEXT. It does not meet my requirement.
Supported platform is (10.5 onwards). So looking at the requirement, can someone suggest and provide some pointers on this topic?
Thanks in advance.
The pattern I've used for that process is to have the user-space process initiate a socket connection to the KEXT; the KEXT creates a new thread to handle messages over that socket and sleeps the thread. When the KEXT detects an event it needs to respond to, it wakes the messaging thread and uses the existing socket to send data to the daemon. On receiving a response, control is passed back to the requesting thread to decide whether to veto the operation.
I don't know of any single resource that will describe that whole pattern completely, but the relevant KPIs are discussed in Mac OS X Internals (which seems old, but the KPIs haven't changed much since it was written) and OS X and iOS Kernel Programming (which I was a tech reviewer on).
For what it's worth, autofs uses what I assume you mean by "RPC-Mig", so it's not too complicated (MIG is used to describe the RPC calls, and the stub code it generates handles calling the appropriate Mach-message sending and receiving code; there are special options to generate kernel-mode stubs).
However, it doesn't need to do any lookups, as automountd (the user-mode daemon to which the autofs kext sends messages) has a "host special port" assigned to it. Doing the lookups to find an arbitrary service would be harder.
If you want to use the socket established with ctl_register() on the KExt side, then beware: The communication from kext to user space (via ctl_enqueuedata()) works OK. However opposite direction is buggy on 10.5.x and 10.6.x.
After about 70.000 or 80.000 send() calls with SOCK_DGRAM in the PF_SYSTEM domain complete net stack breaks with disastrous consequences for complete system (hard turning off is the only way out). This has been fixed in 10.7.0. I workaround by using setsockopt() in our project for the direction from user space to kext as we only send very small data (just to allow/disallow some operation).

Resources