COM interface privilege escalation#1670
COM interface privilege escalation#1670audriusbuika wants to merge 2 commits intoveracrypt:masterfrom
Conversation
|
Thank you for looking into this area. 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, |
There was a problem hiding this comment.
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.
| FSCTL_MOVE_FILE, | ||
| FSCTL_ALLOW_EXTENDED_DASD_IO, | ||
| FSCTL_SET_SPARSE, | ||
| FSCTL_EXTEND_VOLUME, |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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'\\'); |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
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:
ReadWriteFile,CopyFile,DeleteFile)CallDriver,DeviceIoControl)HKLMregistry key (WriteLocalMachineRegistryDwordValue)FormatNtfs,FormatFs)FastFileResize)The only protection layer is the initial UAC elevation prompt itself.
Root Cause
BaseCom.cpppasses 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
BaseCommethod, before any privileged operation executes. Invalid input is rejected withERROR_ACCESS_DENIED.src/Common/BaseCom.cpp— central validation layer inherited by both COM servers:CallDriver: IOCTL whitelist (43 knownTC_IOCTL/VC_IOCTLcodes)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 whendevice=TRUEWriteLocalMachineRegistryDwordValue: key path prefix whitelist (4HKLMsubtrees actually used by VeraCrypt), with boundary checking to prevent prefix-extension attacks (e.g.veracryptEvildoes not matchveracrypt)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
Elevatorclass or theUac*wrapper functions.