Skip to content

Unix: fix issues #1958, #1959, #1960, #1961 and #1962 - #1963

Closed
flatstik wants to merge 6 commits into
veracrypt:masterfrom
flatstik:unix-security-fixes
Closed

flatstik wants to merge 6 commits into
veracrypt:masterfrom
flatstik:unix-security-fixes

Conversation

@flatstik

@flatstik flatstik commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes for the five issues reported by curious-rabbit, one commit each. Each commit describes its change in detail.

  • Hidden volume protection password stays in memory while the outer volume is mounted #1961 The protection password is released together with the outer volume password once the volume is open, so the forked FUSE service no longer inherits it.

  • Options --create-keyfile and --backup-headers overwrite existing files without asking #1962 --create-keyfile and --backup-headers ask before replacing an existing file in text mode, as the GUI does and as text mode already does for new containers (4edad72). --non-interactive fails instead; --force overwrites. The header backup prompt uses the wxWidgets string of the GUI file dialog, so no new translation key is needed.

  • Two volumes mounted at the same time can fail or end up at the wrong mount point #1960 Outside FUSE-T builds, the auxiliary mount point is created with mkdtemp (as FUSE-T builds already do) instead of reusing /tmp/.veracrypt_aux_mntN. The name keeps the .veracrypt_aux_mnt prefix that identifies auxiliary mounts. Its mode is not changed by pathname afterwards (0700 from mkdtemp instead of 0500), since the temporary directory can belong to the user with pam_tmpdir.

  • After emergency unmount the same volume can be mounted twice and gets corrupted #1959 Linux: once the volume is open, an exclusive flock is taken for it and inherited by the FUSE service, which holds it until it exits, so a volume whose service was detached by an emergency dismount cannot be mounted again meanwhile. 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 busy lock is retried for 10 s because a normally dismounted service may still be exiting. Without root or a usable lock directory, mounting works as before. The lock files are not removed, to avoid the usual unlink race; they are small and on tmpfs.

  • key memory is not locked and the volume service can be core dumped #1958 SecureBuffer allocations get whole pages of their own, locked with mlock and excluded from core dumps (MADV_DONTDUMP, MADV_NOCORE on FreeBSD). Memory locks are not inherited by fork, so the FUSE service locks the live buffers again once FUSE has put it in the background. The core service and the FUSE service also set RLIMIT_CORE to 0 and, on Linux, clear the dumpable flag, because key material can be copied outside SecureBuffer and a core_pattern pipe ignores RLIMIT_CORE. Volume::WriteSectors keeps a plain Buffer, still erased when freed: its data is encrypted in place right after the copy, and locking and unlocking its pages on every write reduced write throughput by 19 to 60 % in a benchmark (x86_64, AES-NI, 1 MiB to 4 KiB writes).

  • Tests Tests/test_unix_security_regressions.py checks each of the five issues against the installed package, as root, in the GUI package step of the Linux workflow (about 30 s).

Tested on Linux x86_64 (build without warnings, --test) and on OpenWrt 25.12.6 mipsel (ASUS RT-AX53U, FUSE3, as root), with the reporters' scenarios, comparing 1.26.30 with these commits applied on top of it:

1.26.30 with these commits
#1958 FUSE service VmLck 0 kB, core limit unlimited, 38 MB core file after SIGSEGV VmLck 24 kB, core limit 0, no core file
#1959 mount again after emergency dismount, old service still running mounted twice refused ("already mounted"), mounts normally after the old service exits
#1960 40 rounds of two parallel mounts failures in each run (ParameterIncorrect at MountVolume:1463, fuse: mount failed), once a volume mounted at the other volume's mount point 0 failed, 0 swapped; a foreign /tmp/.veracrypt_aux_mnt1 does not block mounting
#1961 hidden volume password in the FUSE service memory heap copy + argv argv only (the test passes it on the command line)
#1962 --create-keyfile on an existing keyfile replaced silently refused with KEYFILE_ALREADY_EXISTS; --force replaces it

Not built or tested: macOS and FreeBSD (the shared code was only reviewed for them). Not tested: the non-root user plus core service setup of #1961 (the reproduction above runs as root, without a core service).

…lume

MountVolume released the outer volume password once the volume was
open, but kept the protection password referenced by the mount request.
The forked FUSE service inherited it, so the hidden volume password
stayed in plaintext in both service processes until dismount.

