Search code examples
macoskernelstoragekernel-extension

Understanding buffering for Mac OS block devices and IOFilterScheme KEXTs


I am trying to understand how IOFilterScheme KEXTs work in order to eventually develop one myself. I have tried a few sample programs and have gotten basic encryption to work, for example by using this sample.

However, when I add printf() statements and look at the console logs, I am seeing some confusing behavior. Specifically, I almost never see read() calls coming in, except for a few processes when I configure things (like mount_hfs and fsck_hfs).

For example, if I write a new file on a Volume (from a mounted disk image) from some application (ex: vim), when I will see a corresponding write() in the console logs, with the proper PID of 'vim'. I see this when using other applications as well.

However, if I try to read that same file from another application (say, Sublime Text editor), the file opens fine, but I never see any corresponding read() entry in the console log.

While I can tell that the sample encryption is working by looking at the DMG file, I have two problems with the behavior I am seeing:

1) It is hard for me to understand what is going on in terms of reads and writes.

2) Eventually I would like to write a KEXT where the behavior differs depending on the application that is going the reading or writing. To do this, I would need an actual read() from each application that tries to access the file (at least the first time that app does).

After doing some research it seems that block devices on Mac OS have some sort of buffering, but I was not able to find too many details. Experimentally, I tried executing this line in the read() and write() calls, but it had no effect

super::IOStorage::synchronize(client, 0, 0);

If someone can tell me how I can gain more control over the buffering so I can see actual read() calls coming in, that would be great. If that is not possible, then I might have to write my encryption driver at a different level. However, an IOFilterScheme KEXT seems like (except for this point) it really fits my use case, so I am hoping I can make it work.


Solution

  • The caching mostly happens at the file level, in the Unified Buffer Cache (UBC). The file system itself may do any kind of caching, especially for metadata (internal tree structures, etc.). This is many layers above the IOKit, so you have no influence over any of this from an IOStorage driver.

    2) Eventually I would like to write a KEXT where the behavior differs depending on the application that is going the reading or writing. To do this, I would need an actual read() from each application that tries to access the file (at least the first time that app does).

    Trying to do this in a block I/O kext is probably a fools' errand. Modern file systems are complex beasts, so you can't expect any kind of 1:1 correspondence between file I/O and block I/O. If you want application/file level granularity, you'll need to work at the VFS layer, either through your own file system or depending on what exactly you're trying to do, using kauth. If you're not afraid of using APIs Apple has specifically said are not to be used (e.g. internal use/learning), you can also use the MAC framework kext APIs, which give you more control than kauth.