Skip to content

Linux: do not mount a volume again while a detached service keeps it open - #1967

Open
flatstik wants to merge 5 commits into
veracrypt:masterfrom
flatstik:fix-1959-detached-service
Open

flatstik wants to merge 5 commits into
veracrypt:masterfrom
flatstik:fix-1959-detached-service

Conversation

@flatstik

@flatstik flatstik commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1959.

An emergency dismount lazily detaches the file systems, and the FUSE service keeps the volume open until the last file on the detached file system is closed, but the volume is no longer in the mount table. Once the volume is open, an exclusive flock is taken for it and inherited by the FUSE service, which holds it until it exits; a second mount fails with "already mounted" meanwhile. A busy lock is retried for 10 s because a normally dismounted service may still be exiting.

The lock is named after the device number, or the file system and inode numbers, that fstat reports for the opened volume, so it applies to the file actually served whatever path leads to it. It is not taken on the volume file itself, whose locking is disabled for remote file systems. The lock files are created with openat in the root owned /var/run/veracrypt (also present where /run is not), which is opened without following symbolic links and checked through its descriptor. A writable mount is refused when the lock cannot be taken; a read-only mount goes on without it. The lock files are not removed, to avoid the usual unlink race; they are small and on tmpfs. This adds File::GetSystemHandle().

A second commit covers kernel crypto, where the deferred device-mapper device keeps using the volume after the FUSE service has exited: before taking the lock, a volume device held by a device-mapper device, or a volume file backing a loop device held by one (CoreLinux::IsVolumeUsedByKernelDevice), is refused; a writable mount is also refused if such a device cannot be inspected. Only loop devices held by device-mapper are queried, so an unrelated loop device cannot fail or block a mount. A volume mounted under another path is refused at once instead of after the 10 s wait, and --auto-mount=devices skips such a device instead of stopping. A whole loop device takes the identity of its backing (GetVolumeIdentity), so a loop device and its file conflict; a loop chain is not followed, which refuses a writable mount, and a partition of a loop device stays separate. The lock directory handling is shared with #1966 (OpenMountLockFile).

Tested:

  • OpenWrt 25.12.6 mipsel (FUSE3, root), 1.26.30 against 1.26.30 with this change: with a file open on the volume and the volume emergency-dismounted, mounting it again succeeded (mounted twice), against refused with "already mounted"; after the file is closed and the old service exits, it mounts normally.
  • Tests/test_linux_emergency_dismount_remount.py (new, run in the Linux workflow), with FUSE and with kernel crypto, and a mount through a symbolic link while the volume is mounted: passes on ubuntu-24.04 and ubuntu-26.04, https://github.com/flatstik/VeraCrypt/actions/runs/40582819859; with only the first commit, the kernel crypto remount succeeds and the mount through the link fails only after 10 s, https://github.com/flatstik/VeraCrypt/actions/runs/40582822189.
  • The same test covers a detached service of a file against its loop device, and a deferred mapping on a loop device against its file: passes on ubuntu-24.04 and ubuntu-26.04, https://github.com/flatstik/VeraCrypt/actions/runs/42407154888.
  • The --auto-mount=devices change and the alternate loop node layouts are not covered by a test.

The new test adds lines next to test_mount_helper.py in .github/workflows/build-linux.yml, as do the other PRs split from #1963, so whichever is merged later conflicts there; I will rebase it.

Split from #1963, with #1968 (#1958), #1966 (#1960), #1964 (#1961), #1965 (#1962).

@curious-rabbit

Copy link
Copy Markdown
Contributor

MountAllDeviceHostedVolumes does not catch VolumeAlreadyMounted. A device already mounted under another path now stops --auto-mount=devices after 10 s instead of being skipped.
With kernel crypto the lock is released when the aux FUSE mount is unmounted, while the deferred dm device still uses the container. A remount in another slot is not refused.

@flatstik
flatstik force-pushed the fix-1959-detached-service branch from de268d1 to 5a39440 Compare October 27, 2026 21:11
@flatstik

Copy link
Copy Markdown
Contributor Author

