mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2026-05-28 07:20:21 +00:00
macOS: validate format wizard device targets
Keep device selection enumeration unchanged to avoid slow dialog loads. In the format wizard, inspect only the selected target with diskutil info -plist and reject APFS synthesized devices, macOS system/support targets, read-only targets, and current APFS system stores. Add a read-only APFS hint for creation failures.
This commit is contained in:
committed by
Mounir IDRASSI
parent
e6247fbf2a
commit
49c8fd3680
@@ -13,6 +13,7 @@
|
||||
#include "System.h"
|
||||
#include "Platform/SystemInfo.h"
|
||||
#ifdef TC_UNIX
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/statvfs.h> // 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>" + key + "</key>";
|
||||
size_t p = xml.find (keyTag);
|
||||
if (p == string::npos)
|
||||
return false;
|
||||
|
||||
p = xml.find ("<string>", p + keyTag.size());
|
||||
if (p == string::npos)
|
||||
return false;
|
||||
p += 8;
|
||||
|
||||
size_t e = xml.find ("</string>", 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>" + key + "</key>";
|
||||
size_t p = xml.find (keyTag);
|
||||
if (p == string::npos)
|
||||
return false;
|
||||
|
||||
p += keyTag.size();
|
||||
size_t truePos = xml.find ("<true/>", p);
|
||||
size_t falsePos = xml.find ("<false/>", p);
|
||||
size_t nextKeyPos = xml.find ("<key>", 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 <string> ExtractMacOSXAPFSPhysicalStores (const string &xml)
|
||||
{
|
||||
list <string> stores;
|
||||
size_t arrayPos = xml.find ("<key>APFSPhysicalStores</key>");
|
||||
if (arrayPos == string::npos)
|
||||
return stores;
|
||||
|
||||
size_t arrayEnd = xml.find ("</array>", arrayPos);
|
||||
if (arrayEnd == string::npos)
|
||||
return stores;
|
||||
|
||||
for (size_t p = arrayPos; p < arrayEnd; )
|
||||
{
|
||||
size_t keyPos = xml.find ("<key>APFSPhysicalStore</key>", p);
|
||||
size_t alternateKeyPos = xml.find ("<key>DeviceIdentifier</key>", 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 ("<string>", keyPos);
|
||||
if (stringPos == string::npos || stringPos >= arrayEnd)
|
||||
break;
|
||||
stringPos += 8;
|
||||
|
||||
size_t stringEnd = xml.find ("</string>", 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 <string> 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 <string> 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 <string> 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 <const SystemException *> (&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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user