Fix process-name ownership and module lookup outputs, add explicit SPKI hashing and bounded chain access, and improve WDM integration. Deprecate superseded entry points with migration guidance and align branch code and tests with the library style.
176 lines
4.6 KiB
C++
176 lines
4.6 KiB
C++
// Host tests validate the probe plumbing, NOT CI trust behavior.
|
|
#define WIN32_NO_STATUS
|
|
#include <windows.h>
|
|
#undef WIN32_NO_STATUS
|
|
#include <ntstatus.h>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include "ci-probe-constants.inc"
|
|
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
|
|
#define DOMITO_CI_PROBE_HOST_TEST
|
|
typedef void* PFILE_OBJECT;
|
|
|
|
struct MINCRYPT_POLICY_INFO
|
|
{
|
|
ULONG Size;
|
|
ULONG VerificationStatus;
|
|
ULONG PolicyBits;
|
|
void* ChainInfo;
|
|
};
|
|
|
|
static unsigned calls;
|
|
static unsigned frees;
|
|
static unsigned outstanding;
|
|
static unsigned checks;
|
|
static bool failureMode;
|
|
static int fileToken;
|
|
static int chainToken;
|
|
|
|
static void
|
|
Check(
|
|
bool value,
|
|
const char* label
|
|
)
|
|
{
|
|
++checks;
|
|
|
|
if (!value)
|
|
{
|
|
std::fprintf(stderr, "FAIL: %s\n", label);
|
|
std::exit(1);
|
|
}
|
|
}
|
|
|
|
static NTSTATUS
|
|
CiValidateFileObject(
|
|
PFILE_OBJECT file,
|
|
ULONG flags,
|
|
ULONG level,
|
|
MINCRYPT_POLICY_INFO* policy,
|
|
MINCRYPT_POLICY_INFO* timestamp,
|
|
LARGE_INTEGER* time,
|
|
UCHAR* hash,
|
|
UINT32* size,
|
|
ALG_ID* algorithm
|
|
)
|
|
{
|
|
const ULONG expectedFlags[] = {0, 0x20, 0x20, 0, 0, 0x20};
|
|
Check(calls < 6 && flags == expectedFlags[calls], "paired flags and order");
|
|
Check(file == &fileToken && level == 0, "same file and level zero");
|
|
Check(outstanding == 0, "previous outputs freed before next call");
|
|
Check(policy->Size == sizeof(*policy) && timestamp->Size == sizeof(*timestamp), "policy sizes");
|
|
Check(
|
|
!policy->ChainInfo && !timestamp->ChainInfo && !policy->PolicyBits && !timestamp->PolicyBits,
|
|
"fresh policy objects"
|
|
);
|
|
Check(*size == (calls < 4 ? 64u : 63u), "capacity reset for every call");
|
|
Check(*algorithm == 0 && time->QuadPart == 0, "fresh scalar outputs");
|
|
|
|
for (unsigned i = 0; i < 64; ++i)
|
|
{
|
|
Check(hash[i] == 0, "fresh hash buffer");
|
|
}
|
|
|
|
++calls;
|
|
|
|
if (*size < 64)
|
|
{
|
|
*size = 64;
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
policy->ChainInfo = timestamp->ChainInfo = &chainToken;
|
|
outstanding = 2;
|
|
policy->PolicyBits = 0xA5;
|
|
timestamp->PolicyBits = 0x5A;
|
|
policy->VerificationStatus = failureMode ? STATUS_INVALID_IMAGE_HASH : STATUS_SUCCESS;
|
|
timestamp->VerificationStatus = STATUS_SUCCESS;
|
|
// Also allocate on simulated failure to exercise unconditional cleanup.
|
|
if (failureMode)
|
|
{
|
|
return STATUS_INVALID_IMAGE_HASH;
|
|
}
|
|
|
|
std::memset(hash, 0xCC, 32);
|
|
*size = 32;
|
|
*algorithm = 0x800C;
|
|
time->QuadPart = 123;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
CiFreePolicyInfo(
|
|
MINCRYPT_POLICY_INFO* policy
|
|
)
|
|
{
|
|
++frees;
|
|
|
|
if (policy->ChainInfo)
|
|
{
|
|
Check(outstanding > 0, "owned chain");
|
|
--outstanding;
|
|
}
|
|
|
|
*policy = {};
|
|
}
|
|
|
|
#include "ci-file-object-probe.cpp"
|
|
|
|
int
|
|
main()
|
|
{
|
|
for (unsigned pass = 0; pass < 2; ++pass)
|
|
{
|
|
failureMode = pass != 0;
|
|
calls = frees = outstanding = 0;
|
|
DOMITO_CI_PROBE_RESULT results[DOMITO_CI_PROBE_RESULT_COUNT];
|
|
std::memset(results, 0xFF, sizeof(results));
|
|
DomitoProbeCiValidateFileObject(&fileToken, results);
|
|
Check(calls == 6 && frees == 12 && outstanding == 0, "all calls and frees");
|
|
|
|
for (unsigned i = 0; i < 6; ++i)
|
|
{
|
|
const auto& r = results[i];
|
|
Check(r.HashGuardIntact, "buffer guard");
|
|
const NTSTATUS expected = i >= 4 ? STATUS_BUFFER_TOO_SMALL
|
|
: failureMode ? STATUS_INVALID_IMAGE_HASH
|
|
: STATUS_SUCCESS;
|
|
Check(r.Status == expected, "status preserved");
|
|
|
|
if (i < 4)
|
|
{
|
|
Check(r.PolicyBits == 0xA5 && r.TimeStampPolicyBits == 0x5A, "policy capture");
|
|
Check(r.HasChain && r.HasTimeStampChain, "chain presence captured before free");
|
|
}
|
|
|
|
if (NT_SUCCESS(r.Status))
|
|
{
|
|
Check(
|
|
r.HashSize == 32 && r.HashAlgId == 0x800C && r.SigningTime.QuadPart == 123,
|
|
"digest metadata and signing time"
|
|
);
|
|
|
|
for (unsigned j = 0; j < 64; ++j)
|
|
{
|
|
Check(r.Hash[j] == (j < 32 ? 0xCC : 0), "digest copied and tail cleared");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto byte : r.Hash)
|
|
{
|
|
Check(byte == 0, "no digest from failed call");
|
|
}
|
|
}
|
|
|
|
if (i >= 4)
|
|
{
|
|
Check(r.HashSize == 64, "required capacity captured");
|
|
}
|
|
}
|
|
}
|
|
|
|
std::printf("PASS: %u probe checks (mock CI; no runtime trust validation)\n", checks);
|
|
}
|