We are looking for a solution (API, Frameworks) that would allow us to block any type of external device, including storage devices, HIDs, network adapters, and Bluetooth devices according with dynamic rules that comes from management server . This feature is important for endpoint security solutions vendors, and it can be implemented on other platforms and older versions of macOS using the IOKit framework and kexts. I have found one solution that can control the usage only of "storage" devices with the EndpointSecurity framework in conjunction with the DiskArbitration framework. This involves monitoring the MOUNT and OPEN events for /dev/disk files, checking for devices as they appear, and ejecting them if they need to be blocked.. Also, I have found the ES_EVENT_TYPE_AUTH_IOKIT_OPEN event in EndpointSecurity.framework, but it doesn't seem to be useful, at least not for my purposes, because ES doesn't provide AUTH events for some system daemons, such as configd (it only provides NOTIFY events). Furthermore, there are other ways to communicate with devices and their drivers apart from IOKit. DriverKit.framework does not provide the necessary functionality either, as it requires specific entitlements that are only available to certain vendors and devices. Therefore, it cannot be used to create universal drivers for all devices, which should be blocked. Any advice would be greatly appreciated!
SO, I actually want to start by saying something about this point:
it can be implemented on ... older versions of macOS using the IOKit framework and kexts
The implication here is that this was something IOKit actively supported in some "reasonable" way and that just isn't true and never has been. It's true in the broadest sense that you COULD use IOKit to block "anything", however, actually implementing a solution in IOKit that did something as broad as:
...would allow us to block any type of external device, including storage devices, HIDs, network adapters, and Bluetooth devices
...would NOT have gone "well". At a surface level, it's easy to look at IOKit's API and structure* and "see" how it can be used to block activity. It's MUCH harder to turn that into functioning product that actually works in useful way and that's only dealing with the cases that could be blocked from the kernel**.
*For example, when viewed as whole, the IORegistry looks like an elegantly structured tree which would appear allow you to insert an additional driver nearly anywhere in that tree's structure. However, because of how the provider/nub architecture works, more than 1/2 of the object in the registry were directly created by their provider (through "new") and thus cannot be replaced by different driver. I know of at least one driver family which presented in the registry as a layered architecture of ~6 drivers, but was in fact a monolithic driver where none of those layers could be matched against.
*Most of bluetooth's implementation is outside the kernel and always has been, so unless you want to completely disable bluetooth, being in the kernel won't help.
I want to be clear on this point because, in reality, I don't think the basic issues or approaches to this area have actually changed all that much. The system simply doesn't have a standardized "device blocking architecture" and never has, so this needs to be approached as a series of issues/uses case, each of which will need to be independently addressed.
I have found one solution that can control the usage only of "storage" devices with the EndpointSecurity framework in conjunction with the DiskArbitration framework. This involves monitoring the MOUNT and OPEN events for /dev/disk files, checking for devices as they appear, and ejecting them if they need to be blocked..
Clarifying the details of this:
-DiskArbitration (and it's supporting daemon) are what actually auto mount disks (so they appear on the desktop when you plug in a device). It has it's own API for managing disk appearances/mounting/unmounting, including block auto mounts from occurring.
-I would use DiskArbitration as my "primary" (meaning, the thing that will block most mounts) mount blocker, as the API gives you lots of information about what's occurring, plenty of time to make your "decisions", and is integrated with the higher level system which minimizes the disruption.
-It is possible to bypass DiskArbitration and directly mount a volume (through the command line or syscall). EndpointSecurity can block these through it's own auth calls and also acts as the "final" protection level for all mounts.
-As a separate issue, it's also possible to directly open /dev/disk# & /dev/rdisk# nodes, reading and writing directly to the media. These operations can be blocked by EndpointSecurity the same way any other file I/O would be.
Also, I have found the ES_EVENT_TYPE_AUTH_IOKIT_OPEN event in EndpointSecurity.framework, but it doesn't seem to be useful,
I agree that it's not particularly useful. I'd strongly encourage you to file an enhancement request asking for ES_EVENT_TYPE_AUTH_IOKIT_OPEN to include the io_object_t that's being targeted. That would make it FAR more useful.
However, this is NOT an issue:
at least not for my purposes, because ES doesn't provide AUTH events for some system daemons, such as configd (it only provides NOTIFY events).
If you take a look at the values returned by the mute APIs (es_muted_processes_events, es_muted_paths_events, etc) in a newly created client, you'll see that a we mute a number of sources by "default" but which your client can unmute if it chooses.
HOWEVER... you do so at your own risk. Everything we've muted was muted because we've determined that block or even delaying the actions of that daemon is very likely to cause serious issue in the system, up to and INCLUDING panic'ing the kernel or otherwise disabling the entire system.
Expanding on that point, one thing I want to emphasize here is that an EndpointSecurity is much closer to a kernel component than it is a standard user process. The fact that an EndpointSecurity client is capable of panic'ing the kernel is the natural endpoint of that reality.
Furthermore, there are other ways to communicate with devices and their drivers apart from IOKit.
I'm not sure what you're thinking of here, but there aren't THAT many routes out of the kernel. Of the top of my head, I can really only think of three "paths" that data moves out of IOKit:
-
As "files" (/dev nodes) out of the IOKit and into the VFS layer.
-
Through IOKit property modifications and/or user clients (both of which go through IOServiceOpen). Note that DEXTs are basically a special variations of this architecture, not a different architecture. I'm not sure how DEXT activity is exposed to the ES layer, but DEXTs are best understood as "part" of IOKit, not as an unrelated component.
-
Through the network layer. Strictly speaking, I think this is actually done through a mix of 1 & 2, but I haven't actually looked at it in great detail.
In my experience, the issue in most cases here isn't that IOKit isn't the communication channel, it's that:
-
Blocking the communication channel would cause broader issue than you want (this is the problem with bluetooth).
-
Your EndpointSecurity client can't differentiate between what it should block and what it should allow (this is where expanding ES_EVENT_TYPE_AUTH_IOKIT_OPEN would help).
DriverKit.framework does not provide the necessary functionality either, as it requires specific entitlements that are only available to certain vendors and devices. Therefore, it cannot be used to create universal drivers for all devices, which should be blocked.
Yes, but I think this needs to be understood and formalizing "reality", not blocking/limiting a truly viable path. As I talked about above, I don't think IOKit has EVER provided a truly viable mechanism for "universal" device blockage.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware