From deb7f55bfba0b4add55dfee6d8d851808375fa7a Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Wed, 22 Apr 2026 09:27:16 +0200 Subject: [PATCH] macOS: stabilize FUSE-T SMB mount metadata Make /aux-device-info readable for SMB, verify that FUSE records hdiutil device info after mount and recover missing virtual devices from hdiutil before dismounting auxiliary mounts. --- src/Core/Unix/CoreUnix.cpp | 35 ++++-- src/Core/Unix/MacOSX/CoreMacOSX.cpp | 184 +++++++++++++++++++++++++++- src/Driver/Fuse/FuseService.cpp | 71 ++++++++++- src/Driver/Fuse/FuseService.h | 1 + 4 files changed, 273 insertions(+), 18 deletions(-) diff --git a/src/Core/Unix/CoreUnix.cpp b/src/Core/Unix/CoreUnix.cpp index c41594bf..098e9cb4 100644 --- a/src/Core/Unix/CoreUnix.cpp +++ b/src/Core/Unix/CoreUnix.cpp @@ -350,7 +350,7 @@ namespace VeraCrypt // The list is already filtered to VeraCrypt auxiliary mounts; in // FUSE-T builds, the mount table device name varies by backend. #ifdef VC_MACOSX_FUSET - int controlFileRetries = 10; // 10 retries with 500ms sleep each, total 5 seconds + int controlFileRetries = volumePath.IsEmpty() ? 1 : 10; // Up to 10 attempts with 500ms sleeps for specific volume lookups string controlFileError; while (!mountedVol && (controlFileRetries-- > 0)) #endif @@ -372,9 +372,12 @@ namespace VeraCrypt { #ifdef VC_MACOSX_FUSET controlFileError = StringConverter::ToSingle (StringConverter::ToExceptionString (e)); - // FUSE-T's SMB backend can briefly expose the auxiliary mount - // before the control file is readable and deserializable. - Thread::Sleep (500); + if (controlFileRetries > 0) + { + // FUSE-T's SMB backend can briefly expose the auxiliary mount + // before the control file is readable and deserializable. + Thread::Sleep (500); + } #else (void) e; #endif @@ -383,9 +386,12 @@ namespace VeraCrypt catch (...) { controlFileError = "unknown exception"; - // FUSE-T's SMB backend can briefly expose the auxiliary mount - // before the control file is readable and deserializable. - Thread::Sleep (500); + if (controlFileRetries > 0) + { + // FUSE-T's SMB backend can briefly expose the auxiliary mount + // before the control file is readable and deserializable. + Thread::Sleep (500); + } } #endif } @@ -393,12 +399,15 @@ namespace VeraCrypt if (!mountedVol) { #ifdef VC_MACOSX_FUSET - stringstream logMessage; - logMessage << "Failed to read VeraCrypt auxiliary mount control file after retries: " - << string (mf.MountPoint) << FuseService::GetControlPath(); - if (!controlFileError.empty()) - logMessage << ": " << controlFileError; - SystemLog::WriteError (logMessage.str()); + if (!volumePath.IsEmpty()) + { + stringstream logMessage; + logMessage << "Failed to read VeraCrypt auxiliary mount control file after retries: " + << string (mf.MountPoint) << FuseService::GetControlPath(); + if (!controlFileError.empty()) + logMessage << ": " << controlFileError; + SystemLog::WriteError (logMessage.str()); + } #endif continue; // Skip to the next mounted filesystem } diff --git a/src/Core/Unix/MacOSX/CoreMacOSX.cpp b/src/Core/Unix/MacOSX/CoreMacOSX.cpp index 70484cfb..22883fda 100644 --- a/src/Core/Unix/MacOSX/CoreMacOSX.cpp +++ b/src/Core/Unix/MacOSX/CoreMacOSX.cpp @@ -23,9 +23,170 @@ #include "CoreMacOSX.h" #include "Driver/Fuse/FuseService.h" #include "Core/Unix/CoreServiceProxy.h" +#include "Platform/FileStream.h" +#include "Platform/MemoryStream.h" +#include "Platform/Serializable.h" +#include "Platform/SystemLog.h" namespace VeraCrypt { + static string DecodePlistXmlString (const string &xmlString) + { + string decoded; + + for (size_t i = 0; i < xmlString.size(); ++i) + { + if (xmlString[i] != '&') + { + decoded += xmlString[i]; + continue; + } + + if (xmlString.compare (i, 5, "&") == 0) + { + decoded += '&'; + i += 4; + } + else if (xmlString.compare (i, 4, "<") == 0) + { + decoded += '<'; + i += 3; + } + else if (xmlString.compare (i, 4, ">") == 0) + { + decoded += '>'; + i += 3; + } + else if (xmlString.compare (i, 6, """) == 0) + { + decoded += '"'; + i += 5; + } + else if (xmlString.compare (i, 6, "'") == 0) + { + decoded += '\''; + i += 5; + } + else + decoded += xmlString[i]; + } + + return decoded; + } + + static bool ExtractPlistString (const string &xml, const string &key, size_t start, size_t limit, string &value, size_t *endPos = nullptr) + { + string keyTag = "" + key + ""; + size_t p = xml.find (keyTag, start); + if (p == string::npos || p >= limit) + return false; + + p = xml.find ("", p + keyTag.size()); + if (p == string::npos || p >= limit) + return false; + p += 8; + + size_t e = xml.find ("", p); + if (e == string::npos || e > limit) + return false; + + value = DecodePlistXmlString (xml.substr (p, e - p)); + if (endPos) + *endPos = e + 9; + + return true; + } + + static string NormalizeDiskImagePath (const string &path) + { + string normalized; + bool previousSlash = false; + + for (string::const_iterator i = path.begin(); i != path.end(); ++i) + { + if (*i == '/') + { + if (previousSlash) + continue; + + previousSlash = true; + } + else + previousSlash = false; + + normalized += *i; + } + + if (normalized.find ("/private/var/") == 0) + normalized.erase (0, 8); + + return normalized; + } + + static DevicePath FindVirtualDeviceByImagePath (const string &imagePath) + { + list args; + args.push_back ("info"); + args.push_back ("-plist"); + + string xml = Process::Execute ("/usr/bin/hdiutil", args); + string normalizedImagePath = NormalizeDiskImagePath (imagePath); + + for (size_t p = 0; ; ) + { + size_t imageKeyPos = xml.find ("image-path", p); + if (imageKeyPos == string::npos) + break; + + string currentImagePath; + size_t imageValueEnd = 0; + if (!ExtractPlistString (xml, "image-path", imageKeyPos, string::npos, currentImagePath, &imageValueEnd)) + { + p = imageKeyPos + 1; + continue; + } + + size_t nextImageKeyPos = xml.find ("image-path", imageValueEnd); + if (NormalizeDiskImagePath (currentImagePath) == normalizedImagePath) + { + string devEntry; + if (ExtractPlistString (xml, "dev-entry", imageValueEnd, nextImageKeyPos, devEntry)) + return StringConverter::Trim (devEntry); + } + + p = imageValueEnd; + } + + return DevicePath(); + } + + static bool AuxiliaryControlFileHasVirtualDevice (const DirectoryPath &auxMountPoint, const DevicePath &virtualDev) + { + for (int t = 0; t < 50; ++t) + { + try + { + shared_ptr controlFile (new File); + controlFile->Open (string (auxMountPoint) + FuseService::GetControlPath()); + + FileStream controlFileReader (controlFile); + string controlFileData = controlFileReader.ReadToEnd(); + if (controlFileData.empty() || controlFileData.size() > 1024 * 1024) + throw ParameterIncorrect (SRC_POS); + + shared_ptr controlFileStream (new MemoryStream (ConstBufferPtr ((const uint8 *) controlFileData.data(), controlFileData.size()))); + shared_ptr mountedVol = Serializable::DeserializeNew (controlFileStream); + if (mountedVol && string (mountedVol->VirtualDevice) == string (virtualDev)) + return true; + } + catch (...) { } + + Thread::Sleep (100); + } + + return false; + } + CoreMacOSX::CoreMacOSX () { } @@ -36,6 +197,17 @@ namespace VeraCrypt shared_ptr CoreMacOSX::DismountVolume (shared_ptr mountedVolume, bool ignoreOpenFiles, bool syncVolumeInfo) { + if (mountedVolume->VirtualDevice.IsEmpty() && !mountedVolume->AuxMountPoint.IsEmpty()) + { + try + { + DevicePath recoveredVirtualDevice = FindVirtualDeviceByImagePath (string (mountedVolume->AuxMountPoint) + FuseService::GetVolumeImagePath()); + if (!recoveredVirtualDevice.IsEmpty()) + mountedVolume->VirtualDevice = recoveredVirtualDevice; + } + catch (...) { } + } + if (!mountedVolume->VirtualDevice.IsEmpty() && mountedVolume->VirtualDevice.IsBlockDevice()) { list args; @@ -223,6 +395,16 @@ namespace VeraCrypt try { FuseService::SendAuxDeviceInfo (auxMountPoint, virtualDev); + if (!AuxiliaryControlFileHasVirtualDevice (auxMountPoint, virtualDev)) + { + stringstream logMessage; + logMessage << "VeraCrypt auxiliary mount did not report hdiutil device after mount: " + << string (auxMountPoint) << FuseService::GetControlPath() + << ", expected " << string (virtualDev); + SystemLog::WriteError (logMessage.str()); + + throw TimeOut (SRC_POS); + } } catch (...) { @@ -230,7 +412,7 @@ namespace VeraCrypt { list args; args.push_back ("detach"); - args.push_back (volImage); + args.push_back (virtualDev); args.push_back ("-force"); Process::Execute ("/usr/bin/hdiutil", args); diff --git a/src/Driver/Fuse/FuseService.cpp b/src/Driver/Fuse/FuseService.cpp index a755c4d1..bc3ea62d 100644 --- a/src/Driver/Fuse/FuseService.cpp +++ b/src/Driver/Fuse/FuseService.cpp @@ -75,6 +75,14 @@ namespace VeraCrypt statData->st_blocks = fuse_service_ceil_div ((uint64) statData->st_size, VC_FUSE_STAT_BLOCK_SIZE); } + static shared_ptr fuse_service_get_control_info (struct fuse_file_info *fi) + { + if (fi && fi->fh) + return *reinterpret_cast *> (fi->fh); + + return FuseService::GetVolumeInfo(); + } + static int fuse_service_fill_dir_entry (void *buf, fuse_fill_dir_t filler, const char *name, mode_t mode, ino_t ino, off_t nextOff) { struct stat st; @@ -200,7 +208,7 @@ namespace VeraCrypt if (strcmp (path, FuseService::GetAuxDeviceInfoPath()) == 0) { - statData->st_mode = S_IFREG | 0200; + statData->st_mode = S_IFREG | 0600; statData->st_nlink = 1; statData->st_size = VC_FUSE_BLOCK_SIZE; statData->st_ino = VC_FUSE_INODE_AUX_DEVICE_INFO; @@ -218,7 +226,7 @@ namespace VeraCrypt { statData->st_mode = S_IFREG | 0600; statData->st_nlink = 1; - statData->st_size = FuseService::GetVolumeInfo()->Size(); + statData->st_size = VC_FUSE_BLOCK_SIZE; statData->st_ino = VC_FUSE_INODE_CONTROL; fuse_service_set_stat_blocks (statData); } @@ -314,6 +322,7 @@ namespace VeraCrypt if (strcmp (path, FuseService::GetControlPath()) == 0) { + fi->fh = reinterpret_cast (new shared_ptr (FuseService::GetVolumeInfo())); fi->direct_io = 1; return 0; } @@ -372,7 +381,22 @@ namespace VeraCrypt if (strcmp (path, FuseService::GetControlPath()) == 0) { - shared_ptr infoBuf = FuseService::GetVolumeInfo(); + shared_ptr infoBuf = fuse_service_get_control_info (fi); + BufferPtr outBuf ((uint8 *)buf, size); + + if (offset >= (off_t) infoBuf->Size()) + return 0; + + if (offset + size > infoBuf->Size()) + size = infoBuf->Size () - offset; + + outBuf.CopyFrom (infoBuf->GetRange (offset, size)); + return size; + } + + if (strcmp (path, FuseService::GetAuxDeviceInfoPath()) == 0) + { + shared_ptr infoBuf = FuseService::GetAuxDeviceInfo(); BufferPtr outBuf ((uint8 *)buf, size); if (offset >= (off_t) infoBuf->Size()) @@ -393,6 +417,24 @@ namespace VeraCrypt return -ENOENT; } + static int fuse_service_release (const char *path, struct fuse_file_info *fi) + { + try + { + if (strcmp (path, FuseService::GetControlPath()) == 0 && fi && fi->fh) + { + delete reinterpret_cast *> (fi->fh); + fi->fh = 0; + } + } + catch (...) + { + return FuseService::ExceptionToErrorCode(); + } + + return 0; + } + static int fuse_service_readdir_impl (const char *path, void *buf, fuse_fill_dir_t filler, struct fuse_file_info *fi) { (void) fi; @@ -413,7 +455,7 @@ namespace VeraCrypt return 0; if (fuse_service_fill_dir_entry (buf, filler, FuseService::GetControlPath() + 1, S_IFREG | 0600, VC_FUSE_INODE_CONTROL, 0) != 0) return 0; - if (fuse_service_fill_dir_entry (buf, filler, FuseService::GetAuxDeviceInfoPath() + 1, S_IFREG | 0200, VC_FUSE_INODE_AUX_DEVICE_INFO, 0) != 0) + if (fuse_service_fill_dir_entry (buf, filler, FuseService::GetAuxDeviceInfoPath() + 1, S_IFREG | 0600, VC_FUSE_INODE_AUX_DEVICE_INFO, 0) != 0) return 0; } catch (...) @@ -548,6 +590,25 @@ namespace VeraCrypt } } + shared_ptr FuseService::GetAuxDeviceInfo () + { + shared_ptr stream (new MemoryStream); + Serializer sr (stream); + + { + ScopeLock lock (OpenVolumeInfoMutex); + + sr.Serialize ("VirtualDevice", string (OpenVolumeInfo.VirtualDevice)); + sr.Serialize ("LoopDevice", string (OpenVolumeInfo.LoopDevice)); + } + + ConstBufferPtr infoBuf = dynamic_cast (*stream); + shared_ptr outBuf (new Buffer (infoBuf.Size())); + outBuf->CopyFrom (infoBuf); + + return outBuf; + } + shared_ptr FuseService::GetVolumeInfo () { shared_ptr stream (new MemoryStream); @@ -666,6 +727,7 @@ namespace VeraCrypt sr.Serialize ("VirtualDevice", string (virtualDevice)); sr.Serialize ("LoopDevice", string (loopDevice)); fuseServiceControl.Write (dynamic_cast (*stream)); + fuseServiceControl.Close(); } void FuseService::WriteVolumeSectors (const ConstBufferPtr &buffer, uint64 byteOffset) @@ -728,6 +790,7 @@ namespace VeraCrypt fuse_service_oper.opendir = fuse_service_opendir; fuse_service_oper.read = fuse_service_read; fuse_service_oper.readdir = fuse_service_readdir; + fuse_service_oper.release = fuse_service_release; fuse_service_oper.statfs = fuse_service_statfs; fuse_service_oper.write = fuse_service_write; diff --git a/src/Driver/Fuse/FuseService.h b/src/Driver/Fuse/FuseService.h index a07142cb..8e9939e2 100644 --- a/src/Driver/Fuse/FuseService.h +++ b/src/Driver/Fuse/FuseService.h @@ -51,6 +51,7 @@ namespace VeraCrypt static string GetDeviceType () { return "veracrypt"; } static gid_t GetGroupId () { return GroupId; } static uid_t GetUserId () { return UserId; } + static shared_ptr GetAuxDeviceInfo (); static shared_ptr GetVolumeInfo (); static uint64 GetVolumeSize (); static uint64 GetVolumeSectorSize () { return MountedVolume->GetSectorSize(); }