#define WIN32_NO_STATUS #include #undef WIN32_NO_STATUS #include #include #include #include #include #define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0) #ifndef _IRQL_requires_max_ #define _IRQL_requires_max_(x) #endif #define PASSIVE_LEVEL 0 #define PAGED_CODE() ((void)0) typedef void* PFILE_OBJECT; typedef void* LPWIN_CERTIFICATE; struct MINCRYPT_POLICY_INFO { ULONG Size; NTSTATUS VerificationStatus; ULONG PolicyBits; void* ChainInfo; }; #include "ci-validation-api.inc" static unsigned checks, live, validationCalls, embeddedCalls, digestCalls, digestFrees; static NTSTATUS nativeStatus = STATUS_SUCCESS, embeddedStatus = STATUS_SUCCESS, digestStatus = STATUS_SUCCESS; static UINT32 nativeAlgorithm = CALG_SHA_256, nativeSize = 32; static ULONG fakeDigestSize = 32; static bool omitCertificate, omitDigest, partialDigestOnFailure; static const UCHAR image[] = {1,2,3}; static ULONG observedFlags, observedLevel, observedCapacity; static PFILE_OBJECT observedObject; static ALG_ID observedEmbeddedAlgorithm; static ULONG observedSecuritySize; static void* observedCertificate; static void Check(bool value, const char* message) { ++checks; if (!value) { std::fprintf(stderr, "FAIL: %s\n", message); std::exit(1); } } static void* Allocate(SIZE_T size) { void* value = std::malloc(size); Check(value != nullptr, "host allocation"); ++live; return value; } static void Release(void* value) { Check(value && live, "single allocation owner"); --live; std::free(value); } static void CiFreePolicyInfo(MINCRYPT_POLICY_INFO* policy) { if (policy->ChainInfo) Release(policy->ChainInfo); *policy = {}; } static void Populate(MINCRYPT_POLICY_INFO* policy, MINCRYPT_POLICY_INFO* timestamp) { Check(!policy->ChainInfo && !timestamp->ChainInfo, "old result freed before reuse"); policy->Size = timestamp->Size = sizeof(*policy); policy->ChainInfo = Allocate(1); timestamp->ChainInfo = Allocate(1); policy->PolicyBits = 0x123; timestamp->PolicyBits = 0x456; policy->VerificationStatus = STATUS_INVALID_IMAGE_HASH; } static NTSTATUS CiValidateFileObject(PFILE_OBJECT object, UINT32 flags, UINT32 level, MINCRYPT_POLICY_INFO* policy, MINCRYPT_POLICY_INFO* timestamp, LARGE_INTEGER* time, UINT8* hash, UINT32* size, ALG_ID* algorithm) { ++validationCalls; observedObject = object; observedFlags = flags; observedLevel = level; observedCapacity = *size; Check(*size == 64, "fresh native hash capacity"); Check(!time->QuadPart && !*algorithm, "native scalar outputs initialized"); for (unsigned i=0;i<64;++i) Check(!hash[i], "native digest starts empty"); Populate(policy, timestamp); time->QuadPart = 99; std::memset(hash, 0x5a, 64); *size = nativeSize; *algorithm = nativeAlgorithm; return nativeStatus; } static NTSTATUS DomitoCalculatePortableExecutableDigestEx(const UCHAR* bytes, ULONG size, UINT32 algorithm, PULONG digestSize, PVOID* digest, LPWIN_CERTIFICATE* certificate, PULONG securitySize) { ++digestCalls; Check(bytes == image && size == sizeof(image), "same PE snapshot forwarded"); Check(algorithm == CALG_SHA1 || algorithm == CALG_SHA_256 || algorithm == CALG_SHA_384 || algorithm == CALG_SHA_512, "supported digest choice forwarded"); if (NT_SUCCESS(digestStatus) || partialDigestOnFailure) { *digest = omitDigest ? nullptr : Allocate(64); if (*digest) std::memset(*digest, 0xa5, 64); *digestSize = fakeDigestSize; *certificate = omitCertificate ? nullptr : const_cast(image); *securitySize = omitCertificate ? 0 : 123; } return digestStatus; } static void DomitoFreePortableExecutableDigest(PVOID digest) { ++digestFrees; Release(digest); } static NTSTATUS CiCheckSignedFile(PVOID hash, UINT32 size, ALG_ID algorithm, LPWIN_CERTIFICATE certificate, ULONG securitySize, MINCRYPT_POLICY_INFO* policy, LARGE_INTEGER* time, MINCRYPT_POLICY_INFO* timestamp) { ++embeddedCalls; observedEmbeddedAlgorithm = algorithm; observedSecuritySize = securitySize; observedCertificate = certificate; for (UINT32 i=0;i(hash)[i] == 0xa5, "computed digest forwarded unchanged"); Populate(policy, timestamp); time->QuadPart = 123; return embeddedStatus; } #ifdef DOMITO_TEST_OTHER_ARCH #undef _M_AMD64 #undef _AMD64_ #endif #include "ci-validation-under-test.inc" static void CheckEmptyDigest(const DOMITO_CI_VALIDATION_RESULT& result) { Check(!result.HashSize && !result.HashAlgId && !result.SigningTime.QuadPart, "failure digest/time cleared"); for (auto value : result.Hash) Check(!value, "failure hash bytes cleared"); } static void Clear(DOMITO_CI_VALIDATION_RESULT* result) { DomitoFreeCiValidationResult(result); DOMITO_CI_VALIDATION_RESULT empty = {}; Check(std::memcmp(result, &empty, sizeof(empty)) == 0, "full result cleared by destructor"); Check(live == 0, "no leaked policy/digest allocation"); } int main() { DOMITO_CI_VALIDATION_RESULT result = {}; PFILE_OBJECT object = reinterpret_cast(static_cast(0x1234)); DomitoFreeCiValidationResult(nullptr); Clear(&result); Clear(&result); Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,nullptr)==STATUS_INVALID_PARAMETER, "null native result"); Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),CALG_SHA_256,nullptr)==STATUS_INVALID_PARAMETER, "null embedded result"); Check(!validationCalls && !embeddedCalls && !digestCalls, "null result made no validation calls"); for (UINT32 flags : {0UL,0x20UL,0xa0UL,0x80000024UL}) { for (UINT8 level : {UINT8(0),UINT8(7),UINT8(255)}) { result.HashSize=1; result.HashAlgId=0xdead; Check(DomitoValidateFileObjectEx(object,flags,level,DomitoCiSignatureSelectionNative,&result)==STATUS_SUCCESS,"native status"); Check(observedObject==object && observedFlags==flags && observedLevel==level && observedCapacity==64,"native inputs unchanged"); Check(result.FileObjectValidationFlags==flags && result.RequestedSigningLevel==level,"export input controls recorded"); Check(result.HashSize==32 && result.HashAlgId==CALG_SHA_256 && result.Hash[0]==0x5a && result.SigningTime.QuadPart==99,"native result"); // API return success does not silently reinterpret a policy status. Check(result.PolicyInfo.VerificationStatus==STATUS_INVALID_IMAGE_HASH,"policy observation retained"); Clear(&result); } } const unsigned beforeExperimental=validationCalls; NTSTATUS experimental=DomitoValidateFileObjectEx(object,0x20,9,DomitoCiSignatureSelectionFirstSuccessfulExperimental,&result); #ifdef DOMITO_TEST_OTHER_ARCH Check(experimental==STATUS_NOT_SUPPORTED && validationCalls==beforeExperimental,"experimental mode excluded on non-x64"); CheckEmptyDigest(result); #else Check(experimental==STATUS_SUCCESS && validationCalls==beforeExperimental+1,"experimental native call"); Check(observedFlags==0xa0 && observedLevel==9,"only experimental bit added"); #endif Clear(&result); const unsigned beforeInvalid=validationCalls; Check(DomitoValidateFileObjectEx(nullptr,0x20,0,DomitoCiSignatureSelectionNative,&result)==STATUS_INVALID_PARAMETER,"null file object"); Check(DomitoValidateFileObjectEx(object,0x20,0,static_cast(-1),&result)==STATUS_INVALID_PARAMETER,"invalid selection"); Check(validationCalls==beforeInvalid,"invalid requests never call validation"); Clear(&result); for (NTSTATUS failure : {STATUS_INVALID_IMAGE_HASH,STATUS_BUFFER_TOO_SMALL,STATUS_INSUFFICIENT_RESOURCES}) { nativeStatus=failure; Check(DomitoValidateFileObjectEx(object,0x20,0,DomitoCiSignatureSelectionNative,&result)==failure,"native failure preserved"); CheckEmptyDigest(result); Check(live==2 && result.PolicyInfo.PolicyBits==0x123,"failure policy diagnostics remain owned"); Clear(&result); } nativeStatus=STATUS_SUCCESS; nativeSize=65; Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,&result)==STATUS_INVALID_BUFFER_SIZE,"oversized returned hash rejected"); CheckEmptyDigest(result); Clear(&result); nativeSize=31; Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,&result)==STATUS_INVALID_BUFFER_SIZE,"algorithm length mismatch rejected"); Clear(&result); nativeSize=32; nativeAlgorithm=0xdead; Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,&result)==STATUS_NOT_SUPPORTED,"unknown returned algorithm rejected"); Clear(&result); for (ALG_ID algorithm : {CALG_SHA1,CALG_SHA_256,CALG_SHA_384,CALG_SHA_512}) { nativeAlgorithm=algorithm; nativeSize=DomitoCiDigestSize(algorithm); Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,&result)==STATUS_SUCCESS,"known native digest sizes"); for (UINT32 i=result.HashSize;i