macOS FUSE-T build: FUSE service and go-nfsv4 are never terminated on dismount; leftovers hijack the next mount through the shared Bonjour name "fuse-t"
Summary
On macOS, VeraCrypt 1.26.29 (FUSE-T build) leaves two processes behind after every dismount: the FUSE service that serves the decrypted image (a fork of VeraCrypt running fuse_main) and FUSE-T's SMB server go-nfsv4. DismountVolume detaches the disk image and unmounts the auxiliary share, but never asks either process to exit, and the FUSE service ignores SIGTERM by design. This is the process leak reported in #1845 and the reason for the post-dismount access in #1779.
This report adds the part that turns the leak into a user-visible failure. Every go-nfsv4 instance advertises the same Bonjour service instance, fuse-t._smb._tcp.local, and the macOS SMB client mounts by that name. After a few mount and dismount cycles, several servers answer to the name and the next mount can be routed to a stale one. On this machine that ends in the Finder dialog
There was a problem connecting to the server "fuse-t". This file server will not allow any additional users to log on. Try to connect again later.
or in TimeOut at VeraCrypt::FuseService::Mount:727 (#1785), because the control file never appears in the new auxiliary mount. It also means a live mount can end up being served by a leftover of an earlier session, and that the same container can be attached twice, which is a data integrity problem, not only a confidentiality one. I lost roughly a fifth of the files in one container this way; the observed state is described below.
Three concrete findings for whoever fixes this:
- The exact code path that creates the leftovers, and why
SIGTERMcannot end them (below, with line numbers from the 1.26.29 source). - The cleanup recommended in #1845,
pkill -f 'VeraCrypt.*--core-service', does not reach the leftover of a file container. That process is a plain fork of the GUI and carries the GUI's argv,/Applications/VeraCrypt.app/Contents/MacOS/VeraCrypt, with no--core-service. - PR #1866 is the right shape but needs a
SIGKILLfallback and must also endgo-nfsv4, because on FUSE-T the server outlives its client. A separate guard against attaching one container twice is needed as well.
Environment
- macOS 26.7 (25G229), MacBook Pro M4 Max, bare metal; the loss happened on 26.6.x a few days earlier
- VeraCrypt 1.26.29 (FUSE-T build), official dmg
- FUSE-T 1.2.7 (
/Library/Application Support/fuse-t/bin/go-nfsv4 -> go-nfsv4-1.2.7) - File containers with APFS inside, mounted and dismounted from the GUI. Device-backed volumes show the same leak in #1845.
What the code does (1.26.29 source, line numbers from the release tarball)
FuseService::Mount(src/Driver/Fuse/FuseService.cpp:675) forces-o backend=smb,-o nonamedattrand-o rwsize=262144underVC_MACOSX_FUSET(line 691), runsProcess::Execute("fuse", args, -1, &execFunctor)and then polls for the control file 51 times at 100 ms before throwingTimeOut(line 727).Process::Execute(src/Platform/Unix/Process.cpp:79) forks (line 107) and, when anExecFunctoris supplied, calls it in the child instead ofexecvp(line 144). The child keeps the parent's argv.FuseService::ExecFunctor::operator()(FuseService.cpp:789) callssetsid()(line 844), forks the termination-signal helper (line 849) and ends in_exit(fuse_main(...))(line 878). libfuse-t daemonises insidefuse_main(fuse_daemonizeis present inlibfuse-t-1.2.7.dylib), so the process that actually serves the volume is a grandchild that launchd adopts. Its command line inpsis therefore:/Applications/VeraCrypt.app/Contents/MacOS/VeraCryptfor a file container, because the core service is a fork of the GUI without exec (CoreService::Start,src/Core/Unix/CoreService.cpp:485), andVeraCrypt --core-serviceonly for a device-backed volume, which needs the elevated core service (MountVolumeRequest::RequiresElevation,src/Core/Unix/CoreServiceRequest.cpp:257, andCoreService.cpp:554).
fuse_service_init_common(FuseService.cpp:139to 151) setsSIGINT,SIGQUITandSIGTERMtoSIG_IGN, with the comment "Termination signals are handled by a separate process to allow clean dismount on shutdown". The helper process that would handle them (FuseService::OnSignal, line 775) has its pid recorded nowhere, so nothing can signal it later.CoreMacOSX::DismountVolume(src/Core/Unix/MacOSX/CoreMacOSX.cpp:255to 331) runshdiutil detach(line 269), thenumount -- <aux mount point>with retries (line 314), then deletes the directory (line 327) and returns. There is no shutdown request, no wait and no signal.VolumeInfo::Serialize(src/Volume/VolumeInfo.cpp) writes no pid into the control file, so the dismount side has nothing to target even if it wanted to.CoreUnix::MountVolume(src/Core/Unix/CoreUnix.cpp:1124to 1160) selects the first.veracrypt_aux_mntNthat is not in the mount table, deletes a stale directory of that name and recreates it. After a dismount the same_mnt1is reused while the previousgo-nfsv4 ... /private/tmp/.veracrypt_aux_mnt1is still alive and still exporting the same share name.CoreUnix::GetMountedVolumes(CoreUnix.cpp:546) walks the mount table and keeps only mount points containing.veracrypt_aux_mnt(line 552).MountVolumerefuses a container only when that list contains it (IsVolumeMounted, line 1056). Avolume.dmgthat is still attached after its share has gone is invisible to both, so the same container can be mounted a second time. Nothing at the file level prevents it either:Volume::Openasks forFile::ShareNone, but the advisory lock behind it inFile::Open(src/Platform/Unix/File.cpp) is compiled out under#if 0 // File locking is disabled to avoid remote filesystem locking issues, so a second FUSE service opens the same container read-write while the first still holds it.
The sample of a leftover service posted in #1845 on 11 August sits in fuse_session_loop_mt -> fuse_sem_wait: the session never ends because nothing ends it.
What FUSE-T does with a leftover (1.2.7 binaries and the public go-smb2 code)
- libfuse-t launches
/usr/local/bin/go-nfsv4 --namedattr=false --rwsize=262144 --dontbrowse=true --backend smb /private/tmp/.veracrypt_aux_mntN. go-nfsv4listens on127.0.0.1:<random port>and mounts//Guest@%s._smb._tcp.local/%s(format string in the 1.2.7 binary). The first%sis thelocationsetting, defaultfuse-t. The macOS SMB client resolves that instance name through Bonjour to obtain the port; the client-side name is visible in the logs quoted in macos-fuse-t/fuse-t#113 (Starting reconnect with fuse-t._smb._tcp.local).- The advertisement comes from go-smb2's
bonjour.Advertise, which callszeroconf.RegisterProxy(hostname, "_smb._tcp", ".local", port, svcName, ...)with bothhostnameandsvcNameset to the location. The library is grandcat/zeroconf v1.0.0: itsprobe()carries//TODO: implement a proper probing & conflict resolution, records are announced with TTL 3200 s, and a goodbye packet is only sent fromShutdown(), which the server only calls from its signal handler. - go-nfsv4 numbers concurrent instances (
fuse-t,fuse-t-1, and so on) from what is mounted at the time, not from what is still advertised, so after a dismount the base name is handed out again while the leftover still holds it. N leftover servers and the new one then all claimfuse-t._smb._tcp.local, each with its own port, and which port the client receives is arbitrary. A leftover that wasSIGKILLed sends no goodbye, so its record can stay in the resolver cache for up to 3200 s.
What the user sees
- After three to six mount and dismount cycles in one session: the "will not allow any additional users to log on" dialog. That wording is the client's
EUSERSmessage; Apple'ssmbfsmapsSTATUS_REQUEST_NOT_ACCEPTEDtoEUSERS(kernel/netsmb/smb_subr.c,nt2errno[]). The mount fails and VeraCrypt reportsMount:727, because the control file never turns up in the new_mntN. dns-sd -B _smb._tcp local.showsfuse-t;dns-sd -L fuse-t _smb._tcp local.resolves to a port thatlsofattributes to ago-nfsv4whose auxiliary directory has no attached image any more.- Each leftover keeps the container file open, so the disk will not eject (#1845), keeps the decrypted volume reachable through the stale share (#1779, and the "Open Recent" reproduction in #1845), and keeps its Bonjour registration.
- Later, an application that had a document open on a dismounted volume made macOS ask for a name and password for the server
fuse-t-4: the SMB client reconnecting to a leftover on the application's behalf. Connecting as Guest there would have reopened the dismounted volume without VeraCrypt's password, which is #1779 with a prompt in front of it.
Why this needs a prompt fix: data integrity
Two consequences of the leak can damage a volume. Both follow from the code above; the second is what I found on the day I lost data.
A live mount served by a leftover. When the client is routed to a leftover that exports the same share name, which is the normal case because
_mnt1is reused, the new mount is served by the previous session's FUSE process. VeraCrypt reads that process's control file, sees the same container path, attaches itsvolume.dmg, and the "new" volume is the old process's view of the container. From then on the process serving the live APFS volume is exactly the one that every workaround in #1845 tells users toSIGKILL, and one that the next dismount will not clean either. If it dies while the APFS volume is mounted, the volume loses its backing device in the middle of a write.The same container attached twice. When the SMB session behind a mounted volume breaks and the kernel client gives up on the share, the auxiliary mount leaves the mount table, the disk image stays attached, the APFS volume stays in Finder as a dead volume that lists as empty, and VeraCrypt no longer regards the container as mounted (point 6 above). Mounting the container again is accepted, and a second APFS volume with the same name appears next to the first. That is exactly what Finder showed on the day of the loss: two volumes with the identical name from one container, one in use and one empty. The one in use turned out to have lost roughly 20 per cent of its files, which is what an APFS volume looks like when the writes of its previous session never reached the backing file. I cannot prove what broke the first session (the Mac had been through several sleep and wake cycles with the volume mounted, and macos-fuse-t/fuse-t#113 shows the macOS 26.6 client dropping and reconnecting sessions to
go-nfsv4), but every element of the chain is in the code, and the resulting state (an attachedvolume.dmgunder.veracrypt_aux_mntwith no auxiliary mount behind it) is easy to observe withhdiutil infoandmount.
The second road to the same state is macOS bookmarks, and it is the one the attached evidence shows. A sandboxed app keeps a folder the user chose as a security-scoped bookmark, and Preview's Open Recent list and UTM's VM list are bookmarks as well. A bookmark records how to bring its volume back, and macOS resolves it by mounting that volume unless the caller passes NSURLBookmarkResolutionWithoutMounting. For a volume of this build, "its volume" is volume.dmg on the SMB share of one particular server, so resolution means: connect to fuse-t-N._smb._tcp.local, mount the share under /Volumes/.veracrypt_aux_mntN, attach the image found there, mount the APFS volume. With a leftover server answering, that is the silent remount of #1779 and the Open Recent reproduction in #1845, and every resolution adds one more attachment of the same container. The evidence shows the same image attached from three places (T/.veracrypt_aux_mnt1, /Volumes/.veracrypt_aux_mnt1, /Volumes/.veracrypt_aux_mnt1-1), listed three times under one slot by --text --list, and three volumes with the same name in Finder. The trigger was a small App Store photo tool whose output folder I had chosen on the volume months earlier: opening it produced, in turn, a login prompt for the server fuse-t-4 while a leftover answered, the second and third copies of the container, and, once the leftovers were ended, "There was a problem connecting to the server fuse-t-4. The server may not exist or is unavailable". Removing the app ended it.
Reproduction
# 1. Mount a file container from the GUI, then dismount it from the GUI. Repeat three times.
# 2. Nothing is mounted now, yet the processes remain:
ps -axo pid,ppid,user,etime,command | grep -E 'go-nfsv4|VeraCrypt' | grep -v grep
mount | grep veracrypt_aux # empty: VeraCrypt did unmount the share
hdiutil info | grep image-path # no volume.dmg attached
# 3. Bonjour still answers to the name, once per leftover:
dns-sd -B _smb._tcp local. # Ctrl-C after a few seconds
dns-sd -L fuse-t _smb._tcp local. # "can be reached at fuse-t.local.:<port>"
lsof -nP -a -c go-nfsv4 -iTCP -sTCP:LISTEN
# 4. Mount again: either the EUSERS dialog or "TimeOut at VeraCrypt::FuseService::Mount:727".
log show --last 10m --info --predicate 'eventMessage CONTAINS[c] "fuse-t"'Expected after step 2: no go-nfsv4, no serving VeraCrypt process with ppid 1, no fuse-t Bonjour entry. Actual: one go-nfsv4 and two VeraCrypt processes (service and signal helper, both with ppid 1) per cycle, and fuse-t still advertised. A full listing from my machine is attached as evidence.txt.
The double attachment can be forced without waiting for a broken session: with a container mounted, umount -f its auxiliary mount, then mount the same container again from the GUI. VeraCrypt accepts the second mount and Finder shows two volumes with the same name.
Proposed fix
Merge PR #1866 (shutdown request written through the auxiliary filesystem, pid identity check, wait for exit). Two additions are needed for FUSE-T:
- After
WaitForDismounttimes out, sendSIGKILL.SIGTERMis ignored by design and a wedged session never answers a write to/shutdown. - Terminate
go-nfsv4as well. On FUSE-T the server outlives its client. The service calledsetsid(), sokillpg(pid, SIGKILL)reaches the server if it stayed in that process group; otherwise it is the process whose argv ends with the auxiliary mount point. Please verify which of the two holds with 1.2.7 before relying onkillpg.
- After
If a smaller change is preferred: record
getpid()in the control file fromfuse_service_init_common, which runs in the daemonised process, and act on it inCoreMacOSX::DismountVolumeafter theumountloop succeeds. Sketch, untested:
// FuseService.cpp, fuse_service_init_common(), after the sigaction calls
{
ScopeLock lock (OpenVolumeInfoMutex);
OpenVolumeInfo.FuseServicePid = (uint64) getpid();
}
// CoreMacOSX.cpp, DismountVolume(), after the umount loop and before AuxMountPoint.Delete()
#ifdef VC_MACOSX_FUSET
if (mountedVolume->FuseServicePid > 0)
{
pid_t pid = (pid_t) mountedVolume->FuseServicePid;
killpg (pid, SIGKILL); // go-nfsv4, if it shares the session created by setsid()
kill (pid, SIGKILL); // SIGTERM is SIG_IGN in the service by design
for (int t = 0; t < 50 && kill (pid, 0) == 0; t++)
Thread::Sleep (100);
}
#endif FuseServicePid would be a new uint64 in VolumeInfo, serialised next to SerialInstanceNumber.
Do not reuse an auxiliary index while a server for it is alive. Bonjour name uniqueness is FUSE-T's problem and is reported there (macos-fuse-t/fuse-t#116), but VeraCrypt can avoid the identical-share-name case on its own by skipping any
_mntNthat still appears in ago-nfsv4argv, or by using a random suffix.Guard against attaching one container twice, independently of FUSE-T. This matters even after PR #1866: a bookmark resolved while a volume is legitimately mounted mounts the current session's share at
/Volumes/.veracrypt_aux_mntNand attaches the image a second time, with no leftover involved. Before mounting, look athdiutil infofor an attached image whose path lies under a.veracrypt_aux_mnt*directory and refuse the mount (or offer to detach) when it belongs to the same container; afterhdiutil detachinDismountVolume, verify that the device is gone before continuing. Re-enabling theF_SETLKwrite lock inFile::Openfor containers on local filesystems would catch the same case at the lowest level; the remote-filesystem concern that led to#if 0does not apply to a container on APFS.GetMountedVolumescould also report an attached image without an auxiliary mount as a broken volume rather than ignoring it, so that the GUI shows the user what is happening instead of an empty volume in Finder.Until a release ships: state in the #1845 thread and in the release notes that the cleanup has to target
go-nfsv4processes whose argv contains.veracrypt_aux_mnt, and VeraCrypt-binary processes with ppid 1 that hold a container open, not--core-service, and that an attachedvolume.dmgwithout its share must be detached before the container is mounted again. I run a scoped script that does exactly that after every dismount and can share it if useful.
Control result on the NFS backend
Same Mac, VeraCrypt 1.26.24 (FUSE-T build), FUSE-T 1.2.6, so no backend=smb: after a dismount there is no go-nfsv4, no serving process and no auxiliary mount, and libfuse.log shows the session ending on its own ("session exit in read"). The leftover is specific to the SMB backend, where go-nfsv4 keeps running after its mount point is unmounted and the FUSE session therefore never ends. The stale bookmarks still try smb://fuse-t-N and now fail harmlessly.
Related
- #1845, #1785, #1779, #1757, #1667, PR #1866
- GHSA-f3px-f8h2-fm4c and GHSA-3p8m-hrc7-cw3h, referenced in #1845
- macos-fuse-t/fuse-t#111 (
go-nfsv4ignoresSIGTERM), macos-fuse-t/fuse-t#113 (client reconnects tofuse-t._smb._tcp.local) - FUSE-T side of this report: macos-fuse-t/fuse-t#116
Attachments
evidence-2.txt, attached at the end of this issue: the full evidence (the block below is cut at 30,000 characters), with the placeholders corrected as described above. It supersedes the first upload,evidence.txt, which carried the placeholder errors.- collect-evidence-2.sh.txt: the collector that produced it.
Evidence (names redacted)
evidence.txt, collected with collect-evidence-2.sh on this Mac(names redacted: USER, HOST, VOLUME-n, CONTAINER-n, SERVICE-n, IP-n are placeholders, consistent throughout; VeraCrypt's own .veracrypt_aux_mnt names, pids, ports and /dev/diskN are real. CONTAINER-8 is a file held only by the go-nfsv4 processes, not a container; the containers of the three mounted volumes are CONTAINER-5, -6 and -7.)
collect-evidence-2.sh 2026-09-16 17:56:22 -0400
== versions ==
ProductName: macOS
ProductVersion: 26.7
BuildVersion: 25G229
arm64
VeraCrypt: 1.26.29 (FUSE-T build)
lrwxr-xr-x 1 root wheel 54 Sep 15 09:40 /usr/local/bin/go-nfsv4 -> /Library/Application Support/fuse-t/bin/go-nfsv4-1.2.7
lrwxr-xr-x 1 root wheel 21 Sep 15 09:40 /usr/local/lib/libfuse-t.dylib -> libfuse-t-1.2.7.dylib
/Library/Application Support/fuse-t/bin:
total 112816
lrwxr-xr-x 1 root wheel 14 Sep 15 09:40 go-nfsv4 -> go-nfsv4-1.2.7
-rwxr-xr-x 1 root wheel 18723296 Aug 5 2025 go-nfsv4-1.0.49
-rwxr-xr-x 1 root wheel 18687584 Mar 15 2026 go-nfsv4-1.0.54
-rwxr-xr-x 1 root wheel 20342560 Jun 3 08:11 go-nfsv4-1.2.7
org.fuse-t.1.0.49
org.fuse-t.fskit.1.2.7
org.fuse-t.1.0.54
org.fuse-t.core.1.2.7
org.fuse-t.1.0.47
== fuse-t.ini, active lines ==
[Default]
== per-user FUSE-T folder (~/.fuse-t, consulted by libfuse-t) ==
(none)
== /etc/hosts aSource: veracrypt/VeraCrypt