From 1871765a768e5247bb811097842fc56b394317c8 Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Sun, 7 Jun 2026 17:14:37 +0900 Subject: [PATCH] Windows: allow cancelling long mount operations Add a root-driver abort IOCTL that bypasses the mount control mutex and sets cooperative KDF abort flags for the active mount. Restrict abort requests to privileged callers or to the user that initiated the pending mount, and retry early wait-dialog cancel requests until the driver has registered the cancellable mount context. Wire the wait dialog Cancel button to send the abort request through a fresh driver handle, and propagate ERR_USER_ABORT through header/cache processing. Add a /cancelmount command-line switch that sends the same abort request without displaying UI, so users can cancel hidden-wait-dialog mount operations from another process. --- .../en/Command Line Usage for Windows.html | 4 + src/Common/Apidrvr.h | 8 + src/Common/Cache.c | 37 +++- src/Common/Cache.h | 1 + src/Common/Common.rc | 5 +- src/Common/Dlgcode.c | 92 +++++++- src/Common/Dlgcode.h | 1 + src/Common/Volumes.c | 47 +++- src/Common/Volumes.h | 1 + src/Driver/Ntdriver.c | 201 ++++++++++++++++++ src/Driver/Ntdriver.h | 17 ++ src/Driver/Ntvol.c | 34 ++- src/Mount/Mount.c | 12 +- 13 files changed, 437 insertions(+), 23 deletions(-) diff --git a/doc/html/en/Command Line Usage for Windows.html b/doc/html/en/Command Line Usage for Windows.html index b490ec8e..bbfa3e3a 100644 --- a/doc/html/en/Command Line Usage for Windows.html +++ b/doc/html/en/Command Line Usage for Windows.html @@ -110,6 +110,10 @@ if it is followed by n or no: don't try to mou If it is followed by n or no: force the display waiting dialog is displayed while performing operations. +/cancelmount +Cancels any currently running mount operation and exits without displaying a status message. The process returns exit code 0 when the cancel request is accepted by the driver, or 1 otherwise. + + /secureDesktop If it is followed by y or yes or if no parameter is specified: display password dialog and token PIN dialog in a dedicated secure desktop to protect against certain types of attacks.
If it is followed by n or no: the password dialog and token PIN dialog are displayed in the normal desktop. diff --git a/src/Common/Apidrvr.h b/src/Common/Apidrvr.h index 81213a27..6b65f90d 100644 --- a/src/Common/Apidrvr.h +++ b/src/Common/Apidrvr.h @@ -129,6 +129,8 @@ #define VC_IOCTL_ENCRYPTION_QUEUE_PARAMS TC_IOCTL (43) +#define TC_IOCTL_ABORT_MOUNT_VOLUME TC_IOCTL (44) + // Undocumented IOCTL sent by Windows 10 when handling EFS data on volumes #define IOCTL_UNKNOWN_WINDOWS10_EFS_ACCESS 0x455610D8 @@ -180,6 +182,12 @@ typedef struct BOOL VolumeMasterKeyVulnerable; } MOUNT_STRUCT; +typedef struct +{ + int nDosDriveNo; /* Drive number whose pending mount should be aborted; -1 aborts any pending mount */ + int nReturnCode; /* Return code back from driver */ +} MOUNT_ABORT_STRUCT; + typedef struct { int nDosDriveNo; /* Drive letter to unmount */ diff --git a/src/Common/Cache.c b/src/Common/Cache.c index c3b7ed3a..263ecf32 100644 --- a/src/Common/Cache.c +++ b/src/Common/Cache.c @@ -24,6 +24,22 @@ int CachedPim[CACHE_SIZE]; int cacheEmpty = 1; static int nPasswordIdx = 0; +static BOOL IsUserAbortRequested (long volatile *pUserAbort) +{ + return pUserAbort && *pUserAbort; +} + +static BOOL ResetAbortKeyDerivation (long volatile *pAbortKeyDerivation, long volatile *pUserAbort) +{ + if (IsUserAbortRequested (pUserAbort)) + return FALSE; + + if (pAbortKeyDerivation) + *pAbortKeyDerivation = 0; + + return TRUE; +} + uint64 VcGetPasswordEncryptionID (Password* pPassword) { return ((uint64) pPassword->Text) + ((uint64) pPassword); @@ -39,7 +55,7 @@ void VcUnprotectPassword (Password* pPassword, uint64 encID) VcProtectPassword (pPassword, encID); } -int ReadVolumeHeaderWCache (BOOL bBoot, BOOL bCache, BOOL bCachePim, unsigned char *header, Password *password, int pkcs5_prf, int pim, PCRYPTO_INFO *retInfo) +int ReadVolumeHeaderWCacheWithAbort (BOOL bBoot, BOOL bCache, BOOL bCachePim, unsigned char *header, Password *password, int pkcs5_prf, int pim, PCRYPTO_INFO *retInfo, long volatile *pAbortKeyDerivation, long volatile *pUserAbort) { int nReturnCode = ERR_PASSWORD_WRONG; int i, effectivePim; @@ -47,7 +63,10 @@ int ReadVolumeHeaderWCache (BOOL bBoot, BOOL bCache, BOOL bCachePim, unsigned ch /* Attempt to recognize volume using mount password */ if (password->Length > 0) { - nReturnCode = ReadVolumeHeader (bBoot, header, password, pkcs5_prf, pim, retInfo, NULL); + if (!ResetAbortKeyDerivation (pAbortKeyDerivation, pUserAbort)) + return ERR_USER_ABORT; + + nReturnCode = ReadVolumeHeaderWithAbort (bBoot, header, password, pkcs5_prf, pim, retInfo, NULL, pAbortKeyDerivation, pUserAbort); /* Save mount passwords back into cache if asked to do so */ if (bCache && (nReturnCode == 0 || nReturnCode == ERR_CIPHER_INIT_WEAK_KEY)) @@ -113,7 +132,14 @@ int ReadVolumeHeaderWCache (BOOL bBoot, BOOL bCache, BOOL bCachePim, unsigned ch effectivePim = CachedPim[i]; else effectivePim = pim; - nReturnCode = ReadVolumeHeader (bBoot, header, pCurrentPassword, pkcs5_prf, effectivePim, retInfo, NULL); + + if (!ResetAbortKeyDerivation (pAbortKeyDerivation, pUserAbort)) + { + nReturnCode = ERR_USER_ABORT; + break; + } + + nReturnCode = ReadVolumeHeaderWithAbort (bBoot, header, pCurrentPassword, pkcs5_prf, effectivePim, retInfo, NULL, pAbortKeyDerivation, pUserAbort); if (nReturnCode != ERR_PASSWORD_WRONG) break; @@ -128,6 +154,11 @@ int ReadVolumeHeaderWCache (BOOL bBoot, BOOL bCache, BOOL bCachePim, unsigned ch return nReturnCode; } +int ReadVolumeHeaderWCache (BOOL bBoot, BOOL bCache, BOOL bCachePim, unsigned char *header, Password *password, int pkcs5_prf, int pim, PCRYPTO_INFO *retInfo) +{ + return ReadVolumeHeaderWCacheWithAbort (bBoot, bCache, bCachePim, header, password, pkcs5_prf, pim, retInfo, NULL, NULL); +} + void AddPasswordToCache (Password *password, int pim, BOOL bCachePim) { diff --git a/src/Common/Cache.h b/src/Common/Cache.h index 280c73c5..011bdc28 100644 --- a/src/Common/Cache.h +++ b/src/Common/Cache.h @@ -23,4 +23,5 @@ extern int cacheEmpty; void AddPasswordToCache (Password *password, int pim, BOOL bCachePim); void AddLegacyPasswordToCache (__unaligned PasswordLegacy *password, int pim); int ReadVolumeHeaderWCache (BOOL bBoot, BOOL bCache, BOOL bCachePim, unsigned char *header, Password *password, int pkcs5_prf, int pim, PCRYPTO_INFO *retInfo); +int ReadVolumeHeaderWCacheWithAbort (BOOL bBoot, BOOL bCache, BOOL bCachePim, unsigned char *header, Password *password, int pkcs5_prf, int pim, PCRYPTO_INFO *retInfo, long volatile *pAbortKeyDerivation, long volatile *pUserAbort); void WipeCache (void); diff --git a/src/Common/Common.rc b/src/Common/Common.rc index 640895a2..f6196905 100644 --- a/src/Common/Common.rc +++ b/src/Common/Common.rc @@ -338,8 +338,9 @@ STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | CAPTION "VeraCrypt" FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN - CTEXT "Please wait...\nThis process may take a long time and VeraCrypt may seem unresponsive.",IDT_STATIC_MODAL_WAIT_DLG_INFO,9,11,274,33 - CONTROL "",IDC_WAIT_PROGRESS_BAR,"msctls_progress32",WS_BORDER,7,49,278,14 + CTEXT "Please wait...\nThis process may take a long time and VeraCrypt may seem unresponsive.",IDT_STATIC_MODAL_WAIT_DLG_INFO,9,7,274,30 + CONTROL "",IDC_WAIT_PROGRESS_BAR,"msctls_progress32",WS_BORDER,7,41,278,12 + PUSHBUTTON "Cancel",IDCANCEL,121,56,50,14 END IDD_TEXT_EDIT_DLG DIALOGEX 0, 0, 372, 220 diff --git a/src/Common/Dlgcode.c b/src/Common/Dlgcode.c index 8915578a..e54ee739 100644 --- a/src/Common/Dlgcode.c +++ b/src/Common/Dlgcode.c @@ -8880,12 +8880,18 @@ BOOL GetPhysicalDriveStorageInformation(UINT nDriveNumber, STORAGE_ACCESS_ALIGNM // implementation of the generic wait dialog mechanism static UINT g_wmWaitDlg = ::RegisterWindowMessage(L"VeraCryptWaitDlgMessage"); +#define WAIT_DLG_CANCEL_RETRY_TIMER_ID 1 +#define WAIT_DLG_CANCEL_RETRY_INTERVAL 250 + +typedef BOOL (CALLBACK* WaitCancelProc)(void* pArg, HWND hWaitDlg); typedef struct { HWND hwnd; void* pArg; WaitThreadProc callback; + WaitCancelProc cancelCallback; + BOOL cancelRequested; } WaitThreadParam; static void _cdecl WaitThread (void* pParam) @@ -8899,6 +8905,22 @@ static void _cdecl WaitThread (void* pParam) PostMessage (pThreadParam->hwnd, g_wmWaitDlg, 0, 0); } +static BOOL WaitDlgTryCancel (HWND hwndDlg, WaitThreadParam* thParam) +{ + if (thParam && thParam->cancelCallback) + { + if (thParam->cancelCallback (thParam->pArg, hwndDlg)) + { + KillTimer (hwndDlg, WAIT_DLG_CANCEL_RETRY_TIMER_ID); + return TRUE; + } + + SetTimer (hwndDlg, WAIT_DLG_CANCEL_RETRY_TIMER_ID, WAIT_DLG_CANCEL_RETRY_INTERVAL, NULL); + } + + return FALSE; +} + BOOL CALLBACK WaitDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { WORD lw = LOWORD (wParam); @@ -8908,6 +8930,7 @@ BOOL CALLBACK WaitDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) case WM_INITDIALOG: { WaitThreadParam* thParam = (WaitThreadParam*) lParam; + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) thParam); // set the progress bar type to MARQUEE (indefinite progress) HWND hProgress = GetDlgItem (hwndDlg, IDC_WAIT_PROGRESS_BAR); @@ -8935,24 +8958,50 @@ BOOL CALLBACK WaitDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) } LocalizeDialog (hwndDlg, NULL); + if (!thParam->cancelCallback) + ShowWindow (GetDlgItem (hwndDlg, IDCANCEL), SW_HIDE); _beginthread(WaitThread, 0, thParam); return 0; } case WM_COMMAND: - if (lw == IDOK || lw == IDCANCEL) + if (lw == IDCANCEL) + { + WaitThreadParam* thParam = (WaitThreadParam*) GetWindowLongPtr (hwndDlg, DWLP_USER); + if (thParam && thParam->cancelCallback && !thParam->cancelRequested) + { + thParam->cancelRequested = TRUE; + EnableWindow (GetDlgItem (hwndDlg, IDCANCEL), FALSE); + WaitDlgTryCancel (hwndDlg, thParam); + } + return 1; + } + + if (lw == IDOK) return 1; else return 0; + case WM_TIMER: + if (wParam == WAIT_DLG_CANCEL_RETRY_TIMER_ID) + { + WaitThreadParam* thParam = (WaitThreadParam*) GetWindowLongPtr (hwndDlg, DWLP_USER); + if (thParam && thParam->cancelRequested) + WaitDlgTryCancel (hwndDlg, thParam); + return 1; + } + return 0; + case WM_DESTROY: + KillTimer (hwndDlg, WAIT_DLG_CANCEL_RETRY_TIMER_ID); DetachProtectionFromCurrentThread(); return 0; default: if (msg == g_wmWaitDlg) { + KillTimer (hwndDlg, WAIT_DLG_CANCEL_RETRY_TIMER_ID); EndDialog (hwndDlg, IDOK); return 1; } @@ -9015,11 +9064,13 @@ static LRESULT CALLBACK ShowWaitDialogParentWndProc (HWND hWnd, UINT message, WP } -void ShowWaitDialog(HWND hwnd, BOOL bUseHwndAsParent, WaitThreadProc callback, void* pArg) +static void ShowWaitDialogEx(HWND hwnd, BOOL bUseHwndAsParent, WaitThreadProc callback, WaitCancelProc cancelCallback, void* pArg) { BOOL bEffectiveHideWaitingDialog = bCmdHideWaitingDialogValid? bCmdHideWaitingDialog : bHideWaitingDialog; WaitThreadParam threadParam; threadParam.callback = callback; + threadParam.cancelCallback = cancelCallback; + threadParam.cancelRequested = FALSE; threadParam.pArg = pArg; if (WaitDialogDisplaying || bEffectiveHideWaitingDialog) @@ -9082,6 +9133,11 @@ void ShowWaitDialog(HWND hwnd, BOOL bUseHwndAsParent, WaitThreadProc callback, v } } +void ShowWaitDialog(HWND hwnd, BOOL bUseHwndAsParent, WaitThreadProc callback, void* pArg) +{ + ShowWaitDialogEx (hwnd, bUseHwndAsParent, callback, NULL, pArg); +} + #ifndef SETUP /************************************************************************/ @@ -9108,6 +9164,26 @@ static BOOL PerformMountIoctl (MOUNT_STRUCT* pmount, LPDWORD pdwResult, BOOL use sizeof (MOUNT_STRUCT), pmount, sizeof (MOUNT_STRUCT), pdwResult, NULL); } +BOOL AbortMountOperation (int nDosDriveNo) +{ + BOOL bResult; + DWORD dwResult; + HANDLE hAbortDriver = CreateFile (WIN32_ROOT_PREFIX, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + MOUNT_ABORT_STRUCT abortMount; + + if (hAbortDriver == INVALID_HANDLE_VALUE) + return FALSE; + + memset (&abortMount, 0, sizeof (abortMount)); + abortMount.nDosDriveNo = nDosDriveNo; + + bResult = DeviceIoControl (hAbortDriver, TC_IOCTL_ABORT_MOUNT_VOLUME, &abortMount, + sizeof (abortMount), &abortMount, sizeof (abortMount), &dwResult, NULL); + + CloseHandle (hAbortDriver); + return bResult && abortMount.nReturnCode == ERR_USER_ABORT; +} + // specific definitions and implementation for support of mount operation // in wait dialog mechanism @@ -9130,6 +9206,13 @@ void CALLBACK MountWaitThreadProc(void* pArg, HWND ) pThreadParam->dwLastError = GetLastError (); } +BOOL CALLBACK MountWaitCancelProc(void* pArg, HWND ) +{ + MountThreadParam* pThreadParam = (MountThreadParam*) pArg; + + return AbortMountOperation (pThreadParam->pmount->nDosDriveNo); +} + /************************************************************************/ // Use only cached passwords if password = NULL @@ -9366,7 +9449,7 @@ retry: mountThreadParam.pdwResult = &dwResult; mountThreadParam.dwLastError = ERROR_SUCCESS; - ShowWaitDialog (hwndDlg, FALSE, MountWaitThreadProc, &mountThreadParam); + ShowWaitDialogEx (hwndDlg, FALSE, MountWaitThreadProc, MountWaitCancelProc, &mountThreadParam); dwLastError = mountThreadParam.dwLastError; } @@ -9428,6 +9511,9 @@ retry: if (mount.nReturnCode != 0) { + if (mount.nReturnCode == ERR_USER_ABORT) + return -1; + if (mount.nReturnCode == ERR_PASSWORD_WRONG) { // Do not report wrong password, if not instructed to diff --git a/src/Common/Dlgcode.h b/src/Common/Dlgcode.h index e249c5d1..f4308b8b 100644 --- a/src/Common/Dlgcode.h +++ b/src/Common/Dlgcode.h @@ -412,6 +412,7 @@ BOOL IsDriveAvailable (int driveNo); BOOL IsDeviceMounted (wchar_t *deviceName); int DriverUnmountVolume (HWND hwndDlg, int nDosDriveNo, BOOL forced); void BroadcastDeviceChange (WPARAM message, int nDosDriveNo, DWORD driveMap); +BOOL AbortMountOperation (int nDosDriveNo); int MountVolume (HWND hwndDlg, int driveNo, wchar_t *volumePath, Password *password, int pkcs5, int pim, BOOL cachePassword, BOOL cachePim, BOOL sharedAccess, const MountOptions* const mountOptions, BOOL quiet, BOOL bReportWrongPassword); BOOL UnmountVolume (HWND hwndDlg , int nDosDriveNo, BOOL forceUnmount); BOOL UnmountVolumeAfterFormatExCall (HWND hwndDlg, int nDosDriveNo); diff --git a/src/Common/Volumes.c b/src/Common/Volumes.c index 495bbc0b..9d1c5d04 100644 --- a/src/Common/Volumes.c +++ b/src/Common/Volumes.c @@ -186,7 +186,7 @@ static int MapArgon2ResultToVcError (int result) BOOL ReadVolumeHeaderRecoveryMode = FALSE; -int ReadVolumeHeader (BOOL bBoot, unsigned char *encryptedHeader, Password *password, int selected_pkcs5_prf, int pim, PCRYPTO_INFO *retInfo, CRYPTO_INFO *retHeaderCryptoInfo) +int ReadVolumeHeaderWithAbort (BOOL bBoot, unsigned char *encryptedHeader, Password *password, int selected_pkcs5_prf, int pim, PCRYPTO_INFO *retInfo, CRYPTO_INFO *retHeaderCryptoInfo, long volatile *pAbortKeyDerivation, long volatile *pUserAbort) { unsigned char header[TC_VOLUME_HEADER_EFFECTIVE_SIZE]; unsigned char* keyInfoBuffer = NULL; @@ -203,6 +203,7 @@ int ReadVolumeHeader (BOOL bBoot, unsigned char *encryptedHeader, Password *pass int iterationsCount = 0; int memoryCost = 0; LONG volatile abortKeyDerivation = 0; + LONG volatile *effectiveAbortKeyDerivation = pAbortKeyDerivation ? (LONG volatile *) pAbortKeyDerivation : &abortKeyDerivation; #ifndef VC_DCS_DISABLE_ARGON2 int lastArgon2DerivationResult = 0; #endif @@ -326,6 +327,12 @@ int ReadVolumeHeader (BOOL bBoot, unsigned char *encryptedHeader, Password *pass // Test all available PKCS5 PRFs for (enqPkcs5Prf = FIRST_PRF_ID; enqPkcs5Prf <= LAST_PRF_ID || queuedWorkItems > 0; ++enqPkcs5Prf) { + if (pUserAbort && *pUserAbort) + { + status = ERR_USER_ABORT; + goto err; + } + // if a PRF is specified, we skip all other PRFs if (selected_pkcs5_prf != 0 && enqPkcs5Prf != selected_pkcs5_prf) continue; @@ -355,7 +362,7 @@ int ReadVolumeHeader (BOOL bBoot, unsigned char *encryptedHeader, Password *pass iterationsCount = get_pkcs5_iteration_count (enqPkcs5Prf, pim, bBoot, &memoryCost); EncryptionThreadPoolBeginKeyDerivation (keyDerivationCompletedEvent, noOutstandingWorkItemEvent, &item->KeyReady, outstandingWorkItemCount, enqPkcs5Prf, keyInfo->userKey, - keyInfo->keyLength, keyInfo->salt, iterationsCount, memoryCost, item->DerivedKey, &item->DerivationResult, &abortKeyDerivation); + keyInfo->keyLength, keyInfo->salt, iterationsCount, memoryCost, item->DerivedKey, &item->DerivationResult, effectiveAbortKeyDerivation); ++queuedWorkItems; break; @@ -376,6 +383,12 @@ int ReadVolumeHeader (BOOL bBoot, unsigned char *encryptedHeader, Password *pass item = &keyDerivationWorkItems[i]; if (!item->Free && InterlockedExchangeAdd (&item->KeyReady, 0) == TRUE) { + if (pUserAbort && *pUserAbort) + { + status = ERR_USER_ABORT; + goto err; + } + LONG derivationResult = InterlockedExchangeAdd (&item->DerivationResult, 0); if (derivationResult != 0) { @@ -418,29 +431,29 @@ KeyReady: ; { case SHA512: derive_key_sha512 (keyInfo->userKey, keyInfo->keyLength, keyInfo->salt, - PKCS5_SALT_SIZE, keyInfo->noIterations, dk, GetMaxPkcs5OutSize(), &abortKeyDerivation); + PKCS5_SALT_SIZE, keyInfo->noIterations, dk, GetMaxPkcs5OutSize(), effectiveAbortKeyDerivation); break; case SHA256: derive_key_sha256 (keyInfo->userKey, keyInfo->keyLength, keyInfo->salt, - PKCS5_SALT_SIZE, keyInfo->noIterations, dk, GetMaxPkcs5OutSize(), &abortKeyDerivation); + PKCS5_SALT_SIZE, keyInfo->noIterations, dk, GetMaxPkcs5OutSize(), effectiveAbortKeyDerivation); break; #ifndef WOLFCRYPT_BACKEND case BLAKE2S: derive_key_blake2s (keyInfo->userKey, keyInfo->keyLength, keyInfo->salt, - PKCS5_SALT_SIZE, keyInfo->noIterations, dk, GetMaxPkcs5OutSize(), &abortKeyDerivation); + PKCS5_SALT_SIZE, keyInfo->noIterations, dk, GetMaxPkcs5OutSize(), effectiveAbortKeyDerivation); break; case WHIRLPOOL: derive_key_whirlpool (keyInfo->userKey, keyInfo->keyLength, keyInfo->salt, - PKCS5_SALT_SIZE, keyInfo->noIterations, dk, GetMaxPkcs5OutSize(), &abortKeyDerivation); + PKCS5_SALT_SIZE, keyInfo->noIterations, dk, GetMaxPkcs5OutSize(), effectiveAbortKeyDerivation); break; case STREEBOG: derive_key_streebog(keyInfo->userKey, keyInfo->keyLength, keyInfo->salt, - PKCS5_SALT_SIZE, keyInfo->noIterations, dk, GetMaxPkcs5OutSize(), &abortKeyDerivation); + PKCS5_SALT_SIZE, keyInfo->noIterations, dk, GetMaxPkcs5OutSize(), effectiveAbortKeyDerivation); break; @@ -448,7 +461,7 @@ KeyReady: ; case ARGON2: { int derivationResult = derive_key_argon2(keyInfo->userKey, keyInfo->keyLength, keyInfo->salt, - PKCS5_SALT_SIZE, keyInfo->noIterations, keyInfo->memoryCost, dk, ARGON2_HEADER_KEYDATA_SIZE, &abortKeyDerivation); + PKCS5_SALT_SIZE, keyInfo->noIterations, keyInfo->memoryCost, dk, ARGON2_HEADER_KEYDATA_SIZE, effectiveAbortKeyDerivation); if (derivationResult != 0) { if (selected_pkcs5_prf == 0) @@ -470,6 +483,12 @@ KeyReady: ; } } + if (pUserAbort && *pUserAbort) + { + status = ERR_USER_ABORT; + goto err; + } + // Test all available modes of operation for (cryptoInfo->mode = FIRST_MODE_OF_OPERATION_ID; cryptoInfo->mode <= LAST_MODE_OF_OPERATION; @@ -677,7 +696,7 @@ KeyReady: ; if ((selected_pkcs5_prf == 0) && (encryptionThreadCount > 1)) { // Signal other threads to stop - InterlockedExchange(&abortKeyDerivation, 1); + InterlockedExchange(effectiveAbortKeyDerivation, 1); } #endif goto ret; @@ -689,12 +708,15 @@ KeyReady: ; status = MapArgon2ResultToVcError (lastArgon2DerivationResult); else #endif + if (pUserAbort && *pUserAbort) + status = ERR_USER_ABORT; + else status = ERR_PASSWORD_WRONG; err: #if !defined(_UEFI) // Signal threads to stop - InterlockedExchange(&abortKeyDerivation, 1); + InterlockedExchange(effectiveAbortKeyDerivation, 1); #endif if (cryptoInfo != retHeaderCryptoInfo) { @@ -744,6 +766,11 @@ ret: return status; } +int ReadVolumeHeader (BOOL bBoot, unsigned char *encryptedHeader, Password *password, int selected_pkcs5_prf, int pim, PCRYPTO_INFO *retInfo, CRYPTO_INFO *retHeaderCryptoInfo) +{ + return ReadVolumeHeaderWithAbort (bBoot, encryptedHeader, password, selected_pkcs5_prf, pim, retInfo, retHeaderCryptoInfo, NULL, NULL); +} + #if defined(_WIN32) && !defined(_UEFI) void ComputeBootloaderFingerprint (uint8 *bootLoaderBuf, unsigned int bootLoaderSize, uint8* fingerprint) { diff --git a/src/Common/Volumes.h b/src/Common/Volumes.h index 02e3e7f7..3b642cfb 100644 --- a/src/Common/Volumes.h +++ b/src/Common/Volumes.h @@ -152,6 +152,7 @@ int CreateVolumeHeaderInMemory(BOOL bBoot, unsigned char *encryptedHeader, int e BOOL RandgetBytes(unsigned char *buf, int len, BOOL forceSlowPoll); #else int ReadVolumeHeader (BOOL bBoot, unsigned char *encryptedHeader, Password *password, int pkcs5_prf, int pim, PCRYPTO_INFO *retInfo, CRYPTO_INFO *retHeaderCryptoInfo); +int ReadVolumeHeaderWithAbort (BOOL bBoot, unsigned char *encryptedHeader, Password *password, int pkcs5_prf, int pim, PCRYPTO_INFO *retInfo, CRYPTO_INFO *retHeaderCryptoInfo, long volatile *pAbortKeyDerivation, long volatile *pUserAbort); #if defined(_WIN32) && !defined(_UEFI) void ComputeBootloaderFingerprint (uint8 *bootLoaderBuf, unsigned int bootLoaderSize, uint8* fingerprint); #endif diff --git a/src/Driver/Ntdriver.c b/src/Driver/Ntdriver.c index cf0150f9..aa56c98e 100644 --- a/src/Driver/Ntdriver.c +++ b/src/Driver/Ntdriver.c @@ -122,6 +122,8 @@ PDRIVER_OBJECT TCDriverObject; PDEVICE_OBJECT RootDeviceObject = NULL; static KMUTEX RootDeviceControlMutex; +static KMUTEX MountCancelContextMutex; +static MOUNT_CANCEL_CONTEXT ActiveMountCancelContext; BOOL DriverShuttingDown = FALSE; BOOL SelfTestsPassed; int LastUniqueVolumeId; @@ -570,6 +572,9 @@ NTSTATUS TCDispatchQueueIRP (PDEVICE_OBJECT DeviceObject, PIRP Irp) { if (irpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL) { + if (irpSp->Parameters.DeviceIoControl.IoControlCode == TC_IOCTL_ABORT_MOUNT_VOLUME) + return ProcessMainDeviceControlIrp (DeviceObject, Extension, Irp); + NTSTATUS status = KeWaitForMutexObject (&RootDeviceControlMutex, Executive, KernelMode, FALSE, NULL); if (!NT_SUCCESS (status)) return status; @@ -694,6 +699,7 @@ NTSTATUS TCCreateRootDeviceObject (PDRIVER_OBJECT DriverObject) *bRootExtension = TRUE; KeInitializeMutex (&RootDeviceControlMutex, 0); + KeInitializeMutex (&MountCancelContextMutex, 0); ntStatus = IoCreateSymbolicLink (&Win32NameString, &ntUnicodeString); @@ -800,6 +806,177 @@ void RootDeviceControlMutexRelease () KeReleaseMutex (&RootDeviceControlMutex, FALSE); } + +static void RegisterMountCancelContext (PEXTENSION Extension, int nDosDriveNo) +{ + if (!NT_SUCCESS (KeWaitForMutexObject (&MountCancelContextMutex, Executive, KernelMode, FALSE, NULL))) + return; + + ActiveMountCancelContext.nDosDriveNo = nDosDriveNo; + ActiveMountCancelContext.UserSidLength = 0; + InterlockedExchange (&ActiveMountCancelContext.UserSidValid, 0); + InterlockedExchange (&ActiveMountCancelContext.UserAbortRequested, 0); + InterlockedExchange (&ActiveMountCancelContext.KeyDerivationAbort, 0); + InterlockedIncrement (&ActiveMountCancelContext.SequenceNumber); + Extension->MountCancelContext = &ActiveMountCancelContext; + InterlockedExchange (&ActiveMountCancelContext.Active, 1); + + KeReleaseMutex (&MountCancelContextMutex, FALSE); +} + + +static void SetMountCancelContextUserSid (PEXTENSION Extension, PSID userSid) +{ + ULONG sidLength; + + if (!userSid || Extension->MountCancelContext != &ActiveMountCancelContext) + return; + + sidLength = RtlLengthSid (userSid); + if (sidLength > sizeof (ActiveMountCancelContext.UserSid)) + return; + + if (!NT_SUCCESS (KeWaitForMutexObject (&MountCancelContextMutex, Executive, KernelMode, FALSE, NULL))) + return; + + if (Extension->MountCancelContext == &ActiveMountCancelContext + && InterlockedExchangeAdd (&ActiveMountCancelContext.Active, 0) != 0 + && NT_SUCCESS (RtlCopySid (sizeof (ActiveMountCancelContext.UserSid), ActiveMountCancelContext.UserSid, userSid))) + { + ActiveMountCancelContext.UserSidLength = sidLength; + InterlockedExchange (&ActiveMountCancelContext.UserSidValid, 1); + } + + KeReleaseMutex (&MountCancelContextMutex, FALSE); +} + + +static void UnregisterMountCancelContext (PEXTENSION Extension) +{ + if (!NT_SUCCESS (KeWaitForMutexObject (&MountCancelContextMutex, Executive, KernelMode, FALSE, NULL))) + return; + + if (Extension->MountCancelContext == &ActiveMountCancelContext) + { + InterlockedExchange (&ActiveMountCancelContext.Active, 0); + InterlockedExchange (&ActiveMountCancelContext.KeyDerivationAbort, 1); + InterlockedExchange (&ActiveMountCancelContext.UserSidValid, 0); + ActiveMountCancelContext.UserSidLength = 0; + Extension->MountCancelContext = NULL; + } + + KeReleaseMutex (&MountCancelContextMutex, FALSE); +} + + +static BOOL CurrentUserMatchesSid (PSID userSid) +{ + SECURITY_SUBJECT_CONTEXT subContext; + PACCESS_TOKEN accessToken; + PTOKEN_USER tokenUser; + BOOL result = FALSE; + + if (!userSid) + return FALSE; + + SeCaptureSubjectContext (&subContext); + SeLockSubjectContext(&subContext); + if (subContext.ClientToken && subContext.ImpersonationLevel >= SecurityImpersonation) + accessToken = subContext.ClientToken; + else + accessToken = subContext.PrimaryToken; + + if (!accessToken) + goto ret; + + if (SeTokenIsAdmin (accessToken)) + { + result = TRUE; + goto ret; + } + + if (!NT_SUCCESS (SeQueryInformationToken (accessToken, TokenUser, &tokenUser))) + goto ret; + + result = RtlEqualSid (userSid, tokenUser->User.Sid); + ExFreePool (tokenUser); // Documented in newer versions of WDK + +ret: + SeUnlockSubjectContext(&subContext); + SeReleaseSubjectContext (&subContext); + return result; +} + + +static BOOL CurrentUserCanAbortPendingMount (PSID userSid, BOOL userSidValid) +{ + if (IoIsSystemThread (PsGetCurrentThread())) + return TRUE; + + if (UserCanAccessDriveDevice()) + return TRUE; + + return userSidValid && CurrentUserMatchesSid (userSid); +} + + +static NTSTATUS AbortPendingMount (MOUNT_ABORT_STRUCT *abortRequest) +{ + UCHAR userSid[SECURITY_MAX_SID_SIZE]; + ULONG userSidLength = 0; + LONG sequenceNumber = 0; + BOOL userSidValid = FALSE; + BOOL activeMountMatches = FALSE; + + if (!NT_SUCCESS (KeWaitForMutexObject (&MountCancelContextMutex, Executive, KernelMode, FALSE, NULL))) + return STATUS_UNSUCCESSFUL; + + if (InterlockedExchangeAdd (&ActiveMountCancelContext.Active, 0) != 0 + && (abortRequest->nDosDriveNo < 0 || abortRequest->nDosDriveNo == ActiveMountCancelContext.nDosDriveNo)) + { + activeMountMatches = TRUE; + sequenceNumber = InterlockedExchangeAdd (&ActiveMountCancelContext.SequenceNumber, 0); + userSidValid = InterlockedExchangeAdd (&ActiveMountCancelContext.UserSidValid, 0) != 0; + userSidLength = ActiveMountCancelContext.UserSidLength; + if (userSidValid && userSidLength <= sizeof (userSid)) + memcpy (userSid, ActiveMountCancelContext.UserSid, userSidLength); + else + userSidValid = FALSE; + } + + KeReleaseMutex (&MountCancelContextMutex, FALSE); + + if (!activeMountMatches) + { + abortRequest->nReturnCode = ERR_DRIVE_NOT_FOUND; + return STATUS_SUCCESS; + } + + if (!CurrentUserCanAbortPendingMount (userSidValid ? (PSID) userSid : NULL, userSidValid)) + { + abortRequest->nReturnCode = ERR_ACCESS_DENIED; + return STATUS_ACCESS_DENIED; + } + + if (!NT_SUCCESS (KeWaitForMutexObject (&MountCancelContextMutex, Executive, KernelMode, FALSE, NULL))) + return STATUS_UNSUCCESSFUL; + + if (InterlockedExchangeAdd (&ActiveMountCancelContext.Active, 0) != 0 + && sequenceNumber == InterlockedExchangeAdd (&ActiveMountCancelContext.SequenceNumber, 0) + && (abortRequest->nDosDriveNo < 0 || abortRequest->nDosDriveNo == ActiveMountCancelContext.nDosDriveNo)) + { + InterlockedExchange (&ActiveMountCancelContext.UserAbortRequested, 1); + InterlockedExchange (&ActiveMountCancelContext.KeyDerivationAbort, 1); + abortRequest->nReturnCode = ERR_USER_ABORT; + KeReleaseMutex (&MountCancelContextMutex, FALSE); + return STATUS_SUCCESS; + } + + abortRequest->nReturnCode = ERR_DRIVE_NOT_FOUND; + KeReleaseMutex (&MountCancelContextMutex, FALSE); + return STATUS_SUCCESS; +} + /* IOCTL_STORAGE_GET_DEVICE_NUMBER 0x002D1080 IOCTL_STORAGE_GET_HOTPLUG_INFO 0x002D0C14 @@ -2605,6 +2782,23 @@ NTSTATUS ProcessMainDeviceControlIrp (PDEVICE_OBJECT DeviceObject, PEXTENSION Ex } break; + case TC_IOCTL_ABORT_MOUNT_VOLUME: + if (ValidateIOBufferSize (Irp, sizeof (MOUNT_ABORT_STRUCT), ValidateInputOutput)) + { + MOUNT_ABORT_STRUCT *abortRequest = (MOUNT_ABORT_STRUCT *) Irp->AssociatedIrp.SystemBuffer; + + if (irpSp->Parameters.DeviceIoControl.InputBufferLength != sizeof (MOUNT_ABORT_STRUCT)) + { + Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; + Irp->IoStatus.Information = 0; + break; + } + + Irp->IoStatus.Status = AbortPendingMount (abortRequest); + Irp->IoStatus.Information = sizeof (MOUNT_ABORT_STRUCT); + } + break; + case TC_IOCTL_MOUNT_VOLUME: if (ValidateIOBufferSize (Irp, sizeof (MOUNT_STRUCT), ValidateInputOutput)) { @@ -3216,6 +3410,7 @@ LPWSTR TCTranslateCode (ULONG ulCode) { #define TC_CASE_RET_NAME(CODE) case CODE : return L###CODE + TC_CASE_RET_NAME (TC_IOCTL_ABORT_MOUNT_VOLUME); TC_CASE_RET_NAME (TC_IOCTL_ABORT_BOOT_ENCRYPTION_SETUP); TC_CASE_RET_NAME (TC_IOCTL_ABORT_DECOY_SYSTEM_WIPE); TC_CASE_RET_NAME (TC_IOCTL_BOOT_ENCRYPTION_SETUP); @@ -4013,6 +4208,8 @@ NTSTATUS MountDevice (PDEVICE_OBJECT DeviceObject, MOUNT_STRUCT *mount) SECURITY_SUBJECT_CONTEXT subContext; PACCESS_TOKEN accessToken; + RegisterMountCancelContext (NewExtension, mount->nDosDriveNo); + SeCaptureSubjectContext (&subContext); SeLockSubjectContext(&subContext); if (subContext.ClientToken && subContext.ImpersonationLevel >= SecurityImpersonation) @@ -4033,6 +4230,8 @@ NTSTATUS MountDevice (PDEVICE_OBJECT DeviceObject, MOUNT_STRUCT *mount) { ULONG sidLength = RtlLengthSid (tokenUser->User.Sid); + SetMountCancelContextUserSid (NewExtension, tokenUser->User.Sid); + NewExtension->UserSid = TCalloc (sidLength); if (!NewExtension->UserSid) ntStatus = STATUS_INSUFFICIENT_RESOURCES; @@ -4049,6 +4248,8 @@ NTSTATUS MountDevice (PDEVICE_OBJECT DeviceObject, MOUNT_STRUCT *mount) if (NT_SUCCESS (ntStatus)) ntStatus = TCStartVolumeThread (NewDeviceObject, NewExtension, mount); + UnregisterMountCancelContext (NewExtension); + if (!NT_SUCCESS (ntStatus)) { Dump ("Mount FAILURE NT ERROR, ntStatus = 0x%08x\n", ntStatus); diff --git a/src/Driver/Ntdriver.h b/src/Driver/Ntdriver.h index 82253146..8cbb3127 100644 --- a/src/Driver/Ntdriver.h +++ b/src/Driver/Ntdriver.h @@ -26,6 +26,22 @@ typedef struct _THREAD_BLOCK_ MOUNT_STRUCT *mount; } THREAD_BLOCK, *PTHREAD_BLOCK; +#ifndef SECURITY_MAX_SID_SIZE +#define SECURITY_MAX_SID_SIZE 68 +#endif + +typedef struct _MOUNT_CANCEL_CONTEXT_ +{ + LONG Active; + LONG UserAbortRequested; + LONG KeyDerivationAbort; + LONG UserSidValid; + LONG SequenceNumber; + int nDosDriveNo; + ULONG UserSidLength; + UCHAR UserSid[SECURITY_MAX_SID_SIZE]; +} MOUNT_CANCEL_CONTEXT, *PMOUNT_CANCEL_CONTEXT; + /* This structure is allocated for non-root devices! WARNING: bRootDevice must be the first member of the structure! */ @@ -43,6 +59,7 @@ typedef struct EXTENSION BOOL bThreadShouldQuit; /* Instruct per device worker thread to quit */ PETHREAD peThread; /* Thread handle */ KEVENT keCreateEvent; /* Device creation event */ + PMOUNT_CANCEL_CONTEXT MountCancelContext; KSPIN_LOCK ListSpinLock; /* IRP spinlock */ LIST_ENTRY ListEntry; /* IRP listentry */ KSEMAPHORE RequestSemaphore; /* IRP list request Semaphore */ diff --git a/src/Driver/Ntvol.c b/src/Driver/Ntvol.c index 19cc0788..16866a9c 100644 --- a/src/Driver/Ntvol.c +++ b/src/Driver/Ntvol.c @@ -36,6 +36,19 @@ volatile BOOL ProbingHostDeviceForWrite = FALSE; +static BOOL MountCancelRequested (PEXTENSION Extension) +{ + PMOUNT_CANCEL_CONTEXT context = Extension->MountCancelContext; + return context && InterlockedExchangeAdd (&context->UserAbortRequested, 0) != 0; +} + +static void ResetMountKeyDerivationAbort (PEXTENSION Extension) +{ + PMOUNT_CANCEL_CONTEXT context = Extension->MountCancelContext; + if (context && !MountCancelRequested (Extension)) + InterlockedExchange (&context->KeyDerivationAbort, 0); +} + NTSTATUS TCOpenVolume (PDEVICE_OBJECT DeviceObject, PEXTENSION Extension, @@ -593,11 +606,20 @@ NTSTATUS TCOpenVolume (PDEVICE_OBJECT DeviceObject, /* Attempt to recognize the volume (decrypt the header) */ + if (MountCancelRequested (Extension)) + { + mount->nReturnCode = ERR_USER_ABORT; + ntStatus = STATUS_SUCCESS; + goto error; + } + + ResetMountKeyDerivationAbort (Extension); + ReadVolumeHeaderRecoveryMode = mount->RecoveryMode; if ((volumeType == TC_VOLUME_TYPE_HIDDEN) && mount->bProtectHiddenVolume) { - mount->nReturnCode = ReadVolumeHeaderWCache ( + mount->nReturnCode = ReadVolumeHeaderWCacheWithAbort ( FALSE, bAutoCachePassword, mount->bCachePim, @@ -605,11 +627,13 @@ NTSTATUS TCOpenVolume (PDEVICE_OBJECT DeviceObject, &mount->ProtectedHidVolPassword, mount->ProtectedHidVolPkcs5Prf, mount->ProtectedHidVolPim, - &tmpCryptoInfo); + &tmpCryptoInfo, + Extension->MountCancelContext ? &Extension->MountCancelContext->KeyDerivationAbort : NULL, + Extension->MountCancelContext ? &Extension->MountCancelContext->UserAbortRequested : NULL); } else { - mount->nReturnCode = ReadVolumeHeaderWCache ( + mount->nReturnCode = ReadVolumeHeaderWCacheWithAbort ( mount->bPartitionInInactiveSysEncScope && volumeType == TC_VOLUME_TYPE_NORMAL, bAutoCachePassword, mount->bCachePim, @@ -617,7 +641,9 @@ NTSTATUS TCOpenVolume (PDEVICE_OBJECT DeviceObject, &mount->VolumePassword, mount->pkcs5_prf, mount->VolumePim, - &Extension->cryptoInfo); + &Extension->cryptoInfo, + Extension->MountCancelContext ? &Extension->MountCancelContext->KeyDerivationAbort : NULL, + Extension->MountCancelContext ? &Extension->MountCancelContext->UserAbortRequested : NULL); } ReadVolumeHeaderRecoveryMode = FALSE; diff --git a/src/Mount/Mount.c b/src/Mount/Mount.c index 964314b5..a2d22457 100644 --- a/src/Mount/Mount.c +++ b/src/Mount/Mount.c @@ -103,6 +103,7 @@ enum hidden_os_read_only_notif_mode #define TIMER_INTERVAL_KEYB_LAYOUT_GUARD 10 #define TIMER_INTERVAL_UPDATE_DEVICE_LIST 1000 #define TIMER_INTERVAL_CHECK_FOREGROUND 500 +#define TC_COMMAND_CANCEL_MOUNT L"/cancelmount" BootEncryption *BootEncObj = NULL; BootEncryptionStatus BootEncStatus; @@ -5502,7 +5503,7 @@ static BOOL Mount (HWND hwndDlg, int nDosDriveNo, wchar_t *szVolFileName, int pi NormalCursor (); - if (mounted) + if (mounted > 0) { // Check for problematic file extensions (exe, dll, sys) @@ -9558,6 +9559,7 @@ void ExtractCommandLine (HWND hwndDlg, wchar_t *lpszCommandLine) OptionEnableMemoryProtection, OptionEnableScreenProtection, OptionSignalExit, + CommandCancelMount, CommandUnmount, }; @@ -9591,6 +9593,8 @@ void ExtractCommandLine (HWND hwndDlg, wchar_t *lpszCommandLine) { OptionEnableMemoryProtection, L"/protectMemory", NULL, FALSE }, { OptionEnableScreenProtection, L"/protectScreen", NULL, FALSE }, { OptionSignalExit, L"/signalExit", NULL, FALSE }, + // Add /cancelmount to the command table so it appears in /help. + { CommandCancelMount, TC_COMMAND_CANCEL_MOUNT, NULL, FALSE }, { CommandUnmount, L"/unmount", L"/u", FALSE }, }; @@ -10588,6 +10592,12 @@ int WINAPI wWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *lpsz for (int i = 0; argv && i < argc; i++) { + if (_wcsicmp (argv[i], TC_COMMAND_CANCEL_MOUNT) == 0) + { + BOOL abortSent = AbortMountOperation (-1); + LocalFree (argv); // free memory allocated by CommandLineToArgvW + return abortSent ? 0 : 1; + } if (_wcsicmp (argv[i], L"/protectScreen") == 0) { if ((i < argc - 1) && _wcsicmp (argv[i + 1], L"no") == 0)