mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2026-10-18 12:25:56 +00:00
Release header allocations after early setup failures
Free the key-info buffer and owned crypto context on pre-KDF errors, preserving borrowed contexts and avoiding uninitialized drain resources. Inject every setup allocation and event failure to verify cleanup.
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Inject failures into the production Windows header setup/cleanup paths.
|
||||
|
||||
Extracts those paths verbatim; replaces only the intervening cryptographic search
|
||||
with a chosen success/failure result. Tracks allocation, event and lock ownership.
|
||||
Run in an x64 MSVC environment: python Tests/test_windows_header_cleanup.py
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from test_windows_kdf_completion import ROOT, block
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--build-dir", type=Path)
|
||||
args = parser.parse_args()
|
||||
if os.name != "nt" or not shutil.which("cl.exe"):
|
||||
parser.error("An x64 MSVC developer environment on Windows is required")
|
||||
build = (args.build_dir or Path(tempfile.mkdtemp(prefix="vc-header-cleanup-"))).resolve()
|
||||
build.mkdir(parents=True, exist_ok=True)
|
||||
source = (ROOT / "src/Common/Volumes.c").read_text()
|
||||
start = source.index("int ReadVolumeHeaderWithAbort (")
|
||||
header = block(source, start)
|
||||
setup = header[:header.index("\tcrypto_loadkey (keyInfo,")]
|
||||
cleanup = header[header.index("\nerr:"):]
|
||||
signature = source[start:source.index("{", start)]
|
||||
(build / "header_cleanup.inc").write_text(signature + setup + """
|
||||
if (TestHeaderError) { status = ERR_PASSWORD_WRONG; goto err; }
|
||||
status = ERR_SUCCESS;
|
||||
goto ret;
|
||||
""" + cleanup)
|
||||
item_end = source.index("} KeyDerivationWorkItem;") + len("} KeyDerivationWorkItem;")
|
||||
item_start = source.rindex("typedef struct", 0, item_end)
|
||||
(build / "kdf_item.inc").write_text(source[item_start:item_end])
|
||||
exe = build / "windows_header_cleanup_test.exe"
|
||||
command = ["cl.exe", "/nologo", "/O2", "/W3", "/WX", "/MD", "/wd4101",
|
||||
"/DUNICODE", "/D_UNICODE", f"/I{build}"]
|
||||
command += [f"/I{ROOT / 'src' / directory}"
|
||||
for directory in ("", "Common", "Crypto", "Crypto/Argon2/include")]
|
||||
command += [str(ROOT / "Tests/windows_header_cleanup_test.c"), f"/Fe:{exe}"]
|
||||
result = subprocess.run(command, cwd=build, capture_output=True, text=True)
|
||||
(build / "build.log").write_text(result.stdout + result.stderr)
|
||||
if result.returncode:
|
||||
raise RuntimeError(result.stdout + result.stderr)
|
||||
subprocess.run([str(exe)], cwd=build, check=True, timeout=30)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,165 @@
|
||||
/* Copyright (c) 2026 AM Crypto. Licensed under the Apache License 2.0. */
|
||||
#include "Common/EncryptionThreadPool.h"
|
||||
#include "Common/Password.h"
|
||||
#include "Common/Volumes.h"
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct { void *Pointer; size_t Size; BOOL Open; } Allocation;
|
||||
typedef struct { void *Pointer; size_t Size; BOOL Locked; } LockedRange;
|
||||
static Allocation Allocations[32];
|
||||
static LockedRange Locks[32];
|
||||
static HANDLE Events[8];
|
||||
static int AllocationCount, LockCount, EventCount, Attempts, FailAt, TestHeaderError, Checks;
|
||||
static size_t PoolThreads;
|
||||
static CRYPTO_INFO BorrowedCrypto;
|
||||
|
||||
static void Require (BOOL condition, const char *message)
|
||||
{
|
||||
if (!condition) { fprintf (stderr, "FAIL: %s (failure point %d)\n", message, FailAt); ExitProcess (1); }
|
||||
}
|
||||
|
||||
static BOOL Fail (void) { return ++Attempts == FailAt; }
|
||||
|
||||
static void *TestAlloc (size_t size)
|
||||
{
|
||||
void *memory;
|
||||
Allocation *allocation;
|
||||
if (Fail()) return NULL;
|
||||
Require (AllocationCount < 32, "allocation fixture capacity");
|
||||
memory = malloc (size);
|
||||
Require (memory != NULL, "fixture allocation");
|
||||
memset (memory, 0xa6, size);
|
||||
allocation = &Allocations[AllocationCount++];
|
||||
allocation->Pointer = memory;
|
||||
allocation->Size = size;
|
||||
allocation->Open = TRUE;
|
||||
return memory;
|
||||
}
|
||||
|
||||
static void TestFree (void *memory)
|
||||
{
|
||||
int i;
|
||||
for (i = AllocationCount - 1; i >= 0; --i)
|
||||
if (Allocations[i].Open && Allocations[i].Pointer == memory)
|
||||
{
|
||||
Allocations[i].Open = FALSE;
|
||||
free (memory);
|
||||
return;
|
||||
}
|
||||
Require (FALSE, "unknown allocation or double free");
|
||||
}
|
||||
|
||||
static BOOL TestLock (void *memory, size_t size)
|
||||
{
|
||||
LockedRange *range;
|
||||
Require (LockCount < 32, "lock fixture capacity");
|
||||
range = &Locks[LockCount++];
|
||||
range->Pointer = memory;
|
||||
range->Size = size;
|
||||
range->Locked = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL TestUnlock (void *memory, size_t size)
|
||||
{
|
||||
int i;
|
||||
for (i = LockCount - 1; i >= 0; --i)
|
||||
if (Locks[i].Locked && Locks[i].Pointer == memory && Locks[i].Size == size)
|
||||
{
|
||||
Locks[i].Locked = FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
Require (FALSE, "unlock of an uninitialized range");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static HANDLE TestCreateEvent (void *attributes, BOOL manual, BOOL initial, void *name)
|
||||
{
|
||||
HANDLE event;
|
||||
if (Fail()) return NULL;
|
||||
Require (EventCount < 8, "event fixture capacity");
|
||||
event = CreateEventW (attributes, manual, initial, name);
|
||||
Require (event != NULL, "fixture event creation");
|
||||
Events[EventCount++] = event;
|
||||
return event;
|
||||
}
|
||||
|
||||
static BOOL TestClose (HANDLE event)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < EventCount; ++i)
|
||||
if (Events[i] == event && event != NULL)
|
||||
{
|
||||
Events[i] = NULL;
|
||||
return CloseHandle (event);
|
||||
}
|
||||
Require (FALSE, "close of an uninitialized event");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static PCRYPTO_INFO TestCryptoOpen (void) { return TestAlloc (sizeof (CRYPTO_INFO)); }
|
||||
static void TestCryptoClose (PCRYPTO_INFO cryptoInfo)
|
||||
{
|
||||
Require (cryptoInfo != &BorrowedCrypto, "closed a borrowed crypto context");
|
||||
TestFree (cryptoInfo);
|
||||
}
|
||||
static size_t TestThreads (void) { return PoolThreads; }
|
||||
static void TestWait (HANDLE event) { (void) event; Require (FALSE, "wait without submitted workers"); }
|
||||
|
||||
#undef TCalloc
|
||||
#undef TCfree
|
||||
#undef TC_WAIT_EVENT
|
||||
#undef burn
|
||||
#define TCalloc TestAlloc
|
||||
#define TCfree TestFree
|
||||
#define TC_WAIT_EVENT(event) TestWait (event)
|
||||
#define burn(memory,size) memset (memory, 0, size)
|
||||
#define crypto_open TestCryptoOpen
|
||||
#define crypto_close TestCryptoClose
|
||||
#define VirtualLock TestLock
|
||||
#define VirtualUnlock TestUnlock
|
||||
#define CreateEventW TestCreateEvent
|
||||
#define CloseHandle TestClose
|
||||
#define GetEncryptionThreadCount TestThreads
|
||||
#include "kdf_item.inc"
|
||||
#include "header_cleanup.inc"
|
||||
|
||||
static int Run (int failure, BOOL borrowed, BOOL missingOutput, int prf, size_t threads, BOOL headerError)
|
||||
{
|
||||
PCRYPTO_INFO result = NULL;
|
||||
int status, i;
|
||||
AllocationCount = LockCount = EventCount = Attempts = 0;
|
||||
FailAt = failure;
|
||||
PoolThreads = threads;
|
||||
TestHeaderError = headerError;
|
||||
status = ReadVolumeHeaderWithAbort (FALSE, NULL, NULL, prf, 0,
|
||||
missingOutput ? NULL : &result, borrowed ? &BorrowedCrypto : NULL, NULL, NULL);
|
||||
if (failure) Require (status == ERR_OUTOFMEMORY, "allocation failure returned the wrong status");
|
||||
else if (missingOutput && !borrowed) Require (status == ERR_PARAMETER_INCORRECT, "missing output accepted");
|
||||
else Require (status == (headerError ? ERR_PASSWORD_WRONG : ERR_SUCCESS), "header status changed");
|
||||
if (status != ERR_SUCCESS) Require (result == NULL, "failed header retained owned crypto context");
|
||||
if (result) TestCryptoClose (result);
|
||||
for (i = 0; i < AllocationCount; ++i) Require (!Allocations[i].Open, "allocation leaked");
|
||||
for (i = 0; i < LockCount; ++i) Require (!Locks[i].Locked, "locked range leaked");
|
||||
for (i = 0; i < EventCount; ++i) Require (!Events[i], "event leaked");
|
||||
++Checks;
|
||||
return Attempts;
|
||||
}
|
||||
|
||||
int main (void)
|
||||
{
|
||||
int borrowed, setup, error;
|
||||
for (borrowed = 0; borrowed <= 1; ++borrowed)
|
||||
for (setup = 0; setup < 3; ++setup)
|
||||
for (error = 0; error <= 1; ++error)
|
||||
{
|
||||
int prf = setup == 1 ? 1 : 0;
|
||||
size_t threads = setup == 2 ? 1 : 2;
|
||||
int attempts = Run (0, borrowed, borrowed, prf, threads, error), failure;
|
||||
for (failure = 1; failure <= attempts; ++failure)
|
||||
Run (failure, borrowed, borrowed, prf, threads, error);
|
||||
}
|
||||
Run (0, FALSE, TRUE, 0, 2, FALSE);
|
||||
printf ("PASS: %d header setup/cleanup scenarios, including every allocation/event failure and borrowed contexts\n", Checks);
|
||||
return 0;
|
||||
}
|
||||
@@ -221,13 +221,15 @@ static int InitializeRequest (Request *request)
|
||||
TC_EVENT *keyDerivationCompletedEvent = NULL, *noOutstandingWorkItemEvent = NULL;
|
||||
LONG *outstandingWorkItemCount = NULL;
|
||||
KeyDerivationWorkItem *keyDerivationWorkItems = NULL;
|
||||
int keyDerivationWorkItemsSize = 0, pkcs5PrfCount = PRF_COUNT, i;
|
||||
int keyDerivationWorkItemsSize = 0, pkcs5PrfCount = PRF_COUNT, i, status;
|
||||
#include "kdf_init.inc"
|
||||
request->Ready = keyDerivationCompletedEvent;
|
||||
request->Drained = noOutstandingWorkItemEvent;
|
||||
request->References = outstandingWorkItemCount;
|
||||
request->Items = keyDerivationWorkItems;
|
||||
return 0;
|
||||
err_early:
|
||||
return status;
|
||||
}
|
||||
|
||||
static Request *NewRequest (void)
|
||||
|
||||
+31
-10
@@ -193,7 +193,7 @@ int ReadVolumeHeaderWithAbort (BOOL bBoot, unsigned char *encryptedHeader, Passw
|
||||
int keyInfoBufferSize = sizeof (KEY_INFO) + TC_KEY_INFO_BUFFER_ALIGNMENT;
|
||||
size_t keyInfoBufferOffset;
|
||||
PKEY_INFO keyInfo;
|
||||
PCRYPTO_INFO cryptoInfo;
|
||||
PCRYPTO_INFO cryptoInfo = NULL;
|
||||
CRYPTOPP_ALIGN_DATA(TC_DERIVED_KEY_BUFFER_ALIGNMENT) unsigned char dk[MASTER_KEYDATA_SIZE];
|
||||
int enqPkcs5Prf, pkcs5_prf;
|
||||
uint16 headerVersion;
|
||||
@@ -240,12 +240,15 @@ int ReadVolumeHeaderWithAbort (BOOL bBoot, unsigned char *encryptedHeader, Passw
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!retInfo)
|
||||
return ERR_PARAMETER_INCORRECT;
|
||||
if (!retInfo)
|
||||
goto err_early;
|
||||
|
||||
cryptoInfo = *retInfo = crypto_open ();
|
||||
if (cryptoInfo == NULL)
|
||||
return ERR_OUTOFMEMORY;
|
||||
{
|
||||
status = ERR_OUTOFMEMORY;
|
||||
goto err_early;
|
||||
}
|
||||
}
|
||||
#if !defined(_UEFI)
|
||||
/* use thread pool only if no PRF was specified */
|
||||
@@ -253,13 +256,17 @@ int ReadVolumeHeaderWithAbort (BOOL bBoot, unsigned char *encryptedHeader, Passw
|
||||
{
|
||||
keyDerivationCompletedEvent = TCalloc (sizeof (TC_EVENT));
|
||||
if (!keyDerivationCompletedEvent)
|
||||
return ERR_OUTOFMEMORY;
|
||||
{
|
||||
status = ERR_OUTOFMEMORY;
|
||||
goto err_early;
|
||||
}
|
||||
|
||||
noOutstandingWorkItemEvent = TCalloc (sizeof (TC_EVENT));
|
||||
if (!noOutstandingWorkItemEvent)
|
||||
{
|
||||
TCfree(keyDerivationCompletedEvent);
|
||||
return ERR_OUTOFMEMORY;
|
||||
status = ERR_OUTOFMEMORY;
|
||||
goto err_early;
|
||||
}
|
||||
|
||||
outstandingWorkItemCount = TCalloc (sizeof (LONG));
|
||||
@@ -267,7 +274,8 @@ int ReadVolumeHeaderWithAbort (BOOL bBoot, unsigned char *encryptedHeader, Passw
|
||||
{
|
||||
TCfree(keyDerivationCompletedEvent);
|
||||
TCfree(noOutstandingWorkItemEvent);
|
||||
return ERR_OUTOFMEMORY;
|
||||
status = ERR_OUTOFMEMORY;
|
||||
goto err_early;
|
||||
}
|
||||
|
||||
keyDerivationWorkItemsSize = sizeof (KeyDerivationWorkItem) * pkcs5PrfCount;
|
||||
@@ -277,7 +285,8 @@ int ReadVolumeHeaderWithAbort (BOOL bBoot, unsigned char *encryptedHeader, Passw
|
||||
TCfree(keyDerivationCompletedEvent);
|
||||
TCfree(noOutstandingWorkItemEvent);
|
||||
TCfree(outstandingWorkItemCount);
|
||||
return ERR_OUTOFMEMORY;
|
||||
status = ERR_OUTOFMEMORY;
|
||||
goto err_early;
|
||||
}
|
||||
|
||||
for (i = 0; i < pkcs5PrfCount; ++i)
|
||||
@@ -296,7 +305,8 @@ int ReadVolumeHeaderWithAbort (BOOL bBoot, unsigned char *encryptedHeader, Passw
|
||||
TCfree(keyDerivationCompletedEvent);
|
||||
TCfree(noOutstandingWorkItemEvent);
|
||||
TCfree(outstandingWorkItemCount);
|
||||
return ERR_OUTOFMEMORY;
|
||||
status = ERR_OUTOFMEMORY;
|
||||
goto err_early;
|
||||
}
|
||||
|
||||
*noOutstandingWorkItemEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
||||
@@ -307,7 +317,8 @@ int ReadVolumeHeaderWithAbort (BOOL bBoot, unsigned char *encryptedHeader, Passw
|
||||
TCfree(keyDerivationCompletedEvent);
|
||||
TCfree(noOutstandingWorkItemEvent);
|
||||
TCfree(outstandingWorkItemCount);
|
||||
return ERR_OUTOFMEMORY;
|
||||
status = ERR_OUTOFMEMORY;
|
||||
goto err_early;
|
||||
}
|
||||
|
||||
VirtualLock (keyDerivationWorkItems, keyDerivationWorkItemsSize);
|
||||
@@ -759,12 +770,22 @@ ret:
|
||||
}
|
||||
#endif
|
||||
|
||||
ret_key_info:
|
||||
burn (keyInfo, sizeof (KEY_INFO));
|
||||
#if !defined(DEVICE_DRIVER) && !defined(_UEFI)
|
||||
VirtualUnlock (keyInfoBuffer, keyInfoBufferSize);
|
||||
#endif
|
||||
TCfree(keyInfoBuffer);
|
||||
return status;
|
||||
|
||||
err_early:
|
||||
// Partial KDF setup has already been freed; no worker was submitted.
|
||||
if (cryptoInfo && cryptoInfo != retHeaderCryptoInfo)
|
||||
{
|
||||
crypto_close (cryptoInfo);
|
||||
*retInfo = NULL;
|
||||
}
|
||||
goto ret_key_info;
|
||||
}
|
||||
|
||||
int ReadVolumeHeader (BOOL bBoot, unsigned char *encryptedHeader, Password *password, int selected_pkcs5_prf, int pim, PCRYPTO_INFO *retInfo, CRYPTO_INFO *retHeaderCryptoInfo)
|
||||
|
||||
Reference in New Issue
Block a user