1
0
mirror of https://github.com/veracrypt/VeraCrypt.git synced 2026-07-05 20:48:00 -05:00

macOS: honor the CheckFilesystem repair flag (#1791)

CoreMacOSX::CheckFilesystem() ignored both its mountedVolume and repair
arguments and always just launched Disk Utility.app, so the "Check
Filesystem" and "Repair Filesystem" menu items behaved identically and
neither acted on the mounted volume. On Linux/BSD the same operation runs
fsck and honors the flag (passing -n only when repair is false).

Run diskutil on the VeraCrypt virtual device, choosing verifyVolume or
repairVolume per the flag (diskutil unmounts the inner filesystem itself
as needed). The Core layer has no GUI, so the result is shown in a
Terminal window via a temporary .command script; it falls back to
launching Disk Utility.app when no virtual device is available.

Run the macOS check in the unprivileged application process. VeraCrypt does
not need to create or launch the helper script from the elevated core
service: diskutil operates on the mounted virtual device and macOS handles
any device authorization requirements. Once a device-hosted mount has
started the elevated core service, every later service request is routed to
that root process. There it would create the helper script as root (0700)
and open a Terminal in the GUI session that the user could neither read nor
execute. CoreServiceProxy::CheckFilesystem now invokes the core
implementation directly on macOS instead of sending a service request, so
the script is always owned by the GUI user.

The device path is strictly validated as /dev/[r]diskN[sM] before being
single-quoted into the command. The helper script is created securely in
the per-user temp directory via mkstemps() (atomic O_EXCL/0600, fchmod
0700 by descriptor, close() checked for deferred write errors, unlinked on
any failure) rather than at a predictable, enumerable path in the
world-writable /tmp, guarding against a symlink/race on the executed
script. A trap removes the script on exit even if the window is closed
early, and it is also unlinked if launching Terminal fails. The script
captures $? so diskutil's result, including failures, is shown before the
script exits.

Replace the macOS pre-check message (which still told the user Disk
Utility would open and to pick Verify/Repair manually) with check- and
repair-specific text describing the new automatic diskutil flow.

Seed the two new strings into all translation files with the English
text so the XML key-completeness check passes; localization can follow.

Co-authored-by: Damian Rickard <damian@rickard.us>
This commit is contained in:
damianrickard
2026-06-22 01:05:45 -04:00
committed by GitHub
parent 94fabdd0b7
commit 355b61f41a
46 changed files with 242 additions and 1 deletions
+2
View File
@@ -1498,6 +1498,8 @@
<entry lang="en" key="LINUX_REMOUNT_BECAUSEOF_SETTING">Please note that any currently mounted volumes need to be remounted before they can use this setting.</entry>
<entry lang="en" key="LINUX_UNKNOWN_EXC_OCCURRED">Unknown exception occurred.</entry>
<entry lang="en" key="LINUX_FIRST_AID">"Disk Utility will be launched after you press 'OK'.\n\nPlease select your volume in the Disk Utility window and press 'Verify Disk' or 'Repair Disk' button on the 'First Aid' page.</entry>
<entry lang="en" key="MACOSX_CHECK_FILESYS">A Terminal window will open after you press 'OK' and check the file system on the selected VeraCrypt volume using 'diskutil'. The result will be shown in that window.\n\nIf the check cannot be started, Disk Utility will be launched instead.</entry>
<entry lang="en" key="MACOSX_REPAIR_FILESYS">A Terminal window will open after you press 'OK' and attempt to repair the file system on the selected VeraCrypt volume using 'diskutil'. The result will be shown in that window.\n\nIf the repair cannot be started, Disk Utility will be launched instead.</entry>
<entry lang="en" key="LINUX_MOUNT_ALL_DEV">Mount All Devices</entry>
<entry lang="en" key="LINUX_ERROR_LOADING_CONFIG">Error while loading configuration files located in </entry>
<entry lang="en" key="LINUX_SELECT_FREE_SLOT">Please select a free drive slot from the list.</entry>
+10
View File
@@ -27,7 +27,17 @@ namespace VeraCrypt
virtual void CheckFilesystem (shared_ptr <VolumeInfo> mountedVolume, bool repair) const
{
#ifdef TC_MACOSX
// On macOS the check runs diskutil against the VeraCrypt virtual device
// and shows the result in a Terminal window; neither needs root. Run it
// in this (unprivileged) application process directly: once a device-hosted
// mount has started the elevated core service, every request is routed to
// that root process, which would create the helper script as root and open
// a Terminal the GUI-session user cannot read or execute.
T::CheckFilesystem (mountedVolume, repair);
#else
CoreService::RequestCheckFilesystem (mountedVolume, repair);
#endif
}
virtual void DismountFilesystem (const DirectoryPath &mountPoint, bool force) const
+145
View File
@@ -11,7 +11,9 @@
*/
#include <fstream>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>
#include <CoreFoundation/CoreFoundation.h>
@@ -382,8 +384,151 @@ namespace VeraCrypt
}
}
// Strict /dev/diskN or /dev/rdiskN (optionally sN) device-node check so a value
// can be embedded safely in a generated shell command.
static bool IsPlainDiskDevicePath (const string &path)
{
size_t index;
if (path.compare (0, 10, "/dev/rdisk") == 0)
index = 10;
else if (path.compare (0, 9, "/dev/disk") == 0)
index = 9;
else
return false;
size_t digitsStart = index;
while (index < path.size() && path[index] >= '0' && path[index] <= '9')
++index;
if (index == digitsStart)
return false;
if (index == path.size())
return true;
if (path[index++] != 's')
return false;
size_t sliceStart = index;
while (index < path.size() && path[index] >= '0' && path[index] <= '9')
++index;
return index > sliceStart && index == path.size();
}
void CoreMacOSX::CheckFilesystem (shared_ptr <VolumeInfo> mountedVolume, bool repair) const
{
// Honor the check-vs-repair distinction by running diskutil on the VeraCrypt
// virtual device (diskutil unmounts the inner filesystem itself as needed).
// The Core layer has no GUI, so results are shown in a Terminal window via a
// temporary .command script. Falls back to launching Disk Utility.app.
if (mountedVolume && !mountedVolume->VirtualDevice.IsEmpty())
{
const string device = mountedVolume->VirtualDevice;
if (IsPlainDiskDevicePath (device))
{
try
{
const string verb = repair ? "repairVolume" : "verifyVolume";
// Create the script securely: a predictable name in the
// world-writable /tmp invites a symlink/race attack (the file is
// later executed, and this path can run elevated). Use the
// per-user temp directory (owned, 0700) and mkstemps(), which
// creates the file atomically with O_EXCL while keeping the
// ".command" suffix that makes /usr/bin/open launch Terminal.
string tempDir;
{
char dirBuf[MAXPATHLEN];
size_t n = confstr (_CS_DARWIN_USER_TEMP_DIR, dirBuf, sizeof (dirBuf));
if (n > 0 && n <= sizeof (dirBuf))
tempDir = dirBuf;
else if (const char *t = getenv ("TMPDIR"))
tempDir = t;
else
tempDir = "/tmp/";
}
if (tempDir.empty() || tempDir[tempDir.size() - 1] != '/')
tempDir += '/';
const char suffix[] = ".command";
string templ = tempDir + "VeraCrypt-fsck-XXXXXXXX" + suffix;
vector <char> templBuf (templ.begin(), templ.end());
templBuf.push_back ('\0');
int fd = mkstemps (&templBuf[0], static_cast <int> (sizeof (suffix) - 1));
if (fd == -1)
throw ParameterIncorrect (SRC_POS);
const string scriptPath (&templBuf[0]);
try
{
// Always show diskutil's result (including failures) and
// pause; this is why the script captures $? rather than
// using "set -e", which would abort before the prompt.
const string contents =
string ("#!/bin/sh\n")
+ "trap 'rm -f \"$0\"' EXIT\n" // remove the script even if the window is closed early
+ "/usr/sbin/diskutil " + verb + " '" + device + "'\n"
+ "status=$?\n"
+ "echo\n"
+ "echo 'Press Enter to close this window.'\n"
+ "read dummy\n"
+ "exit $status\n";
size_t off = 0;
while (off < contents.size())
{
ssize_t written = write (fd, contents.data() + off, contents.size() - off);
if (written < 0)
{
if (errno == EINTR)
continue;
throw ParameterIncorrect (SRC_POS);
}
off += static_cast <size_t> (written);
}
// fchmod on the fd (not the path) keeps this race-free.
if (fchmod (fd, 0700) != 0)
throw ParameterIncorrect (SRC_POS);
if (close (fd) != 0) // surfaces deferred write errors
throw ParameterIncorrect (SRC_POS);
fd = -1;
}
catch (...)
{
if (fd != -1)
close (fd);
unlink (scriptPath.c_str());
throw;
}
try
{
list <string> openArgs;
openArgs.push_back (scriptPath);
Process::Execute ("/usr/bin/open", openArgs);
}
catch (...)
{
// open failed before Terminal could take ownership of the
// script (the EXIT trap never runs), so remove it here
// rather than leaking it into the temp directory.
unlink (scriptPath.c_str());
throw;
}
return;
}
catch (...)
{
// Fall back to Disk Utility below.
}
}
}
list <string> args;
struct stat sb;
+1 -1
View File
@@ -228,7 +228,7 @@ namespace VeraCrypt
L"cmd.exe", args.c_str(), nullptr, SW_SHOW);
#else
# ifdef TC_MACOSX
Gui->ShowInfo (LangString["LINUX_FIRST_AID"]);
Gui->ShowInfo (LangString[repair ? "MACOSX_REPAIR_FILESYS" : "MACOSX_CHECK_FILESYS"]);
# endif
Core->CheckFilesystem (selectedVolume, repair);
UpdateVolumeList();