Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ src/Main/VeraCrypt
src/Main/VeraCrypt.app
src/Main/*.dmg
src/Setup/MacOSX/*.pkg
# macOS privileged helper build artifacts (binary + plists generated from templates)
src/PrivilegedHelper/org.idrix.VeraCrypt.helper
src/PrivilegedHelper/Helper-Info.plist
src/PrivilegedHelper/Helper-Launchd.plist
*.oo
*.o.32
*.o.64
Expand Down
32 changes: 32 additions & 0 deletions src/Build/Resources/MacOSX/Helper-Info.plist.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>

<key>CFBundleIdentifier</key>
<string>org.idrix.VeraCrypt.helper</string>

<key>CFBundleName</key>
<string>VeraCrypt Privileged Helper</string>

<key>CFBundlePackageType</key>
<string>APPL</string>

<key>CFBundleVersion</key>
<string>_VERSION_</string>

<key>CFBundleShortVersionString</key>
<string>_VERSION_</string>

<!-- Code-signing requirement that a connecting client (the VeraCrypt app)
and the binary the helper is asked to exec must satisfy. The helper
reads this at runtime as the single source of truth for the authorized
client identity. _TEAMID_ is substituted at build time. -->
<key>SMAuthorizedClients</key>
<array>
<string>identifier "org.idrix.VeraCrypt" and anchor apple generic and certificate leaf[subject.OU] = "_TEAMID_"</string>
</array>
</dict>
</plist>
16 changes: 16 additions & 0 deletions src/Build/Resources/MacOSX/Helper-Launchd.plist.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.idrix.VeraCrypt.helper</string>

<!-- launchd publishes this privileged Mach service on the helper's behalf
and launches it on demand when the client connects. -->
<key>MachServices</key>
<dict>
<key>org.idrix.VeraCrypt.helper</key>
<true/>
</dict>
</dict>
</plist>
10 changes: 10 additions & 0 deletions src/Build/Resources/MacOSX/Info.plist.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,15 @@

<key>NSPrincipalClass</key>
<string>NSApplication</string>

<!-- SMJobBless: authorizes installation of the privileged helper. The
requirement must match the helper's code signature; _TEAMID_ is
substituted at build time. Keep in sync with the helper's
CFBundleIdentifier (org.idrix.VeraCrypt.helper). -->
<key>SMPrivilegedExecutables</key>
<dict>
<key>org.idrix.VeraCrypt.helper</key>
<string>identifier "org.idrix.VeraCrypt.helper" and anchor apple generic and certificate leaf[subject.OU] = "_TEAMID_"</string>
</dict>
</dict>
</plist>
1 change: 1 addition & 0 deletions src/Core/Core.make
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ OBJS += Unix/$(PLATFORM)/Core$(PLATFORM).o
OBJS += Unix/$(PLATFORM)/Core$(PLATFORM).o
ifeq "$(PLATFORM)" "MacOSX"
OBJS += Unix/FreeBSD/CoreFreeBSD.o
OBJS += Unix/MacOSX/PrivilegedHelperClient.o
endif

include $(BUILD_INC)/Makefile.inc
85 changes: 75 additions & 10 deletions src/Core/Unix/CoreService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#ifdef TC_MACOSX
#include "Core/Unix/MacOSX/PrivilegedHelperClient.h"
#endif
#include "Platform/FileStream.h"
#include "Platform/MemoryStream.h"
#include "Platform/Serializable.h"
Expand Down Expand Up @@ -61,13 +64,12 @@ namespace VeraCrypt
|| IsMacOSXDevicePathWithPrefix (path, "/dev/rdisk");
}

// The elevated service runs as root, so it must not be tricked into changing
// ownership of an arbitrary path. Every legitimate macOS caller of the
// elevated SetFileOwner targets a real disk device node (/dev/[r]diskN[sM]),
// so restrict the operation to that. lstat() (not stat) is used so a symlink
// is rejected outright rather than followed, and the st_mode check confirms an
// actual block/character device before the chown.
static void ValidateMacOSXSetFileOwnerTarget (const FilesystemPath &path)
// The elevated service runs as root, so it must not be tricked into operating
// on arbitrary paths. Every legitimate macOS caller that reaches these helpers
// targets a real disk device node (/dev/[r]diskN[sM]), so restrict privileged
// operations to that. lstat() (not stat) rejects symlinks outright, and the
// st_mode check confirms an actual block/character device before use.
static void ValidateMacOSXDeviceNodeTarget (const FilesystemPath &path)
{
const string pathStr = path;

Expand All @@ -84,8 +86,7 @@ namespace VeraCrypt

static list <string> BuildMacOSXAPFSFormatterArguments (const ExecuteMacOSXAPFSFormatterRequest &request)
{
if (!IsMacOSXFormatterDevicePath (request.Device))
throw ParameterIncorrect (SRC_POS);
ValidateMacOSXDeviceNodeTarget (request.Device);

if (request.OwnerUserId > static_cast <uint64> ((uid_t) -1)
|| request.OwnerGroupId > static_cast <uint64> ((gid_t) -1))
Expand Down Expand Up @@ -330,7 +331,7 @@ namespace VeraCrypt

#ifdef TC_MACOSX
// Restrict the root-privileged chown to real disk device nodes.
ValidateMacOSXSetFileOwnerTarget (setFileOwnerRequest->Path);
ValidateMacOSXDeviceNodeTarget (setFileOwnerRequest->Path);
#endif
coreUnix->SetFileOwner (setFileOwnerRequest->Path, setFileOwnerRequest->Owner);
SetFileOwnerResponse().Serialize (outputStream);
Expand Down Expand Up @@ -449,6 +450,21 @@ namespace VeraCrypt
{
// Test if the user has an active "sudo" session.
bool authCheckDone = false;
#ifdef TC_MACOSX
// macOS: establish the privileged channel here, in the main
// application process. StartElevated() uses the SMJobBless helper
// and XPC, which cannot run in the unprivileged core service (a
// fork()ed child that never calls exec()); delegating elevation to
// it fails before the native authentication dialog is even shown.
// StartElevated() repoints the Service streams at the root core
// service, so the request is then sent below like any other.
authCheckDone = true;
request.FastElevation = false;
StartElevated (request);
ElevatedServiceAvailable = true;
request.Serialize (ServiceInputStream);
return GetResponse <T> ();
#else
if (!Core->GetUseDummySudoPassword ())
{
// We are using -n to avoid prompting the user for a password.
Expand Down Expand Up @@ -493,6 +509,7 @@ namespace VeraCrypt
request.FastElevation = false;
}
}
#endif

try
{
Expand Down Expand Up @@ -527,6 +544,9 @@ namespace VeraCrypt

request.FastElevation = false;

#ifdef TC_MACOSX
throw;
#endif
if(!authCheckDone)
(*AdminPasswordCallback) (request.AdminPassword);
}
Expand Down Expand Up @@ -562,6 +582,51 @@ namespace VeraCrypt

void CoreService::StartElevated (const CoreServiceRequest &request)
{
#ifdef TC_MACOSX
try
{
std::string errorMsg;
string appPath = request.ApplicationExecutablePath;
if (appPath.empty() || appPath[0] != '/')
{
appPath = Process::FindSystemBinary("VeraCrypt", errorMsg);
if (appPath.empty())
throw SystemException(SRC_POS, errorMsg);
}

// Install (if needed) and drive the SMJobBless privileged helper.
// The helper shows the native macOS authentication dialog at install
// time, validates this app's code signature on every connection, and
// spawns "<appPath> --core-service" as root, returning a connected
// socket. VeraCrypt never handles the administrator password.
int serviceFD = MacOSXConnectElevatedCoreService (appPath);
throw_sys_if (serviceFD == -1);

shared_ptr <File> servicePipe (new File());
servicePipe->AssignSystemHandle (serviceFD, false);
ServiceInputStream = shared_ptr <Stream> (new FileStream (servicePipe));
ServiceOutputStream = shared_ptr <Stream> (new FileStream (servicePipe));

// Send sync code
uint8 sync[] = { 0, 0x11, 0x22 };
ServiceInputStream->Write (ConstBufferPtr (sync, array_capacity (sync)));

return;
}
catch (Exception &)
{
throw;
}
catch (exception &e)
{
throw ExternalException (SRC_POS, StringConverter::ToExceptionString (e));
}
catch (...)
{
throw UnknownException (SRC_POS);
}
#endif

unique_ptr <Pipe> inPipe (new Pipe());
unique_ptr <Pipe> outPipe (new Pipe());
Pipe errPipe;
Expand Down
24 changes: 24 additions & 0 deletions src/Core/Unix/MacOSX/PrivilegedHelperClient.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Copyright (c) 2026 AM Crypto and are governed by the Apache License 2.0
the full text of which is contained in the file License.txt included in
VeraCrypt binary and source code distribution packages.
*/

#ifndef TC_HEADER_Core_Unix_MacOSX_PrivilegedHelperClient
#define TC_HEADER_Core_Unix_MacOSX_PrivilegedHelperClient

#include <string>

namespace VeraCrypt
{
// Ensures the SMJobBless privileged helper is installed and up to date
// (showing the native macOS authentication dialog when an install/upgrade
// is required), then asks it to spawn "appPath --core-service" as root and
// returns a connected socket file descriptor to that root process. The
// returned descriptor is owned by the caller and must be closed.
//
// Throws a VeraCrypt exception (e.g. ElevationFailed) on any failure.
int MacOSXConnectElevatedCoreService (const std::string &appPath);
}

#endif // TC_HEADER_Core_Unix_MacOSX_PrivilegedHelperClient
Loading
Loading