Skip to content

COM interface privilege escalation#1670

Open
audriusbuika wants to merge 2 commits intoveracrypt:masterfrom
audriusbuika:bugfix/com-interface-privilege-escalation
Open

COM interface privilege escalation#1670
audriusbuika wants to merge 2 commits intoveracrypt:masterfrom
audriusbuika:bugfix/com-interface-privilege-escalation

Conversation

@audriusbuika
Copy link
Copy Markdown

Problem

The elevated COM servers (MainCom, FormatCom) expose privileged operations — file I/O, registry writes, IOCTL dispatch, disk formatting — to any process that holds a reference to the COM object. Once a user approves the UAC prompt, every method on the resulting interface executes with full administrator privileges and zero input validation.

This means a process that obtains the COM reference (via COM marshaling, DLL injection into the caller, or by inheriting the elevated context) can:

  • Read, write, or delete arbitrary files (ReadWriteFile, CopyFile, DeleteFile)
  • Send arbitrary IOCTL codes to any device (CallDriver, DeviceIoControl)
  • Write to any HKLM registry key (WriteLocalMachineRegistryDwordValue)
  • Format any drive letter (FormatNtfs, FormatFs)
  • Resize any file (FastFileResize)

The only protection layer is the initial UAC elevation prompt itself.

Root Cause

BaseCom.cpp passes every parameter — IOCTL codes, file paths, registry key paths, drive numbers — directly through to the underlying Windows API or VeraCrypt driver without checking whether the requested operation is one that VeraCrypt actually needs. The COM interface was designed as a transparent privilege-elevation proxy rather than a constrained API surface.

Fix

Add input validation at the top of each BaseCom method, before any privileged operation executes. Invalid input is rejected with ERROR_ACCESS_DENIED.

src/Common/BaseCom.cpp — central validation layer inherited by both COM servers:

  • CallDriver: IOCTL whitelist (43 known TC_IOCTL/VC_IOCTL codes)
  • DeviceIoControl: IOCTL whitelist (20 standard disk/storage/filesystem codes) and device path format validation (\\.\ or \\?\ prefix required)
  • CopyFile, DeleteFile: path traversal rejection (..) and requirement that both paths contain "VeraCrypt" (all legitimate callers operate on VeraCrypt configuration files)
  • ReadWriteFile, GetFileSize, FastFileResize: path traversal rejection; device path format validation when device=TRUE
  • WriteLocalMachineRegistryDwordValue: key path prefix whitelist (4 HKLM subtrees actually used by VeraCrypt), with boundary checking to prevent prefix-extension attacks (e.g. veracryptEvil does not match veracrypt)

src/Format/FormatCom.cpp — format-specific validation:

  • FormatNtfs, FormatFs: drive number range check (0–25, representing A–Z)

All whitelists were derived by auditing every call site in the codebase that invokes these methods through the Elevator class or the Uac* wrapper functions.

@idrassi idrassi self-assigned this Apr 13, 2026
@idrassi
Copy link
Copy Markdown
Member

idrassi commented Apr 13, 2026

Thank you for looking into this area.
I agree this COM surface needs hardening but this patch cannot be merged as-is. It appears to introduce at least two correctness regressions: device paths such as PhysicalDriveN used by boot encryption fallback are rejected before the elevated Device wrapper can open them, and invalid FormatNtfs/FormatFs drive numbers return FALSE/0, which the existing caller treats as success.

The validation policy also needs to be narrower. The driver IOCTL whitelist currently includes all current VeraCrypt driver IOCTLs, including mutating operations, so it does not materially constrain the elevated broker beyond blocking unknown codes. The DeviceIoControl list also includes mutating FSCTLs that do not appear to be required by current elevated COM call sites. For path-based methods, substring checks like "contains veracrypt" and .. rejection are not a reliable location policy.

I think this should be reworked incrementally: first preserve existing elevated boot/system drive behavior, fix the format return value bug, and derive whitelists from actual elevated COM call sites. Then add per-operation validation where practical, with special treatment for workflows that genuinely require caller-provided paths, such as fast creation of user selected containers.

TC_IOCTL_ABORT_DECOY_SYSTEM_WIPE,
TC_IOCTL_GET_DECOY_SYSTEM_WIPE_STATUS,
TC_IOCTL_GET_DECOY_SYSTEM_WIPE_RESULT,
TC_IOCTL_WRITE_BOOT_DRIVE_SECTOR,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whitelist currently includes every current driver IOCTL, including mutating or destructive operations such as TC_IOCTL_BOOT_ENCRYPTION_SETUP, TC_IOCTL_WRITE_BOOT_DRIVE_SECTOR, TC_IOCTL_START_DECOY_SYSTEM_WIPE and VC_IOCTL_EMERGENCY_CLEAR_ALL_KEYS.

That does not create a meaningful authorization boundary for the elevated COM server. It only blocks unknown future IOCTLs. Please narrow this to IOCTLs actually needed through elevated COM and add per-IOCTL validation for input/output sizes, structure fields, target device, offsets, and setup state where applicable before dispatching.

