1
0
mirror of https://github.com/veracrypt/VeraCrypt.git synced 2026-06-18 02:26:07 -05:00

macOS: parse hdiutil -plist output via CoreFoundation instead of string scanning (#1776)

The hdiutil `-plist` output used for mount/dismount device discovery
(MountAuxVolumeImage and UpdateMountedVolumeInfo) was parsed with a
hand-rolled string scanner that assumed the value always follows the
requested key and that <key>/<string> pairs appear in a fixed order.

Replace it with the CoreFoundation property-list API
(CFPropertyListCreateWithData + dictionary/array navigation), which is
correct by construction and robust to hdiutil output ordering/variation.
An RAII helper (CFHolder) ensures CFRelease on every path.

Behavior is preserved: prefer the system-entity that carries a
mount-point, otherwise fall back to the first dev-entry, and match disk
images by normalized image-path. CoreFoundation is already linked on
macOS (via Cocoa), so no build changes are needed.

Verified end-to-end on Apple Silicon: mounting parses `hdiutil attach`
output and dismount parses `hdiutil info` output correctly.

Co-authored-by: Damian Rickard <damian@rickard.us>
This commit is contained in:
damianrickard
2026-06-16 00:54:58 -04:00
committed by GitHub
parent 40f6473c90
commit 37412adf04
+109 -97
View File
@@ -13,6 +13,8 @@
#include <fstream> #include <fstream>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <vector>
#include <CoreFoundation/CoreFoundation.h>
#include <sys/param.h> #include <sys/param.h>
#include <sys/ucred.h> #include <sys/ucred.h>
#include <sys/mount.h> #include <sys/mount.h>
@@ -30,73 +32,66 @@
namespace VeraCrypt namespace VeraCrypt
{ {
static string DecodePlistXmlString (const string &xmlString) // RAII wrapper so CFRelease is never forgotten on early returns / exceptions.
class CFHolder
{ {
string decoded; public:
explicit CFHolder (CFTypeRef ref = nullptr) : Ref (ref) { }
~CFHolder () { if (Ref) CFRelease (Ref); }
CFTypeRef Get () const { return Ref; }
private:
CFHolder (const CFHolder &);
CFHolder &operator= (const CFHolder &);
CFTypeRef Ref;
};
for (size_t i = 0; i < xmlString.size(); ++i) static string CFStringToStdString (CFStringRef cfStr)
{ {
if (xmlString[i] != '&') if (!cfStr)
{ return string();
decoded += xmlString[i];
continue;
}
if (xmlString.compare (i, 5, "&amp;") == 0) CFIndex length = CFStringGetLength (cfStr);
{ CFIndex maxSize = CFStringGetMaximumSizeForEncoding (length, kCFStringEncodingUTF8) + 1;
decoded += '&'; if (maxSize <= 0)
i += 4; return string();
}
else if (xmlString.compare (i, 4, "&lt;") == 0)
{
decoded += '<';
i += 3;
}
else if (xmlString.compare (i, 4, "&gt;") == 0)
{
decoded += '>';
i += 3;
}
else if (xmlString.compare (i, 6, "&quot;") == 0)
{
decoded += '"';
i += 5;
}
else if (xmlString.compare (i, 6, "&apos;") == 0)
{
decoded += '\'';
i += 5;
}
else
decoded += xmlString[i];
}
return decoded; vector <char> buffer ((size_t) maxSize);
if (!CFStringGetCString (cfStr, &buffer[0], maxSize, kCFStringEncodingUTF8))
return string();
return string (&buffer[0]);
} }
static bool ExtractPlistString (const string &xml, const string &key, size_t start, size_t limit, string &value, size_t *endPos = nullptr) // Fetch a string value from a CFDictionary by (C-string) key; empty if absent
// or not a string.
static string CFDictionaryGetStdString (CFDictionaryRef dict, const char *key)
{ {
// hdiutil currently emits simple <key>name</key><string>value</string> pairs. if (!dict)
// This lightweight parser assumes the value follows the requested key. return string();
string keyTag = "<key>" + key + "</key>";
size_t p = xml.find (keyTag, start);
if (p == string::npos || p >= limit)
return false;
p = xml.find ("<string>", p + keyTag.size()); CFHolder cfKey (CFStringCreateWithCString (kCFAllocatorDefault, key, kCFStringEncodingUTF8));
if (p == string::npos || p >= limit) if (!cfKey.Get())
return false; return string();
p += 8;
size_t e = xml.find ("</string>", p); CFTypeRef value = CFDictionaryGetValue (dict, cfKey.Get()); // borrowed reference
if (e == string::npos || e > limit) if (!value || CFGetTypeID (value) != CFStringGetTypeID())
return false; return string();
value = DecodePlistXmlString (xml.substr (p, e - p)); return CFStringToStdString ((CFStringRef) value);
if (endPos) }
*endPos = e + 9;
return true; // Parse an hdiutil -plist (XML) document into a CFPropertyList. Returns nullptr
// on failure; the caller owns the result and must CFRelease it.
static CFPropertyListRef ParsePropertyList (const string &xml)
{
if (xml.empty())
return nullptr;
CFHolder data (CFDataCreate (kCFAllocatorDefault, (const UInt8 *) xml.data(), (CFIndex) xml.size()));
if (!data.Get())
return nullptr;
return CFPropertyListCreateWithData (kCFAllocatorDefault, (CFDataRef) data.Get(), kCFPropertyListImmutable, nullptr, nullptr);
} }
static string NormalizeDiskImagePath (const string &path) static string NormalizeDiskImagePath (const string &path)
@@ -125,45 +120,40 @@ namespace VeraCrypt
return normalized; return normalized;
} }
static bool ExtractDiskImageDeviceAndMountPoint (const string &xml, size_t start, size_t limit, DevicePath &device, DirectoryPath &mountPoint) // Walk a "system-entities" array (from hdiutil attach/info). Prefer the entity
// that carries a mount-point; otherwise fall back to the first dev-entry.
static bool ExtractDeviceAndMountPointFromEntities (CFArrayRef entities, DevicePath &device, DirectoryPath &mountPoint)
{ {
if (!entities || CFGetTypeID (entities) != CFArrayGetTypeID())
return false;
string firstDevice; string firstDevice;
string mountedDevice; string mountedDevice;
string mountedPath; string mountedPath;
for (size_t p = start; ; ) CFIndex count = CFArrayGetCount (entities);
for (CFIndex i = 0; i < count; ++i)
{ {
size_t devKeyPos = xml.find ("<key>dev-entry</key>", p); CFTypeRef entry = CFArrayGetValueAtIndex (entities, i); // borrowed reference
if (devKeyPos == string::npos || devKeyPos >= limit) if (!entry || CFGetTypeID (entry) != CFDictionaryGetTypeID())
break; continue;
string devEntry; CFDictionaryRef entryDict = (CFDictionaryRef) entry;
size_t devValueEnd = 0;
if (!ExtractPlistString (xml, "dev-entry", devKeyPos, limit, devEntry, &devValueEnd)) string devEntry = StringConverter::Trim (CFDictionaryGetStdString (entryDict, "dev-entry"));
{ if (devEntry.empty())
p = devKeyPos + 1;
continue; continue;
}
devEntry = StringConverter::Trim (devEntry);
if (firstDevice.empty()) if (firstDevice.empty())
firstDevice = devEntry; firstDevice = devEntry;
size_t nextDevKeyPos = xml.find ("<key>dev-entry</key>", devValueEnd); string currentMountPoint = CFDictionaryGetStdString (entryDict, "mount-point");
if (nextDevKeyPos == string::npos || nextDevKeyPos > limit) if (!currentMountPoint.empty())
nextDevKeyPos = limit;
string currentMountPoint;
// hdiutil currently emits dev-entry before mount-point inside each entity.
if (ExtractPlistString (xml, "mount-point", devValueEnd, nextDevKeyPos, currentMountPoint)
&& !currentMountPoint.empty())
{ {
mountedDevice = devEntry; mountedDevice = devEntry;
mountedPath = currentMountPoint; mountedPath = currentMountPoint;
break; break;
} }
p = devValueEnd;
} }
if (!mountedDevice.empty()) if (!mountedDevice.empty())
@@ -182,6 +172,20 @@ namespace VeraCrypt
return false; return false;
} }
// Parse "hdiutil attach -plist" output (top-level dict with "system-entities").
static bool ExtractDiskImageDeviceAndMountPoint (const string &attachXml, DevicePath &device, DirectoryPath &mountPoint)
{
CFHolder plist (ParsePropertyList (attachXml));
if (!plist.Get() || CFGetTypeID (plist.Get()) != CFDictionaryGetTypeID())
return false;
CFTypeRef entities = CFDictionaryGetValue ((CFDictionaryRef) plist.Get(), CFSTR ("system-entities")); // borrowed
if (!entities || CFGetTypeID (entities) != CFArrayGetTypeID())
return false;
return ExtractDeviceAndMountPointFromEntities ((CFArrayRef) entities, device, mountPoint);
}
static bool FindDiskImageInfoByImagePath (const string &imagePath, DevicePath &device, DirectoryPath &mountPoint) static bool FindDiskImageInfoByImagePath (const string &imagePath, DevicePath &device, DirectoryPath &mountPoint)
{ {
list <string> args; list <string> args;
@@ -191,27 +195,35 @@ namespace VeraCrypt
string xml = Process::Execute ("/usr/bin/hdiutil", args); string xml = Process::Execute ("/usr/bin/hdiutil", args);
string normalizedImagePath = NormalizeDiskImagePath (imagePath); string normalizedImagePath = NormalizeDiskImagePath (imagePath);
for (size_t p = 0; ; ) CFHolder plist (ParsePropertyList (xml));
if (!plist.Get() || CFGetTypeID (plist.Get()) != CFDictionaryGetTypeID())
return false;
CFTypeRef images = CFDictionaryGetValue ((CFDictionaryRef) plist.Get(), CFSTR ("images")); // borrowed
if (!images || CFGetTypeID (images) != CFArrayGetTypeID())
return false;
CFArrayRef imageArray = (CFArrayRef) images;
CFIndex count = CFArrayGetCount (imageArray);
for (CFIndex i = 0; i < count; ++i)
{ {
size_t imageKeyPos = xml.find ("<key>image-path</key>", p); CFTypeRef image = CFArrayGetValueAtIndex (imageArray, i); // borrowed
if (imageKeyPos == string::npos) if (!image || CFGetTypeID (image) != CFDictionaryGetTypeID())
break;
string currentImagePath;
size_t imageValueEnd = 0;
if (!ExtractPlistString (xml, "image-path", imageKeyPos, string::npos, currentImagePath, &imageValueEnd))
{
p = imageKeyPos + 1;
continue; continue;
}
size_t nextImageKeyPos = xml.find ("<key>image-path</key>", imageValueEnd); CFDictionaryRef imageDict = (CFDictionaryRef) image;
if (NormalizeDiskImagePath (currentImagePath) == normalizedImagePath)
{
return ExtractDiskImageDeviceAndMountPoint (xml, imageValueEnd, nextImageKeyPos, device, mountPoint);
}
p = imageValueEnd; string currentImagePath = CFDictionaryGetStdString (imageDict, "image-path");
if (NormalizeDiskImagePath (currentImagePath) != normalizedImagePath)
continue;
// Matching image found: extract from its system-entities (mirrors the
// previous behavior of returning the result for the first match).
CFTypeRef entities = CFDictionaryGetValue (imageDict, CFSTR ("system-entities")); // borrowed
if (!entities || CFGetTypeID (entities) != CFArrayGetTypeID())
return false;
return ExtractDeviceAndMountPointFromEntities ((CFArrayRef) entities, device, mountPoint);
} }
return false; return false;
@@ -473,7 +485,7 @@ namespace VeraCrypt
DevicePath virtualDev; DevicePath virtualDev;
DirectoryPath mountPoint; DirectoryPath mountPoint;
if (!ExtractDiskImageDeviceAndMountPoint (xml, 0, string::npos, virtualDev, mountPoint) if (!ExtractDiskImageDeviceAndMountPoint (xml, virtualDev, mountPoint)
|| virtualDev.IsEmpty()) || virtualDev.IsEmpty())
throw ParameterIncorrect (SRC_POS); throw ParameterIncorrect (SRC_POS);
(void) mountPoint; (void) mountPoint;