Both fixed in a second commit. A volume device held by a device-mapper device, or a volume file backing a loop device, is now refused before the lock is taken, which covers the deferred dm device after an emergency dismount with kernel crypto. A volume mounted under another path is refused at once, and --auto-mount=devices skips it. The test now runs the emergency dismount with kernel crypto too and mounts through a symbolic link: with only the first commit both checks fail, https://github.com/flatstik/VeraCrypt/actions/runs/40582822189, and they pass with the second, https://github.com/flatstik/VeraCrypt/actions/runs/40582819859. The --auto-mount=devices path has no test.

@idrassi idrassi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address the issues inline.

Both this PR and #1966 add the same OpenMountLockFile helper. Once one is merged, please rebase the other and reuse the helper already in master.

Comment thread src/Core/Unix/CoreUnix.cpp Resolved
Comment thread src/Core/Unix/CoreUnix.cpp Resolved
Comment thread src/Core/Unix/CoreUnix.cpp Resolved
Comment thread src/Core/Unix/CoreUnix.cpp Resolved
@flatstik
flatstik force-pushed the fix-1959-detached-service branch from 5a39440 to db4f54b Compare October 28, 2026 21:36
@flatstik

Copy link
Copy Markdown
Contributor Author

OpenMountLockFile and GetMountLockDirectory are now the same in this PR and #1966. Once one of them is merged, I will rebase the other onto master and drop its copy.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Critical gaps in loop-device identity locking and backing-file verification can still permit two writable mounts of the same container.

5 open issues
What changed in this PR

This PR adds Linux safeguards against remounting volumes still in use after an emergency dismount, addressing #1959 in the Unix mounting code.

Changes:

  • Adds volume-identity locks held by the FUSE service and checks for lingering kernel-device use.
  • Preserves requester-derived file identities and skips already-mounted devices during automatic mounting.
  • Adds remount regression tests and integrates them into the Linux workflow.
File Description
Tests/​test_linux_emergency_dismount_remount.py Tests emergency remounts with FUSE and kernel crypto, plus alternate paths.
Tests/​linux_mount_read_only_test.cpp Supplies a writable lock directory for unprivileged tests.
Tests/​linux_mount_points_test.cpp Supplies a test-local lock directory.
src/​Platform/​Unix/​RequesterAccess.cpp Returns device and inode identity from the requester helper.
src/​Platform/​Unix/​File.cpp Adds requester-aware file identity lookup.
src/​Platform/​File.h Exposes handle/stat access and stores requester-derived identity.
src/​Main/​UserInterface.cpp Skips already-mounted volumes during device auto-mounting.
src/​Core/​Unix/​Linux/​CoreLinux.h Declares the kernel-device usage check.
src/​Core/​Unix/​Linux/​CoreLinux.cpp Checks device-mapper holders and loop-device backing files.
src/​Core/​Unix/​CoreUnix.h Adds kernel-usage and lock-directory hooks.
src/​Core/​Unix/​CoreUnix.cpp Adds volume-identity locking, busy-lock retries, and alias checks.
.github/​workflows/​build-linux.yml Triggers and runs the new Linux regression test.

Review effort: Max

Comment thread src/Core/Unix/CoreUnix.cpp Resolved
Comment thread src/Core/Unix/CoreUnix.cpp Resolved
Comment thread src/Core/Unix/Linux/CoreLinux.cpp Resolved
Comment thread src/Core/Unix/Linux/CoreLinux.cpp Resolved
@flatstik

flatstik commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Will fix these based on suggestions latest on sunday.

…open

An emergency dismount lazily detaches the file systems. The FUSE
service keeps running with the volume open until the last file on the
detached file system is closed, but the volume is no longer in the
mount table, so it could be mounted again and two services wrote to it.

Once the volume is open, take an exclusive flock for it. The FUSE
service inherits the descriptor and holds the lock until it exits; a
second mount fails with "already mounted" meanwhile. Because a normally
dismounted service may still be exiting, a busy lock is retried for up
to 10 seconds.

The lock is named after the device number, or the file system and inode
numbers, that fstat reports for the opened volume, so it applies to the
file actually served, whatever path leads to it. It is not taken on the
volume file itself, whose locking is disabled to avoid remote file
system issues. The lock files are created with openat in the root owned
/var/run/veracrypt, which is opened without following symbolic links
and checked through its descriptor. /var/run also exists on systems
without /run. Without root or a usable lock directory, mounting works
as before.

