macOS: harden volume discovery and FUSE-T teardown

Run display discovery asynchronously and bound batched disk-image inventory
queries. Filter auxiliary mounts by basename, filesystem and owner, and
keep unresolved candidates separate from verified volumes. Allow targeted
dismounts despite incomplete discovery while requiring complete results
for slot allocation and empty-inventory decisions.

Use nonthrowing GUI snapshots with monotonic freshness, completion events
and safe window lifetimes. Suspend inactivity decisions while the snapshot
is stale, without restarting idle timers: activity counters are cumulative
per volume instance. Refresh logout targets and retry only failed ones.
Show a progress dialog for interactive unmounts, but keep automatic ones
synchronous, so a quit or logout request that arrives meanwhile is handled
afterwards rather than refused. Guard core operations against reentry and
route wait-dialog requests only from worker threads, so a main-thread
message cannot wait for itself.

Bind teardown to captured mount and service identities, including process
start time. Report a service exit that cannot be confirmed after auxiliary
mount removal as a distinct error that keeps the original details. Such
volumes are not retried, and a multi-volume unmount reports all of them
together with any other failure or a declined prompt. Warn about it after
automatic unmounts and at quit, and keep the background application until
the warning is acknowledged. Keep rollback responsive, and let services
remove their auxiliary directories without probing mounted paths. Preserve
released /control compatibility and perform best-effort cleanup for older
services.

Reap bounded subprocesses as soon as their output ends. A child that
survives SIGKILL is reaped by a later call, which starts no new child until
then. Keep subjects and subprocess command, status and error output when
formatting exceptions for wrapping and logs. Clarify discovery and rollback
diagnostics. Fix the localization-dependent busy-volume regression
assertion and extend discovery, snapshot, process identity, cleanup, GUI
lifecycle, inactivity and teardown coverage.
This commit is contained in:
Mounir IDRASSI committed 2026-09-28 03:17:28 +02:00
1 parent aedb2ef863
commit 596beb82a1
38 files changed
+2551 -311

No files matched your search

