Skip to content

macOS: mount through the macFUSE FSKit backend on macOS 26 and later - #1998

Open
heios wants to merge 1 commit into
veracrypt:masterfrom
heios:macos-fskit
Open

heios wants to merge 1 commit into
veracrypt:masterfrom
heios:macos-fskit

Conversation

@heios

@heios heios commented Nov 12, 2026

Copy link
Copy Markdown

macFUSE 5 can run file systems in user space through FSKit, which needs neither its kernel extension nor Reduced Security. This PR uses that backend on macOS 26 and later when the kernel extension is not loaded, so the macFUSE build can mount volumes on a Mac with default security settings. Related to #1900, which asks for FSKit through FUSE-T; this covers the macFUSE build only.

The backend is chosen once per mount, before the FUSE process forks. FSKit is used when macOS is 26 or later, the kernel extension is not loaded (no vfs.generic.macfuse.version.number sysctl) and the installed macFUSE is 5.2 or later. Otherwise the mount goes through the kernel extension as before. An extension that is approved but not yet loaded cannot be told apart from an unapproved one without a privileged load attempt, so such a system now mounts through FSKit. An extension loaded later is picked up by the next mount.

What FSKit needed:

  • -o backend=fskit is passed to the auxiliary mount.
  • FSKit loads no kernel extension, so the version sysctl does not exist after the mount. A missing sysctl after a successful mount is now taken as FSKit instead of raising HigherFuseVersionRequired.
  • FSKit ignores direct_io and fails a read that ends before the advertised file size with EIO. control and aux-device-info already advertise 64 KiB, so their reads are now padded with zeros up to that size. This part is shared code, so Linux and FreeBSD serve the same padded files.
  • FSKit extensions are enabled per user, and macFUSE refuses FSKit mounts from root. When the elevated service mounts a volume through FSKit, the FUSE process opens the volume as root, gives the mount point to the requesting user and drops to that user (setgroups, setgid, setuid) before fuse_main. Dropping from root marks the process set-id, so the user's other processes cannot attach to it or get its task port. The signal-handler process forked earlier stays root, as before. Mounts through the kernel extension are unchanged.
  • The host file of the volume is marked close-on-exec on macOS for both backends, so that helpers such as hdiutil do not keep it open after the service exits.

Testing

macOS 27.0.1 on Apple Silicon, macFUSE 5.5.0, kernel extension not loaded:

  • command line (--text), without sudo: create a 64 MB exFAT container, mount, write 20 MiB, unmount, mount again and compare SHA-256. The auxiliary mount shows fskit, the kernel extension stays unloaded, and no process or mount is left after the unmount;
  • the same commands with sudo: the mount belongs to my user and the FUSE process runs with my uid, while the signal-handler process stays root;
  • GUI: create, mount, dismount and quit. No administrator password is asked for a file container.

Linux, because of the padded reads (Debian 13 container, kernel 6.8, aarch64), console build with libfuse 2.9.9 and with libfuse 3.17.2: FAT and exFAT containers, with kernel crypto and with nokernelcrypto, create, mount, --list, write 20 MB, unmount, mount again and compare. control and aux-device-info read back as 65536 bytes. With the libfuse 2 build, test_volume_read.py, test_linux_process.py, test_linux_mount_read_only.py and test_linux_mount_points.py pass, and the three Tests/test.*.hc mounts from the Linux workflow work.

Not tested:

  • the fallback with the kernel extension loaded. As far as I can tell from the code it is unchanged, but I was hesitant to relax the security of my machine to test this, sorry for that;
  • macOS 26 itself, Intel Macs and FreeBSD.

Two things I noticed with FSKit: on the first mount macOS asks once whether VeraCrypt may access files on a network volume, and in text mode macFUSE prints fuse: forking a threaded process is unsafe, the child may crash or deadlock. I do not know whether the kernel backend prints the second one too.

macFUSE 5 can run file systems in user space through FSKit, which needs
neither its kernel extension nor Reduced Security. Use it on macOS 26
and later when the kernel extension is not loaded.

- Choose the backend once per mount, before the FUSE process forks:
  FSKit when running on macOS 26 or later, the kernel extension is not
  loaded (no vfs.generic.macfuse.version.number sysctl) and the
  installed macFUSE is 5.2 or later, which ships MFMount.framework.
  Otherwise the kernel backend is used, so a kernel extension that is
  already loaded, or a macFUSE too old for FSKit, works as before. An
  approved but not yet loaded kernel extension cannot be told apart
  from an unapproved one without a privileged load attempt, so FSKit
  is used in that case; a kernel extension loaded later is picked up
  by the next mount.
- Pass -o backend=fskit to the auxiliary mount.
- The FSKit backend loads no kernel extension, so the sysctl does not
  exist after the mount. A kernel-backend mount loads the extension,
  which cannot unload while the mount exists, so a missing sysctl
  after a successful mount means FSKit; skip the version check then.
- The FSKit backend ignores direct_io and fails any read that ends
  before the advertised file size with EIO. The control and
  aux-device-info files advertise at least VC_FUSE_METADATA_SIZE, so
  pad their reads with zeros up to the advertised size.
- FSKit file system extensions are enabled per user, and macFUSE
  refuses mounts from root ("File system extension not found"). When
  an elevated service mounts a volume for a user through FSKit, drop
  the FUSE process to that user after the volume is open and before
  fuse_main, and give the user the mount point. Dropping from root
  marks the process set-id, which keeps the user's other processes
  from attaching to it.
- Mark the volume's host file close-on-exec on macOS for both FUSE
  backends, so that helpers such as hdiutil do not retain it after the
  service exits.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant