Files
Domito/tests/ci-file-object-probe-tests.cpp
T

97 lines
4.1 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, VerificationStatus, PolicyBits;
void* ChainInfo;
};
static unsigned calls, frees, outstanding, checks;
static bool failureMode;
static int fileToken, 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);
}