mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2026-10-22 06:12:35 +00:00
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.
615 lines
22 KiB
C
615 lines
22 KiB
C
/* Copyright (c) 2026 AM Crypto. Licensed under the Apache License 2.0.
|
|
* Bounded user-mode checks of the production Windows KDF reference protocol.
|
|
* Build through test_windows_kdf_completion.py, which supplies caller excerpts.
|
|
*/
|
|
#include "Common/EncryptionThreadPool.h"
|
|
#include "Common/Pkcs5.h"
|
|
#include <process.h>
|
|
#include <stdio.h>
|
|
|
|
static BOOL TestSetEvent (HANDLE event);
|
|
static DWORD TestWait (HANDLE event);
|
|
static HANDLE WINAPI TestCreateEvent (LPSECURITY_ATTRIBUTES attributes, BOOL manualReset, BOOL initialState, LPCWSTR name);
|
|
static HANDLE WINAPI TestCreateMutex (LPSECURITY_ATTRIBUTES attributes, BOOL initialOwner, LPCWSTR name);
|
|
static uintptr_t TestBeginThread (void *security, unsigned stackSize, unsigned (__stdcall *proc) (void *), void *arg, unsigned flags, unsigned *id);
|
|
static BOOL WINAPI TestCloseHandle (HANDLE handle);
|
|
#undef TC_WAIT_EVENT
|
|
#define TC_WAIT_EVENT(event) TestWait (event)
|
|
#define SetEvent TestSetEvent
|
|
#define CreateEventW TestCreateEvent
|
|
#define CreateMutexW TestCreateMutex
|
|
#define _beginthreadex TestBeginThread
|
|
#define CloseHandle TestCloseHandle
|
|
#include "Common/EncryptionThreadPool.c"
|
|
#undef CloseHandle
|
|
#undef _beginthreadex
|
|
#undef CreateMutexW
|
|
#undef CreateEventW
|
|
#undef SetEvent
|
|
#include "kdf_item.inc"
|
|
|
|
#define MAX_REQUESTS 16
|
|
#define PRF_COUNT (LAST_PRF_ID - FIRST_PRF_ID + 1)
|
|
typedef struct Request Request;
|
|
typedef struct
|
|
{
|
|
Request *Owner;
|
|
KeyDerivationWorkItem *Item;
|
|
HANDLE Started, MayReturn, ReadyPublished, MayReleaseReference, WorkerDone;
|
|
int Result;
|
|
BOOL HoldAfterReady;
|
|
} Job;
|
|
|
|
struct Request
|
|
{
|
|
TC_EVENT *Ready, *Drained;
|
|
LONG *References;
|
|
KeyDerivationWorkItem *Items;
|
|
LONG volatile Abort;
|
|
LONG volatile Signals;
|
|
HANDLE WaitEntered, FinishDone, SignalEntered, MaySignal;
|
|
HANDLE RequesterThread;
|
|
BOOL HoldFinalSignal;
|
|
int JobCount;
|
|
Job Jobs[PRF_COUNT];
|
|
};
|
|
|
|
static Request Requests[MAX_REQUESTS];
|
|
static int RequestCount;
|
|
static __declspec(thread) Job *CurrentJob;
|
|
static __declspec(thread) Request *DrainingRequest;
|
|
typedef struct
|
|
{
|
|
unsigned char Data[6 * ENCRYPTION_DATA_UNIT_SIZE];
|
|
LONG volatile Started, Processed;
|
|
HANDLE BothStarted, MayProcess, Waiting, Paused, MayReturn, Done, Thread;
|
|
BOOL HoldWorkers, HoldRequester;
|
|
uint32 Units;
|
|
EncryptionThreadPoolWorkType Type;
|
|
} CipherJob;
|
|
static __declspec(thread) CipherJob *CurrentCipherJob;
|
|
static HANDLE StopWaiting, StopDone;
|
|
|
|
typedef struct
|
|
{
|
|
HANDLE Handle;
|
|
BOOL Thread, Open;
|
|
} PoolResource;
|
|
static PoolResource Resources[1024];
|
|
static int ResourceCount, OpenResources, CreateAttempts, FailCreateAt;
|
|
|
|
static void Require (BOOL condition, const char *message)
|
|
{
|
|
if (!condition)
|
|
{
|
|
fprintf (stderr, "FAIL: %s\n", message);
|
|
ExitProcess (1);
|
|
}
|
|
}
|
|
|
|
static BOOL FailCreation (void)
|
|
{
|
|
if (++CreateAttempts == FailCreateAt)
|
|
{
|
|
SetLastError (ERROR_NOT_ENOUGH_MEMORY);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static HANDLE TrackResource (HANDLE handle, BOOL thread)
|
|
{
|
|
if (handle)
|
|
{
|
|
PoolResource *resource;
|
|
Require (ResourceCount < sizeof (Resources) / sizeof (Resources[0]), "resource fixture capacity");
|
|
resource = &Resources[ResourceCount++];
|
|
resource->Handle = handle;
|
|
resource->Thread = thread;
|
|
resource->Open = TRUE;
|
|
++OpenResources;
|
|
}
|
|
return handle;
|
|
}
|
|
|
|
static HANDLE WINAPI TestCreateEvent (LPSECURITY_ATTRIBUTES attributes, BOOL manualReset, BOOL initialState, LPCWSTR name)
|
|
{
|
|
return FailCreation () ? NULL : TrackResource (CreateEventW (attributes, manualReset, initialState, name), FALSE);
|
|
}
|
|
|
|
static HANDLE WINAPI TestCreateMutex (LPSECURITY_ATTRIBUTES attributes, BOOL initialOwner, LPCWSTR name)
|
|
{
|
|
return FailCreation () ? NULL : TrackResource (CreateMutexW (attributes, initialOwner, name), FALSE);
|
|
}
|
|
|
|
static uintptr_t TestBeginThread (void *security, unsigned stackSize, unsigned (__stdcall *proc) (void *), void *arg, unsigned flags, unsigned *id)
|
|
{
|
|
return FailCreation () ? 0 : (uintptr_t) TrackResource ((HANDLE) _beginthreadex (security, stackSize, proc, arg, flags, id), TRUE);
|
|
}
|
|
|
|
static BOOL WINAPI TestCloseHandle (HANDLE handle)
|
|
{
|
|
int i;
|
|
for (i = ResourceCount - 1; i >= 0; --i)
|
|
{
|
|
PoolResource *resource = &Resources[i];
|
|
if (resource->Open && resource->Handle == handle)
|
|
{
|
|
if (resource->Thread)
|
|
Require (WaitForSingleObject (handle, 0) == WAIT_OBJECT_0, "worker handle closed before join");
|
|
resource->Open = FALSE;
|
|
--OpenResources;
|
|
Require (CloseHandle (handle), "close pool resource");
|
|
return TRUE;
|
|
}
|
|
}
|
|
Require (FALSE, "closing unknown pool resource");
|
|
return FALSE;
|
|
}
|
|
|
|
static HANDLE Event (void)
|
|
{
|
|
HANDLE event = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
Require (event != NULL, "create test event");
|
|
return event;
|
|
}
|
|
|
|
static void Wait (HANDLE event)
|
|
{
|
|
Require (WaitForSingleObject (event, 10000) == WAIT_OBJECT_0, "test barrier timed out");
|
|
}
|
|
|
|
static LONG References (Request *request)
|
|
{
|
|
return InterlockedCompareExchange (request->References, 0, 0);
|
|
}
|
|
|
|
static DWORD TestWait (HANDLE event)
|
|
{
|
|
DWORD result;
|
|
if (DrainingRequest && event == *DrainingRequest->Drained)
|
|
SetEvent (DrainingRequest->WaitEntered);
|
|
if (StopWaiting && event == ThreadPoolDrainedEvent)
|
|
SetEvent (StopWaiting);
|
|
if (CurrentCipherJob && event != WorkItemCompletedEvent)
|
|
SetEvent (CurrentCipherJob->Waiting);
|
|
result = WaitForSingleObject (event, INFINITE);
|
|
if (CurrentCipherJob && CurrentCipherJob->HoldRequester && event != WorkItemCompletedEvent)
|
|
{
|
|
SetEvent (CurrentCipherJob->Paused);
|
|
Wait (CurrentCipherJob->MayReturn);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static BOOL TestSetEvent (HANDLE event)
|
|
{
|
|
Job *job = CurrentJob;
|
|
if (job)
|
|
{
|
|
Request *request = job->Owner;
|
|
if (event == *request->Drained)
|
|
{
|
|
Require (References (request) == 0, "completion signaled with outstanding references");
|
|
Require (InterlockedIncrement (&request->Signals) == 1, "completion signaled twice");
|
|
if (request->HoldFinalSignal)
|
|
{
|
|
SetEvent (request->SignalEntered);
|
|
Wait (request->MaySignal);
|
|
}
|
|
}
|
|
if (event == *request->Ready && job->HoldAfterReady)
|
|
{
|
|
BOOL result = SetEvent (event);
|
|
SetEvent (job->ReadyPublished);
|
|
Wait (job->MayReleaseReference);
|
|
return result;
|
|
}
|
|
if (event == WorkItemCompletedEvent)
|
|
{
|
|
BOOL result = SetEvent (event);
|
|
CurrentJob = NULL;
|
|
SetEvent (job->WorkerDone);
|
|
return result;
|
|
}
|
|
}
|
|
return SetEvent (event);
|
|
}
|
|
|
|
static int InitializeRequest (Request *request)
|
|
{
|
|
TC_EVENT *keyDerivationCompletedEvent = NULL, *noOutstandingWorkItemEvent = NULL;
|
|
LONG *outstandingWorkItemCount = NULL;
|
|
KeyDerivationWorkItem *keyDerivationWorkItems = NULL;
|
|
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)
|
|
{
|
|
Request *request;
|
|
Require (RequestCount < MAX_REQUESTS, "request fixture capacity");
|
|
request = &Requests[RequestCount++];
|
|
Require (InitializeRequest (request) == 0, "initialize request");
|
|
/* Refuse to exercise an implementation without the caller reference. */
|
|
Require (References (request) == 1, "missing requester reference");
|
|
Require (WaitForSingleObject (*request->Drained, 0) == WAIT_TIMEOUT, "initial completion event signaled");
|
|
request->WaitEntered = Event ();
|
|
request->FinishDone = Event ();
|
|
request->SignalEntered = Event ();
|
|
request->MaySignal = Event ();
|
|
return request;
|
|
}
|
|
|
|
static unsigned __stdcall FinishRequest (void *context)
|
|
{
|
|
Request *request = (Request *) context;
|
|
LONG *outstandingWorkItemCount = request->References;
|
|
TC_EVENT *noOutstandingWorkItemEvent = request->Drained;
|
|
DrainingRequest = request;
|
|
#include "kdf_drain.inc"
|
|
Require (References (request) == 0, "request finished with workers outstanding");
|
|
DrainingRequest = NULL;
|
|
SetEvent (request->FinishDone);
|
|
return 0;
|
|
}
|
|
|
|
static void BeginFinish (Request *request)
|
|
{
|
|
request->RequesterThread = (HANDLE) _beginthreadex (NULL, 0, FinishRequest, request, 0, NULL);
|
|
Require (request->RequesterThread != NULL, "start requester thread");
|
|
Wait (request->WaitEntered);
|
|
Require (WaitForSingleObject (request->FinishDone, 0) == WAIT_TIMEOUT, "request did not wait for workers");
|
|
}
|
|
|
|
static void EndFinish (Request *request)
|
|
{
|
|
Wait (request->FinishDone);
|
|
Wait (request->RequesterThread);
|
|
Require (References (request) == 0 && request->Signals == 1, "last worker completion");
|
|
}
|
|
|
|
static Job *NewJob (Request *request, int prf, BOOL holdAfterReady, int result)
|
|
{
|
|
Job *job;
|
|
Require (request->JobCount < PRF_COUNT, "job fixture capacity");
|
|
job = &request->Jobs[request->JobCount];
|
|
job->Owner = request;
|
|
job->Item = &request->Items[request->JobCount++];
|
|
job->Item->Free = FALSE;
|
|
job->Item->KeyReady = FALSE;
|
|
job->Item->DerivationResult = 0;
|
|
job->Item->Pkcs5Prf = prf;
|
|
job->Result = result;
|
|
job->HoldAfterReady = holdAfterReady;
|
|
job->Started = Event (); job->MayReturn = Event (); job->ReadyPublished = Event ();
|
|
job->MayReleaseReference = Event (); job->WorkerDone = Event ();
|
|
return job;
|
|
}
|
|
|
|
static void SubmitJob (Job *job)
|
|
{
|
|
Request *request = job->Owner;
|
|
EncryptionThreadPoolBeginKeyDerivation (request->Ready, request->Drained, &job->Item->KeyReady,
|
|
request->References, job->Item->Pkcs5Prf, (unsigned char *) job, sizeof (*job), NULL, 1, 8,
|
|
job->Item->DerivedKey, &job->Item->DerivationResult, &request->Abort);
|
|
}
|
|
|
|
static Job *Submit (Request *request, int prf, BOOL holdAfterReady, int result)
|
|
{
|
|
Job *job = NewJob (request, prf, holdAfterReady, result);
|
|
SubmitJob (job);
|
|
return job;
|
|
}
|
|
|
|
static void JobDone (Job *job)
|
|
{
|
|
Wait (job->WorkerDone);
|
|
Require (job->Item->KeyReady == TRUE && job->Item->DerivationResult == job->Result,
|
|
"result was not published before completion");
|
|
}
|
|
|
|
static void FakeKdf (const unsigned char *password, unsigned char *output, int length)
|
|
{
|
|
Job *job = (Job *) password;
|
|
CurrentJob = job;
|
|
SetEvent (job->Started);
|
|
Wait (job->MayReturn);
|
|
memset (output, job->Owner->Abort || job->Result ? 0 : 0x5a, length);
|
|
}
|
|
|
|
#define FAKE_KDF(name) \
|
|
void name (const unsigned char *pwd, int pwd_len, const unsigned char *salt, int salt_len, \
|
|
uint32 iterations, unsigned char *dk, int dklen, long volatile *abortFlag) \
|
|
{ FakeKdf (pwd, dk, dklen); }
|
|
FAKE_KDF (derive_key_blake2s)
|
|
FAKE_KDF (derive_key_sha512)
|
|
FAKE_KDF (derive_key_whirlpool)
|
|
FAKE_KDF (derive_key_sha256)
|
|
FAKE_KDF (derive_key_streebog)
|
|
int derive_key_argon2 (const unsigned char *pwd, int pwd_len, const unsigned char *salt, int salt_len,
|
|
uint32 iterations, uint32 memory, unsigned char *dk, int dklen, long volatile *abortFlag)
|
|
{ FakeKdf (pwd, dk, dklen); return ((Job *) pwd)->Result; }
|
|
|
|
int GetMaxPkcs5OutSize (void) { return MASTER_KEYDATA_SIZE; }
|
|
BOOL IsRamEncryptionEnabled (void) { return FALSE; }
|
|
uint64 VcGetEncryptionID (PCRYPTO_INFO ci) { return 0; }
|
|
void VcUnprotectKeys (PCRYPTO_INFO ci, uint64 id) { }
|
|
static void FakeCrypt (unsigned char *data, TC_LARGEST_COMPILER_UINT count, PCRYPTO_INFO ci, int value)
|
|
{
|
|
CipherJob *job = (CipherJob *) ci;
|
|
if (job->HoldWorkers)
|
|
{
|
|
if (InterlockedIncrement (&job->Started) == 2) SetEvent (job->BothStarted);
|
|
Wait (job->MayProcess);
|
|
}
|
|
memset (data, value, (size_t) count * ENCRYPTION_DATA_UNIT_SIZE);
|
|
InterlockedExchangeAdd (&job->Processed, (LONG) count);
|
|
}
|
|
void EncryptDataUnitsCurrentThread (unsigned __int8 *data, const UINT64_STRUCT *start, TC_LARGEST_COMPILER_UINT count, PCRYPTO_INFO ci)
|
|
{ FakeCrypt (data, count, ci, 0xa5); }
|
|
void DecryptDataUnitsCurrentThread (unsigned __int8 *data, const UINT64_STRUCT *start, TC_LARGEST_COMPILER_UINT count, PCRYPTO_INFO ci)
|
|
{ FakeCrypt (data, count, ci, 0x5a); }
|
|
|
|
static void RequesterLast (int count)
|
|
{
|
|
Request *request = NewRequest ();
|
|
int i;
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
Job *job = Submit (request, FIRST_PRF_ID + i, FALSE, 0);
|
|
SetEvent (job->MayReturn);
|
|
JobDone (job);
|
|
Require (References (request) == 1 && request->Signals == 0, "idle submission phase completed");
|
|
Require (WaitForSingleObject (*request->Drained, 0) == WAIT_TIMEOUT, "stale idle notification");
|
|
}
|
|
FinishRequest (request);
|
|
Wait (request->FinishDone);
|
|
Require (request->Signals == 0, "requester-last path unnecessarily signaled");
|
|
}
|
|
|
|
static void WorkerLast (int count, BOOL cancel, int argonResult)
|
|
{
|
|
Request *request = NewRequest ();
|
|
int i;
|
|
for (i = 0; i < count; ++i)
|
|
Submit (request, i == count - 1 ? ARGON2 : SHA512, FALSE, i == count - 1 ? argonResult : 0);
|
|
BeginFinish (request);
|
|
InterlockedExchange (&request->Abort, cancel);
|
|
for (i = 0; i < count; ++i)
|
|
SetEvent (request->Jobs[i].MayReturn);
|
|
EndFinish (request);
|
|
for (i = 0; i < count; ++i)
|
|
JobDone (&request->Jobs[i]);
|
|
}
|
|
|
|
static void PublicationBoundaries (void)
|
|
{
|
|
Request *request = NewRequest ();
|
|
Job *job = Submit (request, SHA256, TRUE, 0);
|
|
SetEvent (job->MayReturn);
|
|
Wait (job->ReadyPublished);
|
|
BeginFinish (request);
|
|
Require (References (request) == 1, "ready result released reference too early");
|
|
SetEvent (job->MayReleaseReference);
|
|
EndFinish (request);
|
|
JobDone (job);
|
|
|
|
request = NewRequest ();
|
|
request->HoldFinalSignal = TRUE;
|
|
job = Submit (request, SHA512, FALSE, 0);
|
|
BeginFinish (request);
|
|
SetEvent (job->MayReturn);
|
|
Wait (request->SignalEntered);
|
|
Require (References (request) == 0, "final notification boundary");
|
|
Require (WaitForSingleObject (request->FinishDone, 0) == WAIT_TIMEOUT, "zero count bypassed final notification");
|
|
SetEvent (request->MaySignal);
|
|
EndFinish (request);
|
|
JobDone (job);
|
|
}
|
|
|
|
static void ResetResources (int failurePoint)
|
|
{
|
|
Require (OpenResources == 0, "pool resources leaked");
|
|
ResourceCount = CreateAttempts = 0;
|
|
FailCreateAt = failurePoint;
|
|
}
|
|
|
|
static void StartupFailures (size_t available)
|
|
{
|
|
int i, creationCount;
|
|
Require (EncryptionThreadPoolStart (available - 2), "start baseline pool");
|
|
creationCount = CreateAttempts;
|
|
EncryptionThreadPoolStop ();
|
|
ResetResources (0);
|
|
for (i = 1; i <= creationCount; ++i)
|
|
{
|
|
ResetResources (i);
|
|
Require (!EncryptionThreadPoolStart (available - 2), "injected startup failure accepted");
|
|
Require (!IsEncryptionThreadPoolRunning () && GetEncryptionThreadCount () == 0, "failed pool published");
|
|
EncryptionThreadPoolStop ();
|
|
ResetResources (0);
|
|
Require (EncryptionThreadPoolStart (available - 2), "restart after failure");
|
|
EncryptionThreadPoolStop ();
|
|
}
|
|
ResetResources (0);
|
|
printf ("PASS: %d startup failure points, each followed by a successful restart\n", creationCount);
|
|
}
|
|
|
|
static unsigned __stdcall StopPool (void *unused)
|
|
{
|
|
EncryptionThreadPoolStop ();
|
|
SetEvent (StopDone);
|
|
return 0;
|
|
}
|
|
|
|
static HANDLE BeginStop (void)
|
|
{
|
|
HANDLE thread;
|
|
StopWaiting = Event (); StopDone = Event ();
|
|
thread = (HANDLE) _beginthreadex (NULL, 0, StopPool, NULL, 0, NULL);
|
|
Require (thread != NULL, "start shutdown thread");
|
|
Wait (StopWaiting);
|
|
Require (!IsEncryptionThreadPoolRunning (), "shutdown still accepts pool work");
|
|
Require (WaitForSingleObject (StopDone, 0) == WAIT_TIMEOUT, "shutdown did not drain accepted work");
|
|
return thread;
|
|
}
|
|
|
|
static void EndStop (HANDLE thread)
|
|
{
|
|
Wait (StopDone); Wait (thread);
|
|
CloseHandle (thread); CloseHandle (StopWaiting); CloseHandle (StopDone);
|
|
StopWaiting = StopDone = NULL;
|
|
ResetResources (0);
|
|
}
|
|
|
|
static unsigned __stdcall RunCipher (void *context)
|
|
{
|
|
CipherJob *job = (CipherJob *) context;
|
|
UINT64_STRUCT start = { 0 };
|
|
CurrentCipherJob = job;
|
|
EncryptionThreadPoolDoWork (job->Type, job->Data, &start, job->Units, (PCRYPTO_INFO) job);
|
|
CurrentCipherJob = NULL;
|
|
SetEvent (job->Done);
|
|
return 0;
|
|
}
|
|
|
|
static void StartCipher (CipherJob *job, BOOL hold, EncryptionThreadPoolWorkType type)
|
|
{
|
|
memset (job, 0, sizeof (*job));
|
|
job->Units = 6; job->Type = type;
|
|
job->HoldWorkers = job->HoldRequester = hold;
|
|
job->BothStarted = Event (); job->MayProcess = CreateEvent (NULL, TRUE, FALSE, NULL);
|
|
Require (job->MayProcess != NULL, "create cipher gate");
|
|
job->Waiting = Event (); job->Paused = Event (); job->MayReturn = Event (); job->Done = Event ();
|
|
job->Thread = (HANDLE) _beginthreadex (NULL, 0, RunCipher, job, 0, NULL);
|
|
Require (job->Thread != NULL, "start cipher requester");
|
|
Wait (job->Waiting);
|
|
}
|
|
|
|
static void CheckCipher (CipherJob *job)
|
|
{
|
|
size_t i;
|
|
unsigned char expected = job->Type == EncryptDataUnitsWork ? 0xa5 : 0x5a;
|
|
Require (job->Processed == job->Units, "incorrect number of processed units");
|
|
for (i = 0; i < job->Units * ENCRYPTION_DATA_UNIT_SIZE; ++i)
|
|
Require (job->Data[i] == expected, "incomplete cipher output");
|
|
}
|
|
|
|
static void EndCipher (CipherJob *job)
|
|
{
|
|
Wait (job->Done); Wait (job->Thread);
|
|
CheckCipher (job);
|
|
CloseHandle (job->Thread); CloseHandle (job->BothStarted); CloseHandle (job->MayProcess);
|
|
CloseHandle (job->Waiting); CloseHandle (job->Paused); CloseHandle (job->MayReturn); CloseHandle (job->Done);
|
|
}
|
|
|
|
static void InlineKdf (void)
|
|
{
|
|
Request *request = NewRequest ();
|
|
Job *job = NewJob (request, SHA512, FALSE, 0);
|
|
SetEvent (job->MayReturn);
|
|
SubmitJob (job);
|
|
CurrentJob = NULL;
|
|
Require (job->Item->KeyReady && job->Item->DerivationResult == 0 && References (request) == 1,
|
|
"synchronous KDF fallback");
|
|
FinishRequest (request);
|
|
Wait (request->FinishDone);
|
|
}
|
|
|
|
static void ShutdownInFlight (size_t available)
|
|
{
|
|
CipherJob first, queued, synchronous = { 0 };
|
|
UINT64_STRUCT start = { 0 };
|
|
HANDLE stop;
|
|
Request *request;
|
|
int i;
|
|
Require (EncryptionThreadPoolStart (available - 2), "start cipher pool");
|
|
StartCipher (&first, TRUE, EncryptDataUnitsWork);
|
|
Wait (first.BothStarted);
|
|
StartCipher (&queued, FALSE, DecryptDataUnitsWork);
|
|
stop = BeginStop ();
|
|
synchronous.Units = 6; synchronous.Type = EncryptDataUnitsWork;
|
|
EncryptionThreadPoolDoWork (synchronous.Type, synchronous.Data, &start, synchronous.Units, (PCRYPTO_INFO) &synchronous);
|
|
CheckCipher (&synchronous);
|
|
SetEvent (first.MayProcess);
|
|
Wait (first.Paused);
|
|
Require (WaitForSingleObject (StopDone, 0) == WAIT_TIMEOUT, "shutdown closed a requester's completion handle");
|
|
SetEvent (first.MayReturn);
|
|
EndCipher (&first); EndCipher (&queued); EndStop (stop);
|
|
|
|
Require (EncryptionThreadPoolStart (available - 2), "restart for KDF shutdown");
|
|
request = NewRequest ();
|
|
for (i = 0; i < PRF_COUNT; ++i) Submit (request, FIRST_PRF_ID + i, FALSE, 0);
|
|
BeginFinish (request);
|
|
stop = BeginStop ();
|
|
InlineKdf ();
|
|
for (i = 0; i < request->JobCount; ++i) SetEvent (request->Jobs[i].MayReturn);
|
|
EndFinish (request);
|
|
for (i = 0; i < request->JobCount; ++i) JobDone (&request->Jobs[i]);
|
|
EndStop (stop);
|
|
InlineKdf ();
|
|
puts ("PASS: shutdown drains active and queued encryption, KDFs and requesters; synchronous fallback and restart");
|
|
}
|
|
|
|
static void FreeRequests (void)
|
|
{
|
|
int i, j;
|
|
Require (OpenResources == 0, "pool resources leaked");
|
|
for (i = 0; i < RequestCount; ++i)
|
|
{
|
|
Request *r = &Requests[i];
|
|
for (j = 0; j < r->JobCount; ++j)
|
|
{
|
|
Job *job = &r->Jobs[j];
|
|
CloseHandle (job->Started); CloseHandle (job->MayReturn); CloseHandle (job->ReadyPublished);
|
|
CloseHandle (job->MayReleaseReference); CloseHandle (job->WorkerDone);
|
|
}
|
|
if (r->RequesterThread) CloseHandle (r->RequesterThread);
|
|
CloseHandle (r->WaitEntered); CloseHandle (r->FinishDone); CloseHandle (r->SignalEntered); CloseHandle (r->MaySignal);
|
|
CloseHandle (*r->Ready); CloseHandle (*r->Drained);
|
|
VirtualUnlock (r->Items, sizeof (*r->Items) * PRF_COUNT);
|
|
free (r->Ready); free (r->Drained); free (r->References); free (r->Items);
|
|
}
|
|
}
|
|
|
|
int main (int argc, char **argv)
|
|
{
|
|
WORD groups;
|
|
size_t available = GetCpuCount (&groups);
|
|
size_t wanted = argc > 1 ? (size_t) atoi (argv[1]) : 2;
|
|
if (available < 2) { puts ("SKIP: at least two logical CPUs required"); return 0; }
|
|
if (argc > 1 && strcmp (argv[1], "startup") == 0)
|
|
{
|
|
StartupFailures (available);
|
|
return 0;
|
|
}
|
|
if (argc > 1 && strcmp (argv[1], "shutdown") == 0)
|
|
{
|
|
ShutdownInFlight (available);
|
|
FreeRequests ();
|
|
return 0;
|
|
}
|
|
if (wanted > available) wanted = available;
|
|
Require (EncryptionThreadPoolStart (available - wanted), "start production pool");
|
|
Require (GetEncryptionThreadCount () == wanted, "pool worker count");
|
|
RequesterLast (0); RequesterLast (1); RequesterLast (5); RequesterLast (6);
|
|
WorkerLast (2, FALSE, 0); WorkerLast (6, FALSE, 0);
|
|
WorkerLast (2, TRUE, ARGON2_OPERATION_CANCELLED); WorkerLast (6, TRUE, ARGON2_OPERATION_CANCELLED);
|
|
WorkerLast (2, FALSE, ARGON2_MEMORY_ALLOCATION_ERROR);
|
|
PublicationBoundaries ();
|
|
|
|
/* Join every worker before releasing any request-owned allocation or event. */
|
|
EncryptionThreadPoolStop ();
|
|
FreeRequests ();
|
|
printf ("PASS: %d KDF lifetime scenarios with %u workers\n", RequestCount, (unsigned) wanted);
|
|
return 0;
|
|
}
|