// Host tests validate the probe plumbing, NOT CI trust behavior. #define WIN32_NO_STATUS #include #undef WIN32_NO_STATUS #include #include #include #include #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); }