mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2026-10-06 00:56:35 -05:00
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:
38 files changed
+2551
-311
No files matched your search
@@ -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> ();
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user