diff --git a/src/Common/Language.xml b/src/Common/Language.xml index 2d65663b..0e15b413 100644 --- a/src/Common/Language.xml +++ b/src/Common/Language.xml @@ -1515,6 +1515,11 @@ Error: You are trying to encrypt a system partition.\n\nVeraCrypt can encrypt system partitions only under Windows. WARNING: Formatting of the device will destroy all data on filesystem '{0}'.\n\nDo you want to continue? The filesystem of the selected device is currently mounted. Please unmount '{0}' before proceeding. + The selected device '{0}' is an APFS synthesized container or volume and cannot be used as a raw VeraCrypt volume host.\n\nSelect the physical APFS store partition{1} instead. + The selected device '{0}' is a macOS system/support partition and cannot be used as a VeraCrypt volume host. + The selected APFS physical store '{0}' contains the currently mounted macOS system volume and cannot be used as a VeraCrypt volume host. + macOS reports the selected device '{0}' as read-only. Select a writable physical partition or disk. + macOS reported the selected device as read-only. If this is an APFS disk, make sure you selected the physical APFS store partition, not an APFS synthesized volume. Use Disk Utility or 'diskutil list' to identify the physical partition, then retry. The Hidden volume can't have the same password, PIM and keyfiles as the Outer volume Please note that the volume will not be formatted with a FAT filesystem and, therefore, you may be required to install additional filesystem drivers on platforms other than {0}, which will enable you to mount the volume. Error: The hidden volume to be created is larger than {0} TB ({1} GB).\n\nPossible solutions:\n- Create a container/partition smaller than {0} TB.\n diff --git a/src/Main/Forms/VolumeCreationWizard.cpp b/src/Main/Forms/VolumeCreationWizard.cpp index 74a31219..d1ab7789 100644 --- a/src/Main/Forms/VolumeCreationWizard.cpp +++ b/src/Main/Forms/VolumeCreationWizard.cpp @@ -13,6 +13,7 @@ #include "System.h" #include "Platform/SystemInfo.h" #ifdef TC_UNIX +#include #include #include // header for statvfs #include "Platform/Unix/Process.h" @@ -52,6 +53,340 @@ namespace VeraCrypt #ifdef TC_MACOSX + static string DecodeMacOSXPlistXmlString (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 ExtractMacOSXPlistString (const string &xml, const string &key, string &value) + { + string keyTag = "" + key + ""; + size_t p = xml.find (keyTag); + if (p == string::npos) + return false; + + p = xml.find ("", p + keyTag.size()); + if (p == string::npos) + return false; + p += 8; + + size_t e = xml.find ("", p); + if (e == string::npos) + return false; + + value = DecodeMacOSXPlistXmlString (xml.substr (p, e - p)); + return true; + } + + static bool ExtractMacOSXPlistBool (const string &xml, const string &key, bool &value) + { + string keyTag = "" + key + ""; + size_t p = xml.find (keyTag); + if (p == string::npos) + return false; + + p += keyTag.size(); + size_t truePos = xml.find ("", p); + size_t falsePos = xml.find ("", p); + size_t nextKeyPos = xml.find ("", p); + + if (truePos != string::npos && (nextKeyPos == string::npos || truePos < nextKeyPos) + && (falsePos == string::npos || truePos < falsePos)) + { + value = true; + return true; + } + + if (falsePos != string::npos && (nextKeyPos == string::npos || falsePos < nextKeyPos)) + { + value = false; + return true; + } + + return false; + } + + static list ExtractMacOSXAPFSPhysicalStores (const string &xml) + { + list stores; + size_t arrayPos = xml.find ("APFSPhysicalStores"); + if (arrayPos == string::npos) + return stores; + + size_t arrayEnd = xml.find ("", arrayPos); + if (arrayEnd == string::npos) + return stores; + + for (size_t p = arrayPos; p < arrayEnd; ) + { + size_t keyPos = xml.find ("APFSPhysicalStore", p); + size_t alternateKeyPos = xml.find ("DeviceIdentifier", p); + + if (alternateKeyPos != string::npos && alternateKeyPos < arrayEnd + && (keyPos == string::npos || alternateKeyPos < keyPos)) + keyPos = alternateKeyPos; + + if (keyPos == string::npos || keyPos >= arrayEnd) + break; + + size_t stringPos = xml.find ("", keyPos); + if (stringPos == string::npos || stringPos >= arrayEnd) + break; + stringPos += 8; + + size_t stringEnd = xml.find ("", stringPos); + if (stringEnd == string::npos || stringEnd > arrayEnd) + break; + + stores.push_back (DecodeMacOSXPlistXmlString (xml.substr (stringPos, stringEnd - stringPos))); + p = stringEnd + 9; + } + + return stores; + } + + static string GetMacOSXDiskutilDevicePath (const VolumePath &devicePath) + { + string path = devicePath; + + if (path.find ("/dev/rdisk") == 0) + path = string ("/dev/disk") + path.substr (10); + + return path; + } + + static string GetMacOSXRawDevicePath (const string &deviceIdentifier) + { + if (deviceIdentifier.find ("/dev/rdisk") == 0) + return deviceIdentifier; + + if (deviceIdentifier.find ("/dev/disk") == 0) + return string ("/dev/r") + deviceIdentifier.substr (5); + + if (deviceIdentifier.find ("disk") == 0) + return string ("/dev/r") + deviceIdentifier; + + return deviceIdentifier; + } + + static string GetMacOSXDiskutilInfo (const VolumePath &devicePath) + { + list args; + args.push_back ("info"); + args.push_back ("-plist"); + args.push_back (GetMacOSXDiskutilDevicePath (devicePath)); + + return Process::Execute ("/usr/sbin/diskutil", args); + } + + static bool IsMacOSXSystemSupportContent (const string &content) + { + string lowerContent = StringConverter::ToLower (content); + + return lowerContent == "efi" + || lowerContent == "apple_apfs_isc" + || lowerContent == "apple_apfs_recovery" + || lowerContent == "apple_boot" + || lowerContent == "apple_partition_map"; + } + + static bool IsMacOSXSystemMountPoint (const string &mountPoint) + { + return mountPoint == "/" || mountPoint.find ("/System/Volumes/") == 0; + } + + static bool IsMacOSXAPFSSynthesizedDevice (const string &infoXml) + { + string containerReference; + string filesystemType; + string virtualOrPhysical; + bool partitionMapPartition = false; + bool wholeDisk = false; + bool hasPartitionMapPartition = ExtractMacOSXPlistBool (infoXml, "PartitionMapPartition", partitionMapPartition); + bool hasWholeDisk = ExtractMacOSXPlistBool (infoXml, "WholeDisk", wholeDisk); + + ExtractMacOSXPlistString (infoXml, "APFSContainerReference", containerReference); + ExtractMacOSXPlistString (infoXml, "FilesystemType", filesystemType); + ExtractMacOSXPlistString (infoXml, "VirtualOrPhysical", virtualOrPhysical); + + if (StringConverter::ToLower (virtualOrPhysical) == "virtual" && !containerReference.empty()) + return true; + + if (!ExtractMacOSXAPFSPhysicalStores (infoXml).empty() && hasPartitionMapPartition && !partitionMapPartition) + return true; + + if (StringConverter::ToLower (filesystemType) == "apfs" + && hasPartitionMapPartition && !partitionMapPartition + && hasWholeDisk && !wholeDisk) + { + return true; + } + + return false; + } + + static bool IsMacOSXDeviceReadOnly (const string &infoXml) + { + bool value = false; + // Writable and ReadOnlyVolume can reflect a read-only filesystem mount; + // only media writability is fatal before unmounting. + if (ExtractMacOSXPlistBool (infoXml, "ReadOnlyMedia", value) && value) + return true; + if (ExtractMacOSXPlistBool (infoXml, "WritableMedia", value) && !value) + return true; + + return false; + } + + static bool IsSelectedMacOSXSystemAPFSDevice (const string &selectedInfoXml, bool &wholeDiskSelected) + { + wholeDiskSelected = false; + + string selectedDeviceIdentifier; + if (!ExtractMacOSXPlistString (selectedInfoXml, "DeviceIdentifier", selectedDeviceIdentifier) || selectedDeviceIdentifier.empty()) + return false; + + ExtractMacOSXPlistBool (selectedInfoXml, "WholeDisk", wholeDiskSelected); + + try + { + string rootInfoXml = GetMacOSXDiskutilInfo (VolumePath (FilesystemPath ("/"))); + list rootStores = ExtractMacOSXAPFSPhysicalStores (rootInfoXml); + + foreach (const string &store, rootStores) + { + if (selectedDeviceIdentifier == store) + return true; + + if (wholeDiskSelected) + { + try + { + string storeInfoXml = GetMacOSXDiskutilInfo (VolumePath (FilesystemPath (GetMacOSXRawDevicePath (store)))); + string storeParentWholeDisk; + if (ExtractMacOSXPlistString (storeInfoXml, "ParentWholeDisk", storeParentWholeDisk) + && selectedDeviceIdentifier == storeParentWholeDisk) + { + return true; + } + } + catch (...) { } + } + } + } + catch (...) { } + + return false; + } + + static bool ValidateMacOSXSelectedDeviceForCreation (const VolumePath &devicePath) + { + string infoXml; + try + { + infoXml = GetMacOSXDiskutilInfo (devicePath); + } + catch (...) + { + return true; + } + + if (IsMacOSXAPFSSynthesizedDevice (infoXml)) + { + wstring recommendedPath; + list stores = ExtractMacOSXAPFSPhysicalStores (infoXml); + if (!stores.empty()) + recommendedPath = L" (" + StringConverter::ToWide (GetMacOSXRawDevicePath (stores.front())) + L")"; + + Gui->ShowError (StringFormatter (LangString["MACOSX_APFS_SYNTHESIZED_DEVICE"], wstring (devicePath), recommendedPath)); + return false; + } + + string content; + if (ExtractMacOSXPlistString (infoXml, "Content", content) && IsMacOSXSystemSupportContent (content)) + { + Gui->ShowError (StringFormatter (LangString["MACOSX_DEVICE_SYSTEM_PARTITION"], wstring (devicePath))); + return false; + } + + string mountPoint; + if (ExtractMacOSXPlistString (infoXml, "MountPoint", mountPoint) && IsMacOSXSystemMountPoint (mountPoint)) + { + Gui->ShowError (LangString["LINUX_ERROR_TRY_ENCRYPT_SYSTEM_PARTITION"]); + return false; + } + + bool selectedSystemWholeDisk = false; + if (IsSelectedMacOSXSystemAPFSDevice (infoXml, selectedSystemWholeDisk)) + { + if (selectedSystemWholeDisk) + Gui->ShowError (LangString["LINUX_ERROR_TRY_ENCRYPT_SYSTEM_DRIVE"]); + else + Gui->ShowError (StringFormatter (LangString["MACOSX_APFS_SYSTEM_STORE"], wstring (devicePath))); + + return false; + } + + if (IsMacOSXDeviceReadOnly (infoXml)) + { + Gui->ShowError (StringFormatter (LangString["MACOSX_DEVICE_NOT_WRITABLE"], wstring (devicePath))); + return false; + } + + return true; + } + + static void ShowMacOSXVolumeCreationError (const VolumePath &devicePath, const exception &e) + { + const SystemException *sysEx = dynamic_cast (&e); + if (devicePath.IsDevice() && sysEx && sysEx->GetErrorCode() == EROFS) + { + Gui->ShowError (UserInterface::ExceptionToMessage (e) + L"\n\n" + LangString["MACOSX_APFS_EROFS_HINT"]); + return; + } + + Gui->ShowError (e); + } + bool VolumeCreationWizard::ProcessEvent(wxEvent& event) { if(GraphicUserInterface::HandlePasswordEntryCustomEvent (event)) @@ -590,7 +925,11 @@ namespace VeraCrypt SetWorkInProgress (false); workInProgressCleared = true; } +#ifdef TC_MACOSX + ShowMacOSXVolumeCreationError (SelectedVolumePath, e); +#else Gui->ShowError (e); +#endif } if (!workInProgressCleared) @@ -672,6 +1011,11 @@ namespace VeraCrypt DeviceWarningConfirmed = true; +#ifdef TC_MACOSX + if (!ValidateMacOSXSelectedDeviceForCreation (SelectedVolumePath)) + return GetCurrentStep(); +#endif + foreach_ref (const HostDevice &drive, Core->GetHostDevices()) { if (drive.Path == SelectedVolumePath && !drive.Partitions.empty()) @@ -1108,7 +1452,11 @@ namespace VeraCrypt { CreationAborted = true; OnVolumeCreatorFinished(); +#ifdef TC_MACOSX + ShowMacOSXVolumeCreationError (SelectedVolumePath, e); +#else Gui->ShowError (e); +#endif } }