Comment on lines +103 to +106
FSCTL_MOVE_FILE,
FSCTL_ALLOW_EXTENDED_DASD_IO,
FSCTL_SET_SPARSE,
FSCTL_EXTEND_VOLUME,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mutating FSCTLs FSCTL_MOVE_FILE / FSCTL_EXTEND_VOLUME should not be exposed through a generic elevated DeviceIoControl broker without operation-specific validation. With this list, the caller still controls both the target path/device and the input buffer for operations such as FSCTL_MOVE_FILE, FSCTL_EXTEND_VOLUME, FSCTL_SHRINK_VOLUME and
IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES.

Please remove IOCTLs that are not required by current elevated COM call sites or replace them with dedicated broker methods that validate the exact target, buffer size, fields, and operation context.

{
if (!path)
return FALSE;
if (wcsstr (path, L"..") != NULL)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a safe path validation policy. It rejects legitimate file names containing "..." but still accepts arbitrary absolute paths such as "C:\Windows..." and it does not handle canonicalization, junctions, symlinks, mount points, or other reparse points.

Please replace this with canonical path validation tied to each operation's expected target. User selected container paths should be treated as a separate workflow with explicit validation.

return FALSE;
for (size_t i = 0; i <= pathLen - 9; i++)
{
if (_wcsnicmp (&path[i], L"veracrypt", 9) == 0)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking for a veracrypt substring is not a safe location policy. A caller can choose arbitrary paths containing that substring and this does not protect against reparse-point redirection.

Please restrict CopyFile/DeleteFile to the exact expected VeraCrypt-managed files after resolving/canonicalizing the path or replace these generic methods with dedicated operations for the known copy/delete workflows.

{
if (!path || wcslen (path) < 4)
return FALSE;
return (path[0] == L'\\' && path[1] == L'\\' && (path[2] == L'.' || path[2] == L'?') && path[3] == L'\\');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks existing elevated device fallback paths. Device::Device opens PhysicalDriveN locally by prepending \.\ but stores the original Path. System-drive configuration uses values like PhysicalDrive. When non-elevated access fails with ERROR_ACCESS_DENIED, Elevator::ReadWriteFile / Elevator::DeviceIoControl passes that original PhysicalDrive0 string to COM and this validator rejects it.

Accepting \.\PhysicalDrive0 here is not sufficient by itself. Device::Device only preserves paths that already start with \?. Other inputs are prefixed with "\." so passing \.\PhysicalDrive0 into Device::Device would produce an invalid doubled prefix. Please normalize device paths consistently in one place while preserving the existing PhysicalDriveN elevated fallback behavior.

{
if (device)
{
if (!IsValidDevicePath (filePath))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For device == TRUE, this currently performs only syntactic prefix validation. Even after the path normalization issue is fixed, this remains a generic elevated raw device read/write primitive.

Please constrain elevated device reads/writes to the boot/system-drive workflows that require them and validate the target device plus permitted offsets and sizes for each operation.


DWORD BaseCom::WriteLocalMachineRegistryDwordValue (BSTR keyPath, BSTR valueName, DWORD value)
{
if (!IsRegistryKeyPathAllowed (keyPath))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prefix whitelist is still too broad for a generic elevated DWORD write method. Current callers only need exact writes such as veracrypt\VeraCryptConfig with known config bits, VeraCryptEnableMemoryProtection/VeraCryptEnableScreenProtection as 0 or 1, VeraCryptEncryptionFreeCpuCount within the CPU-count limit, VeraCryptSystemFavorites\VeraCryptSystemFavoritesConfig with known service-config bits and HibernateEnabled/HiberbootEnabled set to 0.

Please validate exact (keyPath, valueName, value) combinations. For example, this method should not allow unrelated service values such as SYSTEM\CurrentControlSet\Services\veracrypt\Start: service start changes already have the dedicated SetDriverServiceStartType path.


DWORD BaseCom::FastFileResize (BSTR filePath, __int64 fileSize)
{
if (!ValidatePathNoTraversal (filePath))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FastFileResize is especially sensitive because it enables SE_MANAGE_VOLUME_NAME and calls SetFileValidData. A ".." substring check is not enough for a privileged operation on a caller-provided path.

Please validate that the target is the container file created by the formatting workflow, reject reparse-point/symlink redirection and avoid accepting arbitrary absolute paths through this elevated method.

virtual BOOL STDMETHODCALLTYPE FormatNtfs (int driveNo, int clusterSize)
{
if (driveNo < 0 || driveNo > 25)
return FALSE;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns FALSE but UacFormatNtfs consumes the COM return value as an int status where 0 means success and non-zero means failure. Invalid input would therefore be reported as a successful format.

Please return a non-zero failure code such as ERROR_INVALID_DRIVE or ERROR_INVALID_PARAMETER or update the COM method contract consistently so invalid input
cannot be interpreted as success.

virtual BOOL STDMETHODCALLTYPE FormatFs (int driveNo, int clusterSize, int fsType)
{
if (driveNo < 0 || driveNo > 25)
return FALSE;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as FormatNtfs: FALSE is 0 and the caller treats 0 as success. Please return a non-zero error code for invalid drive numbers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants