Skip to content

Quick look at user-mode file systems on macOS Ventura

Let’s start by plugging in a USB stick (using the FAT, ExFAT or NTFS file systems) on macOS Ventura… and see what happens. The NTFS kext isn’t even present anymore!

It turns out:

% mount | grep disk4
fat://disk4s1/NO NAME on /Volumes/NO NAME (lifs, local, nodev, nosuid, noowners, noatime)

macOS Ventura does use user-mode file systems for external disk drives.

The file system extensions

/System/Library/Filesystems/msdos.fs/Contents/Resources/livefiles_msdos.dylib
/System/Library/Filesystems/exfat.fs/Contents/Resources/livefiles_exfat.dylib
/System/Library/Filesystems/ntfs.fs/Contents/Resources/livefiles_ntfs.dylib

We see that the livefiles extensions are shipping today for 3 file systems: FAT, ExFAT and NTFS.

Those are loaded by the UVFSService process, which is itself loaded by userfsd.

The bad news, the set of entitlements used by UVFSService:

entitlements = {
	"com.apple.private.LiveFS.connection" = true;
	"com.apple.security.iokit-user-client-class" = "AppleLIFSUserClient";
	"com.apple.private.allow-external-storage" = true;
};

This means that we cannot load our own LiveFS extension modules within a stock operating system configuration.

What does that interface between the LiveFS provider and UVFSService look like anyway?

https://github.com/apple-oss-distributions/msdosfs/blob/main/LiveFilesTester/UserVFS.h gives the answer. It’s a fairly conventional interface that matches the semantics required quite well.

What if we try to mount directly?

% sudo diskutil unmount "/Volumes/NO NAME" 
Volume NO NAME on disk4s1 unmounted
% sudo mount -o local,nodev,nosuid,noowners,noatime -t lifs "fat://disk4s1/NO NAME" ~/a   
mount error:: Input/output error
mount: /Users/sunrise/a failed with 71
% sudo dmesg | grep lifs
arm64e_plugin_host: running binary "sudo" in keys-off mode due to entitlement: com.apple.private.security.clear-library-validation
lifs_mount:uid:501:gid:20
lifs_req_callback_thread: thread <ptr> starting for mount <ptr>
lifs_io_strategy_thread: thread <ptr> starting for mount <ptr>
Invalid lifs host port or port dying
Got error during mount request: 5
lifs_io_strategy_thread: thread <ptr> exiting for mount <ptr>
lifs_req_callback_thread: thread <ptr> exiting for mount <ptr>

Ok, it wants the lifs host port to be set properly before calling mount. What if we try to mount the disk using the regular Unix mount command sequence?

% sudo mount -t msdos /dev/disk4s1 ~/a
Executing: /usr/bin/kmutil load -p /System/Library/Extensions/msdosfs.kext
% mount | grep disk4
/dev/disk4s1 on /Users/sunrise/a (msdos, local, noowners)

Ok, in that case it doesn’t use UserFS, but the kext instead. Now what if we use diskutil mount?

% sudo umount ~/a
% sudo diskutil mount /dev/disk4s1
Volume NO NAME on /dev/disk4s1 mounted
% mount | grep disk4       
fat://disk4s1/NO NAME on /Volumes/NO NAME (lifs, local, nodev, nosuid, noowners, noatime)

OK, that makes sense.

More questions…

I didn’t track down yet how to mount manually for this scenario. How should that be done?

And, I really want this to be extensible to FUSE too. It turns out that there’s a sign of that at /System/Library/PrivateFrameworks/UVFSXPCService.framework/XPCServices/UVFSService.xpc/Contents/Resources/knownPlugins.internal.plist which does have a fuse entry. I wonder when that will happen to the public?

Leave a Reply

Your email address will not be published. Required fields are marked *