Release it at the same points as the outer volume password.

Fixes veracrypt#1961
--create-keyfile and --backup-headers opened the destination with
O_CREAT|O_TRUNC without checking whether it existed. Recreating an
existing keyfile replaced it with new random data, so every volume that
used it could no longer be mounted, and an existing file chosen for a
header backup was truncated.

Ask before replacing an existing file, as the graphical interface does,
and as text mode already does for new containers (4edad72). With
--non-interactive the operation fails instead, and --force overwrites
without asking. The keyfile prompt is shown before the random pool is
enriched, so a refusal does not waste that input.

Fixes veracrypt#1962
Outside FUSE-T builds, the auxiliary mount point was the first
/tmp/.veracrypt_aux_mntN not listed in the mount table, deleted and
created again with mkdir. Two volumes mounted at the same time could
pick the same directory, so a mount failed or the file system of one
volume was mounted at the mount point of the other, also across users.
Any local user could block all mounts by creating the 256 directories.

Create a new directory with mkdtemp, as FUSE-T builds already do. The
name keeps the .veracrypt_aux_mnt prefix that identifies auxiliary
mounts. mkdtemp creates it with mode 0700 instead of the previous 0500,
which makes no difference to other users, so its mode is not changed by
pathname afterwards, which could follow a replaced directory when the
temporary directory belongs to the user (pam_tmpdir).

Fixes veracrypt#1960
…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
SecureBuffer only erased its memory when freed. The FUSE service that
holds the master keys had no locked memory and could be core dumped,
and a core file of it contained both halves of the master key.

Give each SecureBuffer whole pages of its own, lock them with mlock and
exclude them from core dumps (MADV_DONTDUMP, or MADV_NOCORE on FreeBSD).
Separate pages let a freed buffer be unlocked without affecting another
one. Memory locks are not inherited by fork, so the FUSE service locks
the live buffers again once FUSE has started it in the background. If
RLIMIT_MEMLOCK does not allow locking, the buffers are used unlocked as
before.

Key material can also be copied outside SecureBuffer, so the core
service and the FUSE service additionally disable core dumps: they set
RLIMIT_CORE to 0 and, on Linux, clear the dumpable flag, which a
core_pattern pipe does not ignore.

Volume::WriteSectors encrypts each write in a buffer that is encrypted
in place right after the copy. Allocating it as a SecureBuffer would
lock and unlock its pages on every write and cut write throughput by
about 20 to 60 percent depending on the write size, so it becomes a
Buffer that is still erased when freed.

Fixes veracrypt#1958
@flatstik
flatstik force-pushed the unix-security-fixes branch from 4ca66b3 to 7872271 Compare October 27, 2026 03:55
@flatstik

Copy link
Copy Markdown
Contributor Author

Updated after rereading the review of #1931, which asked that a directory checked by pathname not be used by pathname afterwards:

The other commits are unchanged. Retested on the OpenWrt router with the same results as in the description.

Run against the installed package as root in the Linux workflow:
locked memory and no core file for the FUSE service (veracrypt#1958), no second
mount while an emergency-detached service keeps the volume open (veracrypt#1959),
parallel mounts and an existing /tmp/.veracrypt_aux_mnt1 (veracrypt#1960), the
hidden volume protection password in the service memory (veracrypt#1961), and
the overwrite checks of --create-keyfile and --backup-headers (veracrypt#1962).
@flatstik

Copy link
Copy Markdown
Contributor Author

Added the regression tests as a sixth commit: Tests/test_unix_security_regressions.py, run in the Linux workflow after test_mount_helper.py. All checks pass on ubuntu-24.04 and ubuntu-26.04 in a run on my fork: https://github.com/flatstik/VeraCrypt/actions/runs/40448815852. The #1962 checks fail on 1.26.30, as expected.

@krustfurt

Copy link
Copy Markdown

You better split the PR into each issue otherwise @idrassi will have a hard time to under which changes belong to which code

@flatstik

Copy link
Copy Markdown
Contributor Author

Split into one PR per issue, as suggested: #1968 (#1958), #1967 (#1959), #1966 (#1960), #1964 (#1961), #1965 (#1962). Each has its fix and a regression test for it. #1962 now refuses an existing keyfile without asking unless --force is given, as suggested in the issue.

@flatstik flatstik closed this Oct 27, 2026
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.

2 participants