Fixes veracrypt#1959
With kernel crypto, the volume is used by a device-mapper device, which an
emergency dismount removes deferred. The FUSE service exits, so the
volume lock was released while the device still used the volume, and the
volume could be mounted again in another slot. Before taking the lock,
refuse a volume device held by a device-mapper device, or a volume file
backing a loop device.

A volume mounted under another path, for example a device alias, made a
second mount wait for the lock for 10 seconds. Refuse at once when a
mounted volume is the same file or device. --auto-mount=devices skips
such a device instead of stopping at it.
The volume lock and the kernel device check let mounting continue when
they could not establish that the volume was unused: an unusable lock
directory, an unexpected flock error, a sysfs or loop device that could
not be inspected, or an fstat failing on a file opened with the
requester's credentials, as FUSE checks the credentials of the calling
process. A writable mount is now refused in these cases; a read-only one
continues as before. A loop device that disappears during the check is
still taken as not using the volume.

The requester's process passes the device and inode of the file it
opened, and the lock uses them for such files instead of fstat.

The kernel device check is repeated once the lock is held: another mount
of the volume may have finished, and been dismounted in an emergency,
while this one was waiting for it. The lock descriptor is closed if an
exception occurs before it is returned.

The lock directory must belong to the user running VeraCrypt, root,
and be writable only by it. CoreUnix::GetMountLockDirectory() gives its
location, which the unprivileged mount test overrides, so that it uses
the production locks instead of failing to create /var/run/veracrypt.
Tests/test_linux_emergency_dismount_remount.py checks no second mount
while an emergency-detached service keeps the volume open against
the installed package in the Linux workflow.
A volume mounted through a loop device took a lock unrelated to the lock of
the same container mounted as a file, so an emergency dismounted file could
be attached to a loop device and mounted a second time. The lock and the
kernel user check now apply to the backing of the opened loop device, a
file or a block device. A chain of loop devices is not followed, which
refuses a writable mount; a partition of a loop device stays separate.

Only loop devices held by device-mapper are queried for their backing file:
the query reaches the file system of that file, so an unrelated loop device
could fail or block every writable mount of a file. Their nodes are found in
the layouts that AttachFileToLoopDevice supports and verified by the device
number from sysfs; one that is still held without a usable node refuses the
mount.

MountVolumeNative verifies the loop device against the opened and locked
file, not a new lookup of its path.

The test resolves the executable once, as it runs commands in its work
directory, and covers a detached service of a file against its loop device,
and a deferred mapping on a loop device against its file.
@flatstik
flatstik force-pushed the fix-1959-detached-service branch from 1bf6fa9 to 89d5532 Compare November 10, 2026 15:00
@curious-rabbit

Copy link
Copy Markdown
Contributor

While I appreciate that the bug I reported is getting attention, this does not look like a reasonable fix at all anymore, but rather like complete AI slop with zero engineering behind it. This is a rare edge case to begin with and I would rather see the bug remain then watch this getting merged

This goes for quite a few recent PRs and I am starting to worry what gets merged into veracrypt

@flatstik

flatstik commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor Author

While I appreciate that the bug I reported is getting attention, this does not look like a reasonable fix at all anymore, but rather like complete AI slop with zero engineering behind it. This is a rare edge case to begin with and I would rather see the bug remain then watch this getting merged

This goes for quite a few recent PRs and I am starting to worry what gets merged into veracrypt

Issue #1982 is about locking the existing Windows option, and #1983 does that. I vibecoded the #1984 if it's wanted for the Unix side too. I am fully aware that user-process version would protect weakly and this is the result. Luckily, I am not the one who does the decision for merging and I'm just as happy if this is closed or merged or used as a startpoint for something else. If people really want something like this, they can use the code for their own amusement by compiling themselves. I'm also fully aware that maintenance of this is ridiculous. I'd use USBguard instead.

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.

After emergency unmount the same volume can be mounted twice and gets corrupted

4 participants