+7 -6
View File
@@ -218,21 +218,22 @@ namespace VeraCrypt
shared_ptr <VolumeInfo> CoreBase::GetMountedVolume (const VolumePath &volumePath) const
{
VolumeInfoList volumes = GetMountedVolumes (volumePath);
if (volumes.empty())
return shared_ptr <VolumeInfo> ();
else
return volumes.front();
VolumeDiscoveryResult result = GetMountedVolumesWithStatus (volumePath);
if (!result.Volumes.empty()) return result.Volumes.front();
if (!result.IsComplete()) throw VolumeDiscoveryFailed (SRC_POS, wstring (result.UnresolvedMounts.front()));
return shared_ptr <VolumeInfo> ();
}
shared_ptr <VolumeInfo> CoreBase::GetMountedVolume (VolumeSlotNumber slot) const
{
foreach (shared_ptr <VolumeInfo> volume, GetMountedVolumes())
VolumeDiscoveryResult result = GetMountedVolumesWithStatus();
foreach (shared_ptr <VolumeInfo> volume, result.Volumes)
{
if (volume->SlotNumber == slot)
return volume;
}
if (!result.IsComplete()) throw VolumeDiscoveryFailed (SRC_POS, wstring (result.UnresolvedMounts.front()));
return shared_ptr <VolumeInfo> ();
}
+6
View File
@@ -58,6 +58,12 @@ namespace VeraCrypt
virtual shared_ptr <VolumeInfo> GetMountedVolume (const VolumePath &volumePath) const;
virtual shared_ptr <VolumeInfo> GetMountedVolume (VolumeSlotNumber slot) const;
virtual VolumeInfoList GetMountedVolumes (const VolumePath &volumePath = VolumePath()) const = 0;
virtual VolumeDiscoveryResult GetMountedVolumesWithStatus (const VolumePath &volumePath = VolumePath()) const
{
VolumeDiscoveryResult result;
result.Volumes = GetMountedVolumes (volumePath);
return result;
}
virtual bool HasAdminPrivileges () const = 0;
virtual void Init () { }
virtual bool IsDeviceChangeInProgress () const { return DeviceChangeInProgress; }
+3
View File
@@ -55,6 +55,9 @@ namespace VeraCrypt
TC_EXCEPTION (MountPointUnavailable); \
TC_EXCEPTION (MountServiceIncompatible); \
TC_EXCEPTION (MountServiceCleanupFailed); \
TC_EXCEPTION (DismountServiceCleanupFailed); \
TC_EXCEPTION (MountServiceUnavailable); \
TC_EXCEPTION (VolumeDiscoveryFailed); \
TC_EXCEPTION (NoDriveLetterAvailable); \
TC_EXCEPTION (TemporaryDirectoryFailure); \
TC_EXCEPTION (UnsupportedSectorSizeHiddenVolumeProtection); \
+141 -85
View File
@@ -78,11 +78,15 @@ namespace VeraCrypt
CoreUnix::CoreUnix ()
{
signal (SIGPIPE, SIG_IGN);
char *loc = setlocale (LC_ALL, "");
if (!loc || string (loc) == "C")
setlocale (LC_ALL, "en_US.UTF-8");
// Additional read-only core instances may be created for GUI discovery.
// Locale and signal disposition are process state, not instance state.
static const bool initialized = [] () {
signal (SIGPIPE, SIG_IGN);
char *loc = setlocale (LC_ALL, "");
if (!loc || string (loc) == "C") setlocale (LC_ALL, "en_US.UTF-8");
return true;
} ();
(void) initialized;
}
CoreUnix::~CoreUnix ()
@@ -521,13 +525,42 @@ namespace VeraCrypt
return mountedFilesystems.front()->MountPoint;
}
shared_ptr <VolumeInfo> CoreUnix::ReadAuxiliaryVolumeInfo (const DirectoryPath &auxMountPoint)
{
File control;
control.Open (string (auxMountPoint) + FuseService::GetControlPath());
Buffer buffer (8192);
string data;
for (uint64 count; (count = control.Read (buffer)) != 0; )
{
if (data.size() + count > 1024 * 1024) throw ParameterTooLarge (SRC_POS);
data.append (reinterpret_cast <const char *> (buffer.Ptr()), count);
}
shared_ptr <Stream> stream (new MemoryStream (ConstBufferPtr (reinterpret_cast <const uint8 *> (data.data()), data.size())));
shared_ptr <VolumeInfo> volume = Serializable::DeserializeNew <VolumeInfo> (stream);
volume->AuxMountPoint = auxMountPoint;
return volume;
}
VolumeInfoList CoreUnix::GetMountedVolumes (const VolumePath &volumePath) const
{
VolumeInfoList volumes;
VolumeDiscoveryResult result = GetMountedVolumesWithStatus (volumePath);
if (!result.IsComplete()) throw VolumeDiscoveryFailed (SRC_POS, wstring (result.UnresolvedMounts.front()));
return result.Volumes;
}
VolumeDiscoveryResult CoreUnix::GetMountedVolumesWithStatus (const VolumePath &volumePath) const
{
VolumeDiscoveryResult result;
VolumeInfoList &volumes = result.Volumes;
foreach_ref (const MountedFilesystem &mf, GetMountedFilesystems ())
{
#ifdef TC_MACOSX
if (!mf.IsAuxiliaryMountCandidate (GetFuseMountDirPrefix(), getuid(), GetRealUserId()))
#else
if (string (mf.MountPoint).find (GetFuseMountDirPrefix()) == string::npos)
#endif
continue;
shared_ptr <VolumeInfo> mountedVol;
@@ -542,16 +575,7 @@ namespace VeraCrypt
{
try
{
shared_ptr <File> controlFile (new File);
controlFile->Open (string (mf.MountPoint) + FuseService::GetControlPath());
FileStream controlFileReader (controlFile);
string controlFileData = controlFileReader.ReadToEnd();
if (controlFileData.empty() || controlFileData.size() > 1024 * 1024)
throw ParameterIncorrect (SRC_POS);
shared_ptr <Stream> controlFileStream (new MemoryStream (ConstBufferPtr ((const uint8 *) controlFileData.data(), controlFileData.size())));
mountedVol = Serializable::DeserializeNew <VolumeInfo> (controlFileStream);
mountedVol = ReadAuxiliaryVolumeInfo (mf.MountPoint);
}
catch (const std::exception& e)
{
@@ -581,7 +605,7 @@ namespace VeraCrypt
#endif
}
if (!mountedVol)
if (!mountedVol)
{
#ifdef VC_MACOSX_FUSET
if (!volumePath.IsEmpty())
@@ -594,7 +618,10 @@ namespace VeraCrypt
SystemLog::WriteError (logMessage.str());
}
#endif
continue; // Skip to the next mounted filesystem
#ifdef TC_MACOSX
result.UnresolvedMounts.push_back (mf.MountPoint);
#endif
continue;
}
if (!volumePath.IsEmpty() && wstring (mountedVol->Path).compare (volumePath) != 0)
@@ -602,12 +629,7 @@ namespace VeraCrypt
mountedVol->AuxMountPoint = mf.MountPoint;
#ifdef TC_MACOSX
// The control file can retain a disk number after detach. Always resolve
// the image first, before consulting the mount table for that device.
try { UpdateMountedVolumeInfo (mountedVol); }
catch (exception &e) { SystemLog::WriteException (e); }
#else
#ifndef TC_MACOSX
if (mountedVol->MountPoint.IsEmpty() && !mountedVol->VirtualDevice.IsEmpty())
{
MountedFilesystemList mpl = GetMountedFilesystems (mountedVol->VirtualDevice);
@@ -626,9 +648,20 @@ namespace VeraCrypt
break;
}
return volumes;
#ifdef TC_MACOSX
UpdateMountedVolumesInfo (volumes);
#endif
return result;
}
#ifdef TC_MACOSX
void CoreUnix::UpdateMountedVolumesInfo (VolumeInfoList &volumes) const
{
foreach (shared_ptr <VolumeInfo> volume, volumes)
UpdateMountedVolumeInfo (volume);
}
#endif
gid_t CoreUnix::GetRealGroupId () const
{
const char *env = getenv ("SUDO_GID");
@@ -1146,7 +1179,6 @@ namespace VeraCrypt
mountDirectory.push_back ('\0');
throw_sys_if (mkdtemp (&mountDirectory[0]) == NULL);
fuseMountPoint = &mountDirectory[0];
throw_sys_if (chmod (fuseMountPoint.c_str(), S_IRUSR | S_IXUSR) == -1);
#else
// Find a free mount point for FUSE service
MountedFilesystemList mountedFilesystems = GetMountedFilesystems ();
@@ -1194,6 +1226,9 @@ namespace VeraCrypt
try
{
#ifdef VC_MACOSX_FUSET
throw_sys_if (chmod (fuseMountPoint.c_str(), S_IRUSR | S_IXUSR) == -1);
#endif
#ifdef TC_MACOSX
// FUSE canonicalizes its mount path. Give hdiutil the same path so
// its inventory identifies the image even when TMPDIR is a symlink.
@@ -1277,34 +1312,46 @@ namespace VeraCrypt
}
#endif
}
catch (...)
{
try
#ifdef VC_MACOSX_FUSET
shared_ptr <VolumeInfo> mountedVolume = GetMountedVolume (*options.Path);
if (mountedVolume)
{
VolumeInfoList mountedVolumes = GetMountedVolumes (*options.Path);
if (mountedVolumes.size() > 0)
if (mountedVolume->SerialInstanceNumber != fuseServiceSerialInstanceNumber)
throw ParameterIncorrect (SRC_POS);
if (!mountedVirtualDevice.IsEmpty())
{
shared_ptr <VolumeInfo> mountedVolume (mountedVolumes.front());
DismountVolume (mountedVolume);
if (mountedVolume->VirtualDevice.IsEmpty())
mountedVolume->VirtualDevice = mountedVirtualDevice;
if (!options.NoFilesystem && mountedVolume->MountPoint.IsEmpty())
{
for (int mountPointRetries = 20; mountPointRetries > 0; --mountPointRetries)
{
try
{
mountedVolume->MountPoint = GetDeviceMountPoint (mountedVirtualDevice);
if (!mountedVolume->MountPoint.IsEmpty())
break;
}
catch (...) { }
Thread::Sleep (500);
}
}
}
}
catch (...) { }
throw;
}
#ifdef VC_MACOSX_FUSET
VolumeInfoList mountedVolumes = GetMountedVolumes (*options.Path);
shared_ptr <VolumeInfo> mountedVolume;
if (mountedVolumes.size() == 1)
{
mountedVolume = mountedVolumes.front();
if (!mountedVirtualDevice.IsEmpty())
else if (!mountedVirtualDevice.IsEmpty())
{
if (mountedVolume->VirtualDevice.IsEmpty())
mountedVolume->VirtualDevice = mountedVirtualDevice;
mountedVolume.reset (new VolumeInfo);
mountedVolume->Set (*volume);
mountedVolume->ProgramVersion = VERSION_NUM;
mountedVolume->SlotNumber = options.SlotNumber;
mountedVolume->AuxMountPoint = fuseMountPoint;
mountedVolume->VirtualDevice = mountedVirtualDevice;
if (!options.NoFilesystem && mountedVolume->MountPoint.IsEmpty())
mountedVolume->SerialInstanceNumber = fuseServiceSerialInstanceNumber;
if (!options.NoFilesystem)
{
for (int mountPointRetries = 20; mountPointRetries > 0; --mountPointRetries)
{
@@ -1320,47 +1367,56 @@ namespace VeraCrypt
}
}
}
}
else if (!mountedVirtualDevice.IsEmpty())
{
mountedVolume.reset (new VolumeInfo);
mountedVolume->Set (*volume);
mountedVolume->ProgramVersion = VERSION_NUM;
mountedVolume->SlotNumber = options.SlotNumber;
mountedVolume->AuxMountPoint = fuseMountPoint;
mountedVolume->VirtualDevice = mountedVirtualDevice;
mountedVolume->SerialInstanceNumber = fuseServiceSerialInstanceNumber;
if (!options.NoFilesystem)
{
for (int mountPointRetries = 20; mountPointRetries > 0; --mountPointRetries)
{
try
{
mountedVolume->MountPoint = GetDeviceMountPoint (mountedVirtualDevice);
if (!mountedVolume->MountPoint.IsEmpty())
break;
}
catch (...) { }
Thread::Sleep (500);
}
}
}
#else
VolumeInfoList mountedVolumes = GetMountedVolumes (*options.Path);
shared_ptr <VolumeInfo> mountedVolume;
if (mountedVolumes.size() == 1)
mountedVolume = mountedVolumes.front();
shared_ptr <VolumeInfo> mountedVolume = GetMountedVolume (*options.Path);
#endif
if (!mountedVolume)
throw ParameterIncorrect (SRC_POS);
if (!mountedVolume)
throw ParameterIncorrect (SRC_POS);
VolumeEventArgs eventArgs (mountedVolume);
VolumeMountedEvent.Raise (eventArgs);
VolumeEventArgs eventArgs (mountedVolume);
VolumeMountedEvent.Raise (eventArgs);
return mountedVolume;
return mountedVolume;
}
catch (...)
{
#ifdef VC_MACOSX_FUSET
// Enumeration may itself have failed. Cleanup belongs to the instance
// we just started, not a later volume discovered by path or slot.
try { throw; }
catch (exception &e) { SystemLog::WriteException (e); }
catch (...) { }
try
{
shared_ptr <VolumeInfo> cleanupVolume (new VolumeInfo);
cleanupVolume->Set (*volume);
cleanupVolume->ProgramVersion = VERSION_NUM;
cleanupVolume->SlotNumber = options.SlotNumber;
cleanupVolume->SerialInstanceNumber = fuseServiceSerialInstanceNumber;
cleanupVolume->AuxMountPoint = fuseMountPoint;
DismountVolume (cleanupVolume);
}
catch (DismountServiceCleanupFailed &) { throw; }
catch (exception &e)
{
SystemLog::WriteException (e);
throw MountServiceCleanupFailed (SRC_POS, StringConverter::ToWide (fuseMountPoint) + L"\n" + StringConverter::ToExceptionString (e));
}
catch (...)
{
throw MountServiceCleanupFailed (SRC_POS, StringConverter::ToWide (fuseMountPoint));
}
#else
try
{
shared_ptr <VolumeInfo> mountedVolume = GetMountedVolume (*options.Path);
if (mountedVolume)
DismountVolume (mountedVolume);
}
catch (...) { }
#endif
throw;
}
}
DevicePath CoreUnix::MountAuxVolumeImage (const DirectoryPath &auxMountPoint, const MountOptions &options) const
+5
View File
@@ -39,6 +39,7 @@ namespace VeraCrypt
virtual int GetOSMajorVersion () const { throw NotApplicable (SRC_POS); }
virtual int GetOSMinorVersion () const { throw NotApplicable (SRC_POS); }
virtual VolumeInfoList GetMountedVolumes (const VolumePath &volumePath = VolumePath()) const;
virtual VolumeDiscoveryResult GetMountedVolumesWithStatus (const VolumePath &volumePath = VolumePath()) const;
virtual bool IsDevicePresent (const DevicePath &device) const { throw NotApplicable (SRC_POS); }
virtual bool IsInPortableMode () const { return false; }
virtual bool IsMountPointAvailable (const DirectoryPath &mountPoint) const;
@@ -55,6 +56,7 @@ namespace VeraCrypt
virtual bool IsDirectoryOnUserPath(const DirectoryPath &directory) const;
protected:
static shared_ptr <VolumeInfo> ReadAuxiliaryVolumeInfo (const DirectoryPath &auxMountPoint);
virtual DevicePath AttachFileToLoopDevice (const FilePath &filePath, bool readOnly) const { throw NotApplicable (SRC_POS); }
virtual void DetachLoopDevice (const DevicePath &devicePath) const { throw NotApplicable (SRC_POS); }
virtual void DismountNativeVolume (shared_ptr <VolumeInfo> mountedVolume) const { throw NotApplicable (SRC_POS); }
@@ -75,6 +77,9 @@ namespace VeraCrypt
virtual DevicePath MountAuxVolumeImage (const DirectoryPath &auxMountPoint, const MountOptions &options) const;
virtual void MountVolumeNative (shared_ptr <Volume> volume, MountOptions &options, const DirectoryPath &auxMountPoint) const { throw NotApplicable (SRC_POS); }
virtual void UpdateMountedVolumeInfo (shared_ptr <VolumeInfo> mountedVolume) const { (void) mountedVolume; }
#ifdef TC_MACOSX
virtual void UpdateMountedVolumesInfo (VolumeInfoList &volumes) const;
#endif
#ifdef TC_LINUX
string DetectFilesystemType (const DevicePath &devicePath) const;
bool IsFilesystemTypeRegistered (const string &filesystemType) const;
+10
View File
@@ -153,7 +153,12 @@ namespace VeraCrypt
MountedFilesystemList CoreFreeBSD::GetMountedFilesystems (const DevicePath &devicePath, const DirectoryPath &mountPoint) const
{
#ifdef TC_MACOSX
// A detached discovery worker can finish during static teardown.
static Mutex &mutex = *new Mutex;
#else
static Mutex mutex;
#endif
ScopeLock sl (mutex);
struct statfs *sysMountList;
@@ -175,6 +180,11 @@ namespace VeraCrypt
mf->MountPoint = DirectoryPath (sysMountList[i].f_mntonname);
mf->Type = sysMountList[i].f_fstypename;
#ifdef TC_MACOSX
mf->Owner = sysMountList[i].f_owner;
mf->MountId[0] = sysMountList[i].f_fsid.val[0];
mf->MountId[1] = sysMountList[i].f_fsid.val[1];
#endif
if ((devicePath.IsEmpty() || devicePath == mf->Device) && (mountPoint.IsEmpty() || mountPoint == mf->MountPoint))
mountedFilesystems.push_back (mf);
+150 -72
View File
@@ -170,66 +170,84 @@ namespace VeraCrypt
return ExtractDeviceAndMountPointFromEntities ((CFArrayRef) entities, device, mountPoint);
}
static bool FindDiskImageInfoByImagePath (const string &imagePath, DevicePath &device, DirectoryPath &mountPoint)
static string GetDiskImageInventory ()
{
list <string> args;
args.push_back ("info");
args.push_back ("-plist");
return Process::ExecuteBounded ("/usr/bin/hdiutil", args, 2000);
}
string xml = Process::Execute ("/usr/bin/hdiutil", args);
static bool FindDiskImageInfoByImagePath (const string &xml, const string &imagePath, DevicePath &device, DirectoryPath &mountPoint)
{
string canonicalImagePath = CanonicalizeDiskImagePath (imagePath);
// TMPDIR aliases change the parent path, not VeraCrypt's auxiliary
// directory name or the image filename appended to it.
size_t imageSeparator = canonicalImagePath.find_last_of ('/');
if (imageSeparator == string::npos || imageSeparator == 0)
throw ParameterIncorrect (SRC_POS);
size_t auxiliarySeparator = canonicalImagePath.find_last_of ('/', imageSeparator - 1);
if (imageSeparator == string::npos || auxiliarySeparator == string::npos)
if (auxiliarySeparator == string::npos)
throw ParameterIncorrect (SRC_POS);
const string imageSuffix = canonicalImagePath.substr (auxiliarySeparator);
CFHolder plist (ParsePropertyList (xml));
if (!plist.Get() || CFGetTypeID (plist.Get()) != CFDictionaryGetTypeID())
throw ParameterIncorrect (SRC_POS);
CFTypeRef images = CFDictionaryGetValue ((CFDictionaryRef) plist.Get(), CFSTR ("images")); // borrowed
CFTypeRef images = CFDictionaryGetValue ((CFDictionaryRef) plist.Get(), CFSTR ("images"));
if (!images || CFGetTypeID (images) != CFArrayGetTypeID())
throw ParameterIncorrect (SRC_POS);
CFArrayRef imageArray = (CFArrayRef) images;
CFIndex count = CFArrayGetCount (imageArray);
for (CFIndex i = 0; i < count; ++i)
bool uncertain = false;
// Positive exact matches take precedence over unreadable unrelated images.
// Only the second pass touches paths used by older TMPDIR aliases.
for (int pass = 0; pass < 2; ++pass)
{
CFTypeRef image = CFArrayGetValueAtIndex (imageArray, i); // borrowed
if (!image || CFGetTypeID (image) != CFDictionaryGetTypeID())
throw ParameterIncorrect (SRC_POS);
CFDictionaryRef imageDict = (CFDictionaryRef) image;
string currentImagePath = CFDictionaryGetStdString (imageDict, "image-path");
if (currentImagePath.empty())
throw ParameterIncorrect (SRC_POS);
if (currentImagePath != canonicalImagePath)
for (CFIndex i = 0; i < CFArrayGetCount (imageArray); ++i)
{
// Older clients attached through TMPDIR aliases. Resolve those too,
// but do not access unrelated disk images (which may be offline).
if (currentImagePath[0] != '/' || currentImagePath.size() < imageSuffix.size()
|| currentImagePath.compare (currentImagePath.size() - imageSuffix.size(), imageSuffix.size(), imageSuffix) != 0)
CFTypeRef image = CFArrayGetValueAtIndex (imageArray, i);
if (!image || CFGetTypeID (image) != CFDictionaryGetTypeID())
{
uncertain = true;
continue;
// Failure to resolve a candidate is not proof that our image is gone.
if (CanonicalizeDiskImagePath (currentImagePath) != canonicalImagePath)
}
CFDictionaryRef imageDict = (CFDictionaryRef) image;
string currentImagePath = CFDictionaryGetStdString (imageDict, "image-path");
if (currentImagePath.empty())
{
uncertain = true;
continue;
}
if (pass == 0)
{
if (currentImagePath != canonicalImagePath) continue;
}
else
{
if (currentImagePath == canonicalImagePath || currentImagePath[0] != '/'
|| currentImagePath.size() < imageSuffix.size()
|| currentImagePath.compare (currentImagePath.size() - imageSuffix.size(), imageSuffix.size(), imageSuffix) != 0)
continue;
try
{
if (CanonicalizeDiskImagePath (currentImagePath) != canonicalImagePath) continue;
}
catch (exception &)
{
uncertain = true;
continue;
}
}
CFTypeRef entities = CFDictionaryGetValue (imageDict, CFSTR ("system-entities"));
if (!entities || CFGetTypeID (entities) != CFArrayGetTypeID()
|| !ExtractDeviceAndMountPointFromEntities ((CFArrayRef) entities, device, mountPoint))
throw ParameterIncorrect (SRC_POS);
return true;
}
// A missing image and an unreadable inventory are different states.
// Callers must never fall back to a cached disk number on an error.
CFTypeRef entities = CFDictionaryGetValue (imageDict, CFSTR ("system-entities")); // borrowed
if (!entities || CFGetTypeID (entities) != CFArrayGetTypeID())
throw ParameterIncorrect (SRC_POS);
if (!ExtractDeviceAndMountPointFromEntities ((CFArrayRef) entities, device, mountPoint))
throw ParameterIncorrect (SRC_POS);
return true;
}
// A partial inventory cannot establish absence. In particular, never
// substitute a cached BSD disk number when ownership is unknown.
if (uncertain) throw ParameterIncorrect (SRC_POS);
return false;
}
@@ -268,6 +286,21 @@ namespace VeraCrypt
{
}
shared_ptr <VolumeInfo> CoreMacOSX::ValidateMountedVolume (shared_ptr <VolumeInfo> volume) const
{
if (!volume || volume->AuxMountPoint.IsEmpty()) throw ParameterIncorrect (SRC_POS);
MountedFilesystemList before = GetMountedFilesystems (DevicePath(), volume->AuxMountPoint);
if (before.size() != 1) throw MountServiceUnavailable (SRC_POS, wstring (volume->AuxMountPoint));
shared_ptr <VolumeInfo> current = ReadAuxiliaryVolumeInfo (volume->AuxMountPoint);
MountedFilesystemList after = GetMountedFilesystems (DevicePath(), volume->AuxMountPoint);
if (after.size() != 1 || before.front()->MountId[0] != after.front()->MountId[0]
|| before.front()->MountId[1] != after.front()->MountId[1]
|| current->SerialInstanceNumber != volume->SerialInstanceNumber
|| current->SlotNumber != volume->SlotNumber || current->Path != volume->Path)
throw MountServiceUnavailable (SRC_POS, wstring (volume->AuxMountPoint));
return current;
}
shared_ptr <VolumeInfo> CoreMacOSX::DismountVolume (shared_ptr <VolumeInfo> mountedVolume, bool ignoreOpenFiles, bool syncVolumeInfo)
{
if (!mountedVolume || mountedVolume->AuxMountPoint.IsEmpty())
@@ -277,19 +310,20 @@ namespace VeraCrypt
// the disk image. Retain its identity independently of SMB metadata.
const FuseService::DismountRequest dismountRequest = FuseService::PrepareDismount (mountedVolume->AuxMountPoint,
mountedVolume->SerialInstanceNumber, mountedVolume->SlotNumber, ignoreOpenFiles);
#else
mountedVolume = ValidateMountedVolume (mountedVolume);
#endif
// Resolve ownership immediately before detach, including retries after a
// busy auxiliary unmount or an external eject. BSD disk numbers are reused.
#ifdef VC_MACOSX_FUSET
if (!FuseService::IsDismountMountPresent (dismountRequest))
if (FuseService::IsDismountMountPresent (dismountRequest))
{
if (!dismountRequest.LegacyService)
FuseService::WaitForDismount (FuseService::RequestDismount (dismountRequest), mountedVolume->AuxMountPoint, mountedVolume->SlotNumber);
return mountedVolume;
}
#endif
UpdateMountedVolumeInfo (mountedVolume);
#ifndef VC_MACOSX_FUSET
ValidateMountedVolume (mountedVolume);
#endif
if (!mountedVolume->VirtualDevice.IsEmpty() && mountedVolume->VirtualDevice.IsBlockDevice()
#ifdef VC_MACOSX_FUSET
@@ -329,18 +363,31 @@ namespace VeraCrypt
if (syncVolumeInfo || mountedVolume->Protection == VolumeProtection::HiddenVolumeReadOnly)
{
sync();
VolumeInfoList ml = GetMountedVolumes (mountedVolume->Path);
if (ml.size() > 0 && ml.front()->SerialInstanceNumber == mountedVolume->SerialInstanceNumber)
mountedVolume = ml.front();
// Refresh only the captured service. Other users' auxiliary mounts
// must not interrupt teardown after the disk image has been detached.
mountedVolume = ValidateMountedVolume (mountedVolume);
}
#ifdef VC_MACOSX_FUSET
}
// The service unmounts SMB while its FUSE loop can still answer requests.
if (!dismountRequest.LegacyService)
{
pid_t fuseServiceProcessId = FuseService::RequestDismount (dismountRequest);
FuseService::WaitForDismount (fuseServiceProcessId, mountedVolume->AuxMountPoint, mountedVolume->SlotNumber);
try
{
if (FuseService::IsDismountMountPresent (dismountRequest))
FuseService::RequestDismount (dismountRequest);
FuseService::WaitForDismount (dismountRequest.ProcessId, mountedVolume->AuxMountPoint, mountedVolume->SlotNumber, 10000, dismountRequest.ProcessStartTime);
}
catch (DismountServiceCleanupFailed &) { throw; }
catch (exception &e)
{
// Without SMB, the volume can no longer be discovered or retried.
// Report the unconfirmed service exit with the original details.
if (FuseService::IsDismountMountPresent (dismountRequest))
throw;
throw DismountServiceCleanupFailed (SRC_POS, StringConverter::ToExceptionString (e));
}
}
else
FuseService::DismountLegacy (dismountRequest);
@@ -365,41 +412,70 @@ namespace VeraCrypt
}
#endif
try
{
mountedVolume->AuxMountPoint.Delete();
}
catch (...) { }
// Current services remove their original directory. Older development
// services may leave it behind; rmdir only removes an empty directory
// and cannot follow a replacement symlink or remove a mounted filesystem.
rmdir (string (mountedVolume->AuxMountPoint).c_str());
return mountedVolume;
}
void CoreMacOSX::UpdateMountedVolumeInfo (shared_ptr <VolumeInfo> mountedVolume) const
static void ClearDiskImageInfo (shared_ptr <VolumeInfo> volume)
{
if (!mountedVolume)
return;
volume->VirtualDevice = DevicePath();
volume->MountPoint = DirectoryPath();
volume->Discovery = VolumeInfo::DiscoveryUnknown;
}
// Clear stale metadata even if discovery fails. Destructive callers get
// the exception; enumeration can still show the auxiliary mount for retry.
mountedVolume->VirtualDevice = DevicePath();
mountedVolume->MountPoint = DirectoryPath();
if (mountedVolume->AuxMountPoint.IsEmpty())
return;
DevicePath recoveredVirtualDevice;
DirectoryPath recoveredMountPoint;
if (!FindDiskImageInfoByImagePath (string (mountedVolume->AuxMountPoint) + FuseService::GetVolumeImagePath(), recoveredVirtualDevice, recoveredMountPoint))
return;
mountedVolume->VirtualDevice = recoveredVirtualDevice;
mountedVolume->MountPoint = recoveredMountPoint;
void CoreMacOSX::UpdateMountedVolumesInfo (VolumeInfoList &volumes) const
{
foreach (shared_ptr <VolumeInfo> volume, volumes)
ClearDiskImageInfo (volume);
if (volumes.empty()) return;
try
{
// One inventory per enumeration, regardless of the volume count.
const string inventory = GetDiskImageInventory();
foreach (shared_ptr <VolumeInfo> volume, volumes)
{
try { UpdateMountedVolumeInfo (volume, inventory); }
catch (exception &e) { SystemLog::WriteException (e); }
}
}
catch (exception &e) { SystemLog::WriteException (e); }
}
if (mountedVolume->MountPoint.IsEmpty() && !mountedVolume->VirtualDevice.IsEmpty())
void CoreMacOSX::UpdateMountedVolumeInfo (shared_ptr <VolumeInfo> volume) const
{
if (!volume) return;
ClearDiskImageInfo (volume);
try { UpdateMountedVolumeInfo (volume, GetDiskImageInventory()); }
catch (exception &e)
{
throw VolumeDiscoveryFailed (SRC_POS, StringConverter::ToExceptionString (e));
}
}
void CoreMacOSX::UpdateMountedVolumeInfo (shared_ptr <VolumeInfo> volume, const string &inventory) const
{
ClearDiskImageInfo (volume);
if (volume->AuxMountPoint.IsEmpty()) throw ParameterIncorrect (SRC_POS);
DevicePath device;
DirectoryPath mountPoint;
if (!FindDiskImageInfoByImagePath (inventory, string (volume->AuxMountPoint) + FuseService::GetVolumeImagePath(), device, mountPoint))
{
volume->Discovery = VolumeInfo::ImageAbsent;
return;
}
volume->VirtualDevice = device;
volume->MountPoint = mountPoint;
volume->Discovery = VolumeInfo::ImageAttached;
if (volume->MountPoint.IsEmpty())
{
try
{
MountedFilesystemList mountedFilesystems = GetMountedFilesystems (mountedVolume->VirtualDevice);
if (mountedFilesystems.size() > 0)
mountedVolume->MountPoint = mountedFilesystems.front()->MountPoint;
MountedFilesystemList filesystems = GetMountedFilesystems (device);
if (!filesystems.empty()) volume->MountPoint = filesystems.front()->MountPoint;
}
catch (...) { }
}
@@ -438,7 +514,9 @@ namespace VeraCrypt
void CoreMacOSX::CheckFilesystem (shared_ptr <VolumeInfo> mountedVolume, bool repair) const
{
mountedVolume = ValidateMountedVolume (mountedVolume);
UpdateMountedVolumeInfo (mountedVolume);
ValidateMountedVolume (mountedVolume);
// 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
+3
View File
@@ -31,10 +31,13 @@ namespace VeraCrypt
protected:
virtual DevicePath MountAuxVolumeImage (const DirectoryPath &auxMountPoint, const MountOptions &options) const;
virtual void UpdateMountedVolumeInfo (shared_ptr <VolumeInfo> mountedVolume) const;
virtual void UpdateMountedVolumesInfo (VolumeInfoList &volumes) const;
private:
CoreMacOSX (const CoreMacOSX &);
CoreMacOSX &operator= (const CoreMacOSX &);
void UpdateMountedVolumeInfo (shared_ptr <VolumeInfo> volume, const string &inventory) const;
shared_ptr <VolumeInfo> ValidateMountedVolume (shared_ptr <VolumeInfo> volume) const;
};
}
+12
View File
@@ -20,6 +20,18 @@ namespace VeraCrypt
struct MountedFilesystem
{
public:
#ifdef TC_MACOSX
MountedFilesystem () : Owner (static_cast <uid_t> (-1)) { MountId[0] = MountId[1] = 0; }
bool IsAuxiliaryMountCandidate (const string &prefix, uid_t userId, uid_t realUserId) const
{
const string name = MountPoint.ToBaseName();
return name.compare (0, prefix.size(), prefix) == 0
&& (Owner == userId || Owner == 0 || (userId == 0 && Owner == realUserId))
&& (Type == "smbfs" || Type == "nfs" || Type == "macfuse" || Type == "osxfuse" || Type == "fusefs");
}
uid_t Owner;
int32 MountId[2];
#endif
DevicePath Device;
DirectoryPath MountPoint;
string Type;