From: "Rémi Bernon" Subject: [PATCH 2/3] winetest: Introduce new winetest static library. Message-Id: <20210831102559.1981441-2-rbernon@codeweavers.com> Date: Tue, 31 Aug 2021 12:25:58 +0200 In-Reply-To: <20210831102559.1981441-1-rbernon@codeweavers.com> References: <20210831102559.1981441-1-rbernon@codeweavers.com> To share driver signing, loading and testing code. Its use is optional, and the interface is available in a new "wine/test_driver.h" header. Tests which do not need a driver stay standalone, using "wine/test.h" only. Signed-off-by: Rémi Bernon --- configure.ac | 1 + dlls/winetest/Makefile.in | 4 + dlls/winetest/winetest_driver.c | 569 ++++++++++++++++++++++++++++++++ include/wine/test_driver.h | 492 +++++++++++++++++++++++++++ 4 files changed, 1066 insertions(+) create mode 100644 dlls/winetest/Makefile.in create mode 100644 dlls/winetest/winetest_driver.c create mode 100644 include/wine/test_driver.h diff --git a/configure.ac b/configure.ac index 594794ed93c..56422a4fb6a 100644 --- a/configure.ac +++ b/configure.ac @@ -3795,6 +3795,7 @@ WINE_CONFIG_MAKEFILE(dlls/wineps.drv) WINE_CONFIG_MAKEFILE(dlls/wineps16.drv16,enable_win16) WINE_CONFIG_MAKEFILE(dlls/winepulse.drv) WINE_CONFIG_MAKEFILE(dlls/wineqtdecoder) +WINE_CONFIG_MAKEFILE(dlls/winetest) WINE_CONFIG_MAKEFILE(dlls/wineusb.sys) WINE_CONFIG_MAKEFILE(dlls/winevulkan) WINE_CONFIG_MAKEFILE(dlls/winex11.drv) diff --git a/dlls/winetest/Makefile.in b/dlls/winetest/Makefile.in new file mode 100644 index 00000000000..d4d092d993f --- /dev/null +++ b/dlls/winetest/Makefile.in @@ -0,0 +1,4 @@ +MODULE = libwinetest.a + +C_SRCS = \ + winetest_driver.c diff --git a/dlls/winetest/winetest_driver.c b/dlls/winetest/winetest_driver.c new file mode 100644 index 00000000000..9d5202f09df --- /dev/null +++ b/dlls/winetest/winetest_driver.c @@ -0,0 +1,569 @@ +/* + * Wine testing framework - Driver test helpers + * + * Copyright 2015 Sebastian Lackner + * Copyright 2015 Michael Müller + * Copyright 2015 Christian Costa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winioctl.h" +#include "winternl.h" +#include "winuser.h" +#include "winnls.h" +#include "winsvc.h" +#include "winreg.h" +#include "wincrypt.h" + +#include "mscat.h" +#include "newdev.h" +#include "ntsecapi.h" +#include "objbase.h" +#include "setupapi.h" + +#include "wine/mssign.h" +#include "wine/test.h" +#include "wine/test_driver.h" + +static HANDLE test_data_mapping; +static struct winetest_shared_data *test_data; + +static HRESULT (WINAPI *pSignerSign)(SIGNER_SUBJECT_INFO *subject, SIGNER_CERT *cert, + SIGNER_SIGNATURE_INFO *signature, SIGNER_PROVIDER_INFO *provider, + const WCHAR *timestamp, CRYPT_ATTRIBUTES *attr, void *sip_data); + +static void load_resource(const WCHAR *name, WCHAR *filename) +{ + static WCHAR path[MAX_PATH]; + DWORD written; + HANDLE file; + HRSRC res; + void *ptr; + + GetTempPathW(ARRAY_SIZE(path), path); + GetTempFileNameW(path, name, 0, filename); + + file = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0); + ok(file != INVALID_HANDLE_VALUE, "failed to create %s, error %u\n", debugstr_w(filename), GetLastError()); + + res = FindResourceW(NULL, name, L"TESTDLL"); + ok( res != 0, "couldn't find resource\n" ); + ptr = LockResource( LoadResource( GetModuleHandleW(NULL), res )); + WriteFile( file, ptr, SizeofResource( GetModuleHandleW(NULL), res ), &written, NULL ); + ok( written == SizeofResource( GetModuleHandleW(NULL), res ), "couldn't write resource\n" ); + CloseHandle( file ); +} + +static BOOL testsign_create_cert(struct winetest_driver_context *ctx) +{ + BYTE encoded_name[100], encoded_key_id[200], public_key_info_buffer[1000]; + WCHAR container_name[26]; + BYTE hash_buffer[16], cert_buffer[1000], provider_nameA[100], serial[16]; + CERT_PUBLIC_KEY_INFO *public_key_info = (CERT_PUBLIC_KEY_INFO *)public_key_info_buffer; + CRYPT_KEY_PROV_INFO provider_info = {0}; + CRYPT_ALGORITHM_IDENTIFIER algid = {0}; + CERT_AUTHORITY_KEY_ID_INFO key_info; + CERT_INFO cert_info = {0}; + WCHAR provider_nameW[100]; + CERT_EXTENSION extension; + HCRYPTKEY key; + DWORD size; + BOOL ret; + + memset(ctx, 0, sizeof(*ctx)); + + srand(time(NULL)); + swprintf(container_name, ARRAY_SIZE(container_name), L"wine_testsign%u", rand()); + + ret = CryptAcquireContextW(&ctx->provider, container_name, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET); + ok(ret, "Failed to create container, error %u\n", GetLastError()); + + ret = CryptGenKey(ctx->provider, AT_SIGNATURE, CRYPT_EXPORTABLE, &key); + ok(ret, "Failed to create key, error %u\n", GetLastError()); + ret = CryptDestroyKey(key); + ok(ret, "Failed to destroy key, error %u\n", GetLastError()); + ret = CryptGetUserKey(ctx->provider, AT_SIGNATURE, &key); + ok(ret, "Failed to get user key, error %u\n", GetLastError()); + ret = CryptDestroyKey(key); + ok(ret, "Failed to destroy key, error %u\n", GetLastError()); + + size = sizeof(encoded_name); + ret = CertStrToNameW(X509_ASN_ENCODING, L"CN=winetest_cert", CERT_X500_NAME_STR, NULL, encoded_name, &size, NULL); + ok(ret, "Failed to convert name, error %u\n", GetLastError()); + key_info.CertIssuer.cbData = size; + key_info.CertIssuer.pbData = encoded_name; + + size = sizeof(public_key_info_buffer); + ret = CryptExportPublicKeyInfo(ctx->provider, AT_SIGNATURE, X509_ASN_ENCODING, public_key_info, &size); + ok(ret, "Failed to export public key, error %u\n", GetLastError()); + cert_info.SubjectPublicKeyInfo = *public_key_info; + + size = sizeof(hash_buffer); + ret = CryptHashPublicKeyInfo(ctx->provider, CALG_MD5, 0, X509_ASN_ENCODING, public_key_info, hash_buffer, &size); + ok(ret, "Failed to hash public key, error %u\n", GetLastError()); + + key_info.KeyId.cbData = size; + key_info.KeyId.pbData = hash_buffer; + + RtlGenRandom(serial, sizeof(serial)); + key_info.CertSerialNumber.cbData = sizeof(serial); + key_info.CertSerialNumber.pbData = serial; + + size = sizeof(encoded_key_id); + ret = CryptEncodeObject(X509_ASN_ENCODING, X509_AUTHORITY_KEY_ID, &key_info, encoded_key_id, &size); + ok(ret, "Failed to convert name, error %u\n", GetLastError()); + + extension.pszObjId = (char *)szOID_AUTHORITY_KEY_IDENTIFIER; + extension.fCritical = TRUE; + extension.Value.cbData = size; + extension.Value.pbData = encoded_key_id; + + cert_info.dwVersion = CERT_V3; + cert_info.SerialNumber = key_info.CertSerialNumber; + cert_info.SignatureAlgorithm.pszObjId = (char *)szOID_RSA_SHA1RSA; + cert_info.Issuer = key_info.CertIssuer; + GetSystemTimeAsFileTime(&cert_info.NotBefore); + GetSystemTimeAsFileTime(&cert_info.NotAfter); + cert_info.NotAfter.dwHighDateTime += 1; + cert_info.Subject = key_info.CertIssuer; + cert_info.cExtension = 1; + cert_info.rgExtension = &extension; + algid.pszObjId = (char *)szOID_RSA_SHA1RSA; + size = sizeof(cert_buffer); + ret = CryptSignAndEncodeCertificate(ctx->provider, AT_SIGNATURE, X509_ASN_ENCODING, + X509_CERT_TO_BE_SIGNED, &cert_info, &algid, NULL, cert_buffer, &size); + ok(ret, "Failed to create certificate, error %u\n", GetLastError()); + + ctx->cert = CertCreateCertificateContext(X509_ASN_ENCODING, cert_buffer, size); + ok(!!ctx->cert, "Failed to create context, error %u\n", GetLastError()); + + size = sizeof(provider_nameA); + ret = CryptGetProvParam(ctx->provider, PP_NAME, provider_nameA, &size, 0); + ok(ret, "Failed to get prov param, error %u\n", GetLastError()); + MultiByteToWideChar(CP_ACP, 0, (char *)provider_nameA, -1, provider_nameW, ARRAY_SIZE(provider_nameW)); + + provider_info.pwszContainerName = (WCHAR *)container_name; + provider_info.pwszProvName = provider_nameW; + provider_info.dwProvType = PROV_RSA_FULL; + provider_info.dwKeySpec = AT_SIGNATURE; + ret = CertSetCertificateContextProperty(ctx->cert, CERT_KEY_PROV_INFO_PROP_ID, 0, &provider_info); + ok(ret, "Failed to set provider info, error %u\n", GetLastError()); + + ctx->root_store = CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_A, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, "root"); + if (!ctx->root_store && GetLastError() == ERROR_ACCESS_DENIED) + { + skip("Failed to open root store.\n"); + + ret = CertFreeCertificateContext(ctx->cert); + ok(ret, "Failed to free certificate, error %u\n", GetLastError()); + ret = CryptReleaseContext(ctx->provider, 0); + ok(ret, "failed to release context, error %u\n", GetLastError()); + + return FALSE; + } + ok(!!ctx->root_store, "Failed to open store, error %u\n", GetLastError()); + ret = CertAddCertificateContextToStore(ctx->root_store, ctx->cert, CERT_STORE_ADD_ALWAYS, &ctx->root_cert); + if (!ret && GetLastError() == ERROR_ACCESS_DENIED) + { + skip("Failed to add self-signed certificate to store.\n"); + + ret = CertFreeCertificateContext(ctx->cert); + ok(ret, "Failed to free certificate, error %u\n", GetLastError()); + ret = CertCloseStore(ctx->root_store, CERT_CLOSE_STORE_CHECK_FLAG); + ok(ret, "Failed to close store, error %u\n", GetLastError()); + ret = CryptReleaseContext(ctx->provider, 0); + ok(ret, "failed to release context, error %u\n", GetLastError()); + + return FALSE; + } + ok(ret, "Failed to add certificate, error %u\n", GetLastError()); + + ctx->publisher_store = CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_A, 0, 0, + CERT_SYSTEM_STORE_LOCAL_MACHINE, "trustedpublisher"); + ok(!!ctx->publisher_store, "Failed to open store, error %u\n", GetLastError()); + ret = CertAddCertificateContextToStore(ctx->publisher_store, ctx->cert, + CERT_STORE_ADD_ALWAYS, &ctx->publisher_cert); + ok(ret, "Failed to add certificate, error %u\n", GetLastError()); + + return TRUE; +} + +static void testsign_cleanup(struct winetest_driver_context *ctx) +{ + BOOL ret; + + ret = CertFreeCertificateContext(ctx->cert); + ok(ret, "Failed to free certificate, error %u\n", GetLastError()); + + ret = CertDeleteCertificateFromStore(ctx->root_cert); + ok(ret, "Failed to remove certificate, error %u\n", GetLastError()); + ret = CertCloseStore(ctx->root_store, CERT_CLOSE_STORE_CHECK_FLAG); + ok(ret, "Failed to close store, error %u\n", GetLastError()); + + ret = CertDeleteCertificateFromStore(ctx->publisher_cert); + ok(ret, "Failed to remove certificate, error %u\n", GetLastError()); + ret = CertCloseStore(ctx->publisher_store, CERT_CLOSE_STORE_CHECK_FLAG); + ok(ret, "Failed to close store, error %u\n", GetLastError()); + + ret = CryptReleaseContext(ctx->provider, 0); + ok(ret, "failed to release context, error %u\n", GetLastError()); +} + +static void testsign_sign(struct winetest_driver_context *ctx, const WCHAR *filename) +{ + SIGNER_ATTR_AUTHCODE authcode = {sizeof(authcode)}; + SIGNER_SIGNATURE_INFO signature = {sizeof(signature)}; + SIGNER_SUBJECT_INFO subject = {sizeof(subject)}; + SIGNER_CERT_STORE_INFO store = {sizeof(store)}; + SIGNER_CERT cert_info = {sizeof(cert_info)}; + SIGNER_FILE_INFO file = {sizeof(file)}; + DWORD index = 0; + HRESULT hr; + + subject.dwSubjectChoice = 1; + subject.pdwIndex = &index; + subject.pSignerFileInfo = &file; + file.pwszFileName = (WCHAR *)filename; + cert_info.dwCertChoice = 2; + cert_info.pCertStoreInfo = &store; + store.pSigningCert = ctx->cert; + store.dwCertPolicy = 0; + signature.algidHash = CALG_SHA_256; + signature.dwAttrChoice = SIGNER_AUTHCODE_ATTR; + signature.pAttrAuthcode = &authcode; + authcode.pwszName = L""; + authcode.pwszInfo = L""; + hr = pSignerSign(&subject, &cert_info, &signature, NULL, NULL, NULL, NULL); + todo_wine ok(hr == S_OK || broken(hr == NTE_BAD_ALGID) /* < 7 */, "Failed to sign, hr %#x\n", hr); +} + +static void unload_driver(SC_HANDLE service) +{ + SERVICE_STATUS status; + + ControlService(service, SERVICE_CONTROL_STOP, &status); + while (status.dwCurrentState == SERVICE_STOP_PENDING) + { + BOOL ret; + Sleep(100); + ret = QueryServiceStatus(service, &status); + ok(ret, "QueryServiceStatus failed: %u\n", GetLastError()); + } + ok(status.dwCurrentState == SERVICE_STOPPED, + "expected SERVICE_STOPPED, got %d\n", status.dwCurrentState); + + DeleteService(service); + CloseServiceHandle(service); +} + +static HANDLE okfile; + +void winetest_driver_check_failures(void) +{ + char buffer[512]; + DWORD size; + + SetFilePointer(okfile, 0, NULL, FILE_BEGIN); + + do + { + ReadFile(okfile, buffer, sizeof(buffer), &size, NULL); + printf("%.*s", size, buffer); + } while (size == sizeof(buffer)); + + SetFilePointer(okfile, 0, NULL, FILE_BEGIN); + SetEndOfFile(okfile); + + winetest_add_failures(InterlockedExchange(&test_data->failures, 0)); + winetest_add_failures(InterlockedExchange(&test_data->todo_failures, 0)); +} + +#ifdef __i386__ +#define EXT "x86" +#elif defined(__x86_64__) +#define EXT "amd64" +#elif defined(__arm__) +#define EXT "arm" +#elif defined(__aarch64__) +#define EXT "arm64" +#else +#define EXT +#endif + +static const char inf_text[] = + "[Version]\n" + "Signature=$Chicago$\n" + "ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318}\n" + "CatalogFile=winetest.cat\n" + "DriverVer=09/21/2006,6.0.5736.1\n" + + "[Manufacturer]\n" + "Wine=mfg_section,NT" EXT "\n" + + "[mfg_section.NT" EXT "]\n" + "Wine test root driver=device_section,test_hardware_id\n" + + "[device_section.NT" EXT "]\n" + "CopyFiles=file_section\n" + + "[device_section.NT" EXT ".Services]\n" + "AddService=winetest,0x2,svc_section\n" + + "[file_section]\n" + "winetest.sys\n" + + "[SourceDisksFiles]\n" + "winetest.sys=1\n" + + "[SourceDisksNames]\n" + "1=,winetest.sys\n" + + "[DestinationDirs]\n" + "DefaultDestDir=12\n" + + "[svc_section]\n" + "ServiceBinary=%12%\\winetest.sys\n" + "ServiceType=1\n" + "StartType=3\n" + "ErrorControl=1\n" + "LoadOrderGroup=Extended Base\n" + "DisplayName=\"winetest bus driver\"\n" + "; they don't sleep anymore, on the beach\n"; + +static void add_file_to_catalog(HANDLE catalog, const WCHAR *file) +{ + SIP_SUBJECTINFO subject_info = {sizeof(SIP_SUBJECTINFO)}; + SIP_INDIRECT_DATA *indirect_data; + const WCHAR *filepart = file; + CRYPTCATMEMBER *member; + WCHAR hash_buffer[100]; + GUID subject_guid; + unsigned int i; + DWORD size; + BOOL ret; + + ret = CryptSIPRetrieveSubjectGuidForCatalogFile(file, NULL, &subject_guid); + todo_wine ok(ret, "Failed to get subject guid, error %u\n", GetLastError()); + + size = 0; + subject_info.pgSubjectType = &subject_guid; + subject_info.pwsFileName = file; + subject_info.DigestAlgorithm.pszObjId = (char *)szOID_OIWSEC_sha1; + subject_info.dwFlags = SPC_INC_PE_RESOURCES_FLAG | SPC_INC_PE_IMPORT_ADDR_TABLE_FLAG | SPC_EXC_PE_PAGE_HASHES_FLAG | 0x10000; + ret = CryptSIPCreateIndirectData(&subject_info, &size, NULL); + todo_wine ok(ret, "Failed to get indirect data size, error %u\n", GetLastError()); + + indirect_data = malloc(size); + ret = CryptSIPCreateIndirectData(&subject_info, &size, indirect_data); + todo_wine ok(ret, "Failed to get indirect data, error %u\n", GetLastError()); + if (ret) + { + memset(hash_buffer, 0, sizeof(hash_buffer)); + for (i = 0; i < indirect_data->Digest.cbData; ++i) + swprintf(&hash_buffer[i * 2], 2, L"%02X", indirect_data->Digest.pbData[i]); + + member = CryptCATPutMemberInfo(catalog, (WCHAR *)file, + hash_buffer, &subject_guid, 0, size, (BYTE *)indirect_data); + ok(!!member, "Failed to write member, error %u\n", GetLastError()); + + if (wcsrchr(file, '\\')) + filepart = wcsrchr(file, '\\') + 1; + + ret = !!CryptCATPutAttrInfo(catalog, member, (WCHAR *)L"File", + CRYPTCAT_ATTR_NAMEASCII | CRYPTCAT_ATTR_DATAASCII | CRYPTCAT_ATTR_AUTHENTICATED, + (wcslen(filepart) + 1) * 2, (BYTE *)filepart); + ok(ret, "Failed to write attr, error %u\n", GetLastError()); + + ret = !!CryptCATPutAttrInfo(catalog, member, (WCHAR *)L"OSAttr", + CRYPTCAT_ATTR_NAMEASCII | CRYPTCAT_ATTR_DATAASCII | CRYPTCAT_ATTR_AUTHENTICATED, + sizeof(L"2:6.0"), (BYTE *)L"2:6.0"); + ok(ret, "Failed to write attr, error %u\n", GetLastError()); + } +} + +BOOL winetest_driver_start(struct winetest_driver_context *ctx, const WCHAR *resource) +{ + static const WCHAR hardware_id[] = L"test_hardware_id\0"; + WCHAR path[MAX_PATH], tempdir[MAX_PATH], filename[MAX_PATH]; + SC_HANDLE manager, service; + BOOL ret, need_reboot; + HANDLE catalog; + FILE *f; + + GetCurrentDirectoryW(ARRAY_SIZE(ctx->cwd), ctx->cwd); + GetTempPathW(ARRAY_SIZE(tempdir), tempdir); + SetCurrentDirectoryW(tempdir); + + load_resource(resource, filename); + ret = MoveFileExW(filename, L"winetest.sys", MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING); + ok(ret, "failed to move file, error %u\n", GetLastError()); + + f = fopen("winetest.inf", "w"); + ok(!!f, "failed to open winetest.inf: %s\n", strerror(errno)); + fputs(inf_text, f); + fclose(f); + + /* Create the catalog file. */ + + catalog = CryptCATOpen((WCHAR *)L"winetest.cat", CRYPTCAT_OPEN_CREATENEW, 0, CRYPTCAT_VERSION_1, 0); + ok(catalog != INVALID_HANDLE_VALUE, "Failed to create catalog, error %u\n", GetLastError()); + + add_file_to_catalog(catalog, L"winetest.sys"); + add_file_to_catalog(catalog, L"winetest.inf"); + + ret = CryptCATPersistStore(catalog); + todo_wine ok(ret, "Failed to write catalog, error %u\n", GetLastError()); + + ret = CryptCATClose(catalog); + ok(ret, "Failed to close catalog, error %u\n", GetLastError()); + + testsign_sign(ctx, L"winetest.cat"); + + /* Install the driver. */ + + ctx->set = SetupDiCreateDeviceInfoList(NULL, NULL); + ok(ctx->set != INVALID_HANDLE_VALUE, "failed to create device list, error %u\n", GetLastError()); + + ctx->device.cbSize = sizeof(ctx->device); + ret = SetupDiCreateDeviceInfoW(ctx->set, L"root\\winetest\\0", &GUID_NULL, NULL, NULL, 0, &ctx->device); + ok(ret, "failed to create device, error %u\n", GetLastError()); + + ret = SetupDiSetDeviceRegistryPropertyW( ctx->set, &ctx->device, SPDRP_HARDWAREID, + (const BYTE *)hardware_id, sizeof(hardware_id) ); + ok(ret, "failed to create set hardware ID, error %u\n", GetLastError()); + + ret = SetupDiCallClassInstaller(DIF_REGISTERDEVICE, ctx->set, &ctx->device); + ok(ret, "failed to register device, error %u\n", GetLastError()); + + GetFullPathNameW(L"winetest.inf", sizeof(path), path, NULL); + ret = UpdateDriverForPlugAndPlayDevicesW(NULL, hardware_id, path, INSTALLFLAG_FORCE, &need_reboot); + ok(ret, "failed to install device, error %u\n", GetLastError()); + ok(!need_reboot, "expected no reboot necessary\n"); + + /* Check that the service is created and started. */ + manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT); + ok(!!manager, "failed to open service manager, error %u\n", GetLastError()); + service = OpenServiceW(manager, L"winetest", SERVICE_START | SERVICE_STOP | DELETE); + ok(!!service, "failed to open service, error %u\n", GetLastError()); + + ret = StartServiceW(service, 0, NULL); + ok(!ret, "service didn't start automatically\n"); + if (!ret && GetLastError() != ERROR_SERVICE_ALREADY_RUNNING) + { + /* If Secure Boot is enabled or the machine is 64-bit, it will reject an unsigned driver. */ + ok(GetLastError() == ERROR_DRIVER_BLOCKED || GetLastError() == ERROR_INVALID_IMAGE_HASH, + "unexpected error %u\n", GetLastError()); + skip("Failed to start service; probably your machine doesn't accept unsigned drivers.\n"); + } + + CloseServiceHandle(service); + CloseServiceHandle(manager); + return ret || GetLastError() == ERROR_SERVICE_ALREADY_RUNNING; +} + +void winetest_driver_stop(struct winetest_driver_context *ctx) +{ + WCHAR path[MAX_PATH], dest[MAX_PATH], *filepart; + SC_HANDLE manager, service; + HANDLE file; + BOOL ret; + + ret = SetupDiCallClassInstaller(DIF_REMOVE, ctx->set, &ctx->device); + ok(ret, "failed to remove device, error %u\n", GetLastError()); + + file = CreateFileW(L"\\\\?\\root#winetest#0#{deadbeef-29ef-4538-a5fd-b69573a362c0}", 0, 0, NULL, OPEN_EXISTING, 0, NULL); + ok(file == INVALID_HANDLE_VALUE, "expected failure\n"); + ok(GetLastError() == ERROR_FILE_NOT_FOUND, "got error %u\n", GetLastError()); + + ret = SetupDiDestroyDeviceInfoList(ctx->set); + ok(ret, "failed to destroy set, error %u\n", GetLastError()); + + /* Windows stops the service but does not delete it. */ + manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT); + ok(!!manager, "failed to open service manager, error %u\n", GetLastError()); + service = OpenServiceW(manager, L"winetest", SERVICE_STOP | DELETE); + ok(!!service, "failed to open service, error %u\n", GetLastError()); + + unload_driver(service); + CloseServiceHandle(manager); + + winetest_driver_check_failures(); + + GetFullPathNameW(L"winetest.inf", sizeof(path), path, NULL); + ret = SetupCopyOEMInfW(path, NULL, 0, 0, dest, sizeof(dest), NULL, &filepart); + ok(ret, "Failed to copy INF, error %u\n", GetLastError()); + ret = SetupUninstallOEMInfW(filepart, 0, NULL); + ok(ret, "Failed to uninstall INF, error %u\n", GetLastError()); + + ret = DeleteFileW(L"winetest.cat"); + ok(ret, "Failed to delete file, error %u\n", GetLastError()); + ret = DeleteFileW(L"winetest.inf"); + ok(ret, "Failed to delete file, error %u\n", GetLastError()); + ret = DeleteFileW(L"winetest.sys"); + ok(ret, "Failed to delete file, error %u\n", GetLastError()); + /* Windows 10 apparently deletes the image in SetupUninstallOEMInf(). */ + ret = DeleteFileW(L"C:/windows/system32/drivers/winetest.sys"); + ok(ret || GetLastError() == ERROR_FILE_NOT_FOUND, "Failed to delete file, error %u\n", GetLastError()); + + SetCurrentDirectoryW(ctx->cwd); +} + +BOOL winetest_driver_init_(struct winetest_driver_context *ctx) +{ + BOOL is_wow64; + + pSignerSign = (void *)GetProcAddress(LoadLibraryW(L"mssign32"), "SignerSign"); + + if (IsWow64Process(GetCurrentProcess(), &is_wow64) && is_wow64) + { + skip("Running in WoW64.\n"); + return FALSE; + } + + if (!testsign_create_cert(ctx)) + return FALSE; + + test_data_mapping = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, + 0, sizeof(*test_data), L"Global\\winetest_driver_section"); + ok(!!test_data_mapping, "got error %u\n", GetLastError()); + test_data = MapViewOfFile(test_data_mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 1024); + test_data->running_under_wine = !strcmp(winetest_platform, "wine"); + test_data->winetest_report_success = winetest_report_success; + test_data->winetest_debug = winetest_debug; + + okfile = CreateFileW(L"C:\\windows\\winetest_driver_okfile", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); + ok(okfile != INVALID_HANDLE_VALUE, "failed to create file, error %u\n", GetLastError()); + + return TRUE; +} + +void winetest_driver_cleanup(struct winetest_driver_context *ctx) +{ + testsign_cleanup(ctx); + UnmapViewOfFile(test_data); + CloseHandle(test_data_mapping); + CloseHandle(okfile); + DeleteFileW(L"C:\\windows\\winetest_driver_okfile"); +} diff --git a/include/wine/test_driver.h b/include/wine/test_driver.h new file mode 100644 index 00000000000..b832c572533 --- /dev/null +++ b/include/wine/test_driver.h @@ -0,0 +1,492 @@ +/* + * Wine testing framework - Driver test helpers + * + * Copyright 2015 Sebastian Lackner + * Copyright 2015 Michael Müller + * Copyright 2015 Christian Costa + * Copyright 2020 Paul Gofman for CodeWeavers + * Copyright 2020-2021 Zebediah Figura for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __WINE_WINE_TEST_DRIVER_H +#define __WINE_WINE_TEST_DRIVER_H + +#include +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include +#include +#include + +#include + +/* kernel/user shared data */ +struct winetest_shared_data +{ + int running_under_wine; + int winetest_report_success; + int winetest_debug; + int failures; + int todo_failures; +}; + +#ifdef __WINE_WINE_TEST_H + +/* user-space test API */ +struct winetest_driver_context +{ + HCRYPTPROV provider; + const CERT_CONTEXT *cert; + const CERT_CONTEXT *root_cert; + const CERT_CONTEXT *publisher_cert; + HCERTSTORE root_store; + HCERTSTORE publisher_store; + + WCHAR cwd[MAX_PATH]; + HDEVINFO set; + SP_DEVINFO_DATA device; +}; + +#define winetest_driver_init(a) (subtest("winetest_driver"), winetest_driver_init_(a)) +extern BOOL winetest_driver_init_( struct winetest_driver_context *ctx ); +extern void winetest_driver_cleanup( struct winetest_driver_context *ctx ); +extern BOOL winetest_driver_start( struct winetest_driver_context *ctx, const WCHAR *resource ); +extern void winetest_driver_stop( struct winetest_driver_context *ctx ); +extern void winetest_driver_check_failures( void ); + +#else + +#if !defined(__WINE_USE_MSVCRT) || defined(__MINGW32__) +#define __WINE_PRINTF_ATTR(fmt,args) __attribute__((format (printf,fmt,args))) +#else +#define __WINE_PRINTF_ATTR(fmt,args) +#endif + +static HANDLE okfile; +static LONG successes; +static LONG failures; +static LONG skipped; +static LONG todo_successes; +static LONG todo_failures; +static LONG muted_traces; +static LONG muted_skipped; +static LONG muted_todo_successes; + +static int running_under_wine; +static int winetest_debug; +static int winetest_report_success; + +/* silence todos and skips above this threshold */ +static int winetest_mute_threshold = 42; + +/* counts how many times a given line printed a message */ +static LONG line_counters[16384]; + +/* The following data must be kept track of on a per-thread basis */ +struct tls_data +{ + HANDLE thread; + const char* current_file; /* file of current check */ + int current_line; /* line of current check */ + unsigned int todo_level; /* current todo nesting level */ + int todo_do_loop; + char *str_pos; /* position in debug buffer */ + char strings[2000]; /* buffer for debug strings */ + char context[8][128]; /* data to print before messages */ + unsigned int context_count; /* number of context prefixes */ +}; + +static KSPIN_LOCK tls_data_lock; +static struct tls_data tls_data_pool[128]; +static DWORD tls_data_count; + +static inline struct tls_data *get_tls_data(void) +{ + static struct tls_data tls_overflow; + struct tls_data *data; + HANDLE thread = PsGetCurrentThreadId(); + KIRQL irql; + + KeAcquireSpinLock(&tls_data_lock, &irql); + for (data = tls_data_pool; data != tls_data_pool + tls_data_count; ++data) + if (data->thread == thread) break; + if (data == tls_data_pool + ARRAY_SIZE(tls_data_pool)) + data = &tls_overflow; + else if (data == tls_data_pool + tls_data_count) + { + data->thread = thread; + data->str_pos = data->strings; + tls_data_count++; + } + KeReleaseSpinLock(&tls_data_lock, irql); + + return data; +} + +static inline void winetest_set_location( const char* file, int line ) +{ + struct tls_data *data = get_tls_data(); + data->current_file=strrchr(file,'/'); + if (data->current_file==NULL) + data->current_file=strrchr(file,'\\'); + if (data->current_file==NULL) + data->current_file=file; + else + data->current_file++; + data->current_line=line; +} + +static inline void kvprintf(const char *format, __ms_va_list ap) +{ + struct tls_data *data = get_tls_data(); + IO_STATUS_BLOCK io; + int len = vsnprintf(data->strings, sizeof(data->strings), format, ap); + ZwWriteFile(okfile, NULL, NULL, NULL, &io, data->strings, len, NULL, NULL); +} + +static inline void WINAPIV kprintf(const char *format, ...) __WINE_PRINTF_ATTR(1,2); +static inline void WINAPIV kprintf(const char *format, ...) +{ + __ms_va_list valist; + + __ms_va_start(valist, format); + kvprintf(format, valist); + __ms_va_end(valist); +} + +static inline void WINAPIV winetest_printf( const char *msg, ... ) __WINE_PRINTF_ATTR(1,2); +static inline void WINAPIV winetest_printf( const char *msg, ... ) +{ + struct tls_data *data = get_tls_data(); + __ms_va_list valist; + + kprintf( "%s:%d: ", data->current_file, data->current_line ); + __ms_va_start( valist, msg ); + kvprintf( msg, valist ); + __ms_va_end( valist ); +} + +static inline NTSTATUS winetest_init(void) +{ + const struct winetest_shared_data *data; + SIZE_T size = sizeof(*data); + OBJECT_ATTRIBUTES attr; + UNICODE_STRING string; + IO_STATUS_BLOCK io; + void *addr = NULL; + HANDLE section; + NTSTATUS ret; + + KeInitializeSpinLock(&tls_data_lock); + + RtlInitUnicodeString(&string, L"\\BaseNamedObjects\\winetest_driver_section"); + /* OBJ_KERNEL_HANDLE is necessary for the file to be accessible from system threads */ + InitializeObjectAttributes(&attr, &string, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, 0, NULL); + if ((ret = ZwOpenSection(§ion, SECTION_MAP_READ, &attr))) + return ret; + + if ((ret = ZwMapViewOfSection(section, NtCurrentProcess(), &addr, + 0, 0, NULL, &size, ViewUnmap, 0, PAGE_READONLY))) + { + ZwClose(section); + return ret; + } + data = addr; + running_under_wine = data->running_under_wine; + winetest_debug = data->winetest_debug; + winetest_report_success = data->winetest_report_success; + + ZwUnmapViewOfSection(NtCurrentProcess(), addr); + ZwClose(section); + + RtlInitUnicodeString(&string, L"\\??\\C:\\windows\\winetest_driver_okfile"); + return ZwOpenFile(&okfile, FILE_APPEND_DATA | SYNCHRONIZE, &attr, &io, + FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_NONALERT); +} + +#define winetest_cleanup() winetest_cleanup_(__FILE__) +static inline void winetest_cleanup_(const char *file) +{ + char test_name[MAX_PATH], *tmp; + struct winetest_shared_data *data; + SIZE_T size = sizeof(*data); + const char *source_file; + OBJECT_ATTRIBUTES attr; + UNICODE_STRING string; + void *addr = NULL; + HANDLE section; + + source_file = strrchr(file, '/'); + if (!source_file) source_file = strrchr(file, '\\'); + if (!source_file) source_file = file; + else source_file++; + + strcpy(test_name, source_file); + if ((tmp = strrchr(test_name, '.'))) *tmp = 0; + + if (winetest_debug) + { + kprintf("%04x:%s: %d tests executed (%d marked as todo, %d %s), %d skipped.\n", + (DWORD)(DWORD_PTR)PsGetCurrentProcessId(), test_name, + successes + failures + todo_successes + todo_failures, + todo_successes, failures + todo_failures, + (failures + todo_failures != 1) ? "failures" : "failure", skipped ); + } + + RtlInitUnicodeString(&string, L"\\BaseNamedObjects\\winetest_driver_section"); + /* OBJ_KERNEL_HANDLE is necessary for the file to be accessible from system threads */ + InitializeObjectAttributes(&attr, &string, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, 0, NULL); + + if (!ZwOpenSection(§ion, SECTION_MAP_READ | SECTION_MAP_WRITE, &attr)) + { + if (!ZwMapViewOfSection(section, NtCurrentProcess(), &addr, + 0, 0, NULL, &size, ViewUnmap, 0, PAGE_READWRITE)) + { + data = addr; + + InterlockedExchangeAdd(&data->failures, failures); + InterlockedExchangeAdd(&data->todo_failures, todo_failures); + + ZwUnmapViewOfSection(NtCurrentProcess(), addr); + } + ZwClose(section); + } + + ZwClose(okfile); +} + +static inline void winetest_print_context( const char *msgtype ) +{ + struct tls_data *data = get_tls_data(); + unsigned int i; + + winetest_printf( "%s", msgtype ); + for (i = 0; i < data->context_count; ++i) + kprintf( "%s: ", data->context[i] ); +} + +static inline LONG winetest_add_line( void ) +{ + struct tls_data *data; + int index, count; + + if (winetest_debug > 1) + return 0; + + data = get_tls_data(); + index = data->current_line % ARRAY_SIZE(line_counters); + count = InterlockedIncrement(line_counters + index) - 1; + if (count == winetest_mute_threshold) + winetest_printf( "Line has been silenced after %d occurrences\n", winetest_mute_threshold ); + + return count; +} + +static inline int winetest_vok( int condition, const char *msg, __ms_va_list args ) +{ + struct tls_data *data = get_tls_data(); + + if (data->todo_level) + { + if (condition) + { + winetest_print_context( "Test succeeded inside todo block: " ); + kvprintf(msg, args); + InterlockedIncrement(&todo_failures); + return 0; + } + else + { + if (!winetest_debug || + winetest_add_line() < winetest_mute_threshold) + { + if (winetest_debug > 0) + { + winetest_print_context( "Test marked todo: " ); + kvprintf(msg, args); + } + InterlockedIncrement(&todo_successes); + } + else + InterlockedIncrement(&muted_todo_successes); + return 1; + } + } + else + { + if (!condition) + { + winetest_print_context( "Test failed: " ); + kvprintf(msg, args); + InterlockedIncrement(&failures); + return 0; + } + else + { + if (winetest_report_success) + winetest_printf("Test succeeded\n"); + InterlockedIncrement(&successes); + return 1; + } + } +} + +static inline void WINAPIV winetest_ok( int condition, const char *msg, ... ) __WINE_PRINTF_ATTR(2,3); +static inline void WINAPIV winetest_ok( int condition, const char *msg, ... ) +{ + __ms_va_list args; + __ms_va_start(args, msg); + winetest_vok(condition, msg, args); + __ms_va_end(args); +} + +static inline void winetest_vskip( const char *msg, __ms_va_list args ) +{ + if (winetest_add_line() < winetest_mute_threshold) + { + winetest_print_context( "Tests skipped: " ); + kvprintf(msg, args); + InterlockedIncrement(&skipped); + } + else + InterlockedIncrement(&muted_skipped); +} + +static inline void WINAPIV winetest_skip( const char *msg, ... ) __WINE_PRINTF_ATTR(1,2); +static inline void WINAPIV winetest_skip( const char *msg, ... ) +{ + __ms_va_list args; + __ms_va_start(args, msg); + winetest_vskip(msg, args); + __ms_va_end(args); +} + +static inline void WINAPIV winetest_win_skip( const char *msg, ... ) __WINE_PRINTF_ATTR(1,2); +static inline void WINAPIV winetest_win_skip( const char *msg, ... ) +{ + __ms_va_list args; + __ms_va_start(args, msg); + if (running_under_wine) + winetest_vskip(msg, args); + else + winetest_vok(0, msg, args); + __ms_va_end(args); +} + +static inline void WINAPIV winetest_trace( const char *msg, ... ) __WINE_PRINTF_ATTR(1,2); +static inline void WINAPIV winetest_trace( const char *msg, ... ) +{ + __ms_va_list args; + + if (!winetest_debug) + return; + if (winetest_add_line() < winetest_mute_threshold) + { + winetest_print_context( "" ); + __ms_va_start(args, msg); + kvprintf( msg, args ); + __ms_va_end(args); + } + else + InterlockedIncrement(&muted_traces); +} + +static inline void winetest_start_todo( int is_todo ) +{ + struct tls_data *data = get_tls_data(); + data->todo_level = (data->todo_level << 1) | (is_todo != 0); + data->todo_do_loop=1; +} + +static inline int winetest_loop_todo(void) +{ + struct tls_data *data = get_tls_data(); + int do_loop=data->todo_do_loop; + data->todo_do_loop=0; + return do_loop; +} + +static inline void winetest_end_todo(void) +{ + struct tls_data *data = get_tls_data(); + data->todo_level >>= 1; +} + +static inline void WINAPIV winetest_push_context( const char *fmt, ... ) __WINE_PRINTF_ATTR(1, 2); +static inline void WINAPIV winetest_push_context( const char *fmt, ... ) +{ + struct tls_data *data = get_tls_data(); + __ms_va_list valist; + + if (data->context_count < ARRAY_SIZE(data->context)) + { + __ms_va_start(valist, fmt); + vsnprintf( data->context[data->context_count], sizeof(data->context[data->context_count]), fmt, valist ); + __ms_va_end(valist); + data->context[data->context_count][sizeof(data->context[data->context_count]) - 1] = 0; + } + ++data->context_count; +} + +static inline void winetest_pop_context(void) +{ + struct tls_data *data = get_tls_data(); + + if (data->context_count) + --data->context_count; +} + +static inline int broken(int condition) +{ + return !running_under_wine && condition; +} + +#ifdef WINETEST_NO_LINE_NUMBERS +# define subtest_(file, line) (winetest_set_location(file, 0), 0) ? (void)0 : winetest_subtest +# define ignore_exceptions_(file, line) (winetest_set_location(file, 0), 0) ? (void)0 : winetest_ignore_exceptions +# define ok_(file, line) (winetest_set_location(file, 0), 0) ? (void)0 : winetest_ok +# define skip_(file, line) (winetest_set_location(file, 0), 0) ? (void)0 : winetest_skip +# define win_skip_(file, line) (winetest_set_location(file, 0), 0) ? (void)0 : winetest_win_skip +# define trace_(file, line) (winetest_set_location(file, 0), 0) ? (void)0 : winetest_trace +# define wait_child_process_(file, line) (winetest_set_location(file, 0), 0) ? (void)0 : winetest_wait_child_process +#else +# define subtest_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_subtest +# define ignore_exceptions_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_ignore_exceptions +# define ok_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_ok +# define skip_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_skip +# define win_skip_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_win_skip +# define trace_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_trace +# define wait_child_process_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_wait_child_process +#endif + +#define ok ok_(__FILE__, __LINE__) +#define skip skip_(__FILE__, __LINE__) +#define trace trace_(__FILE__, __LINE__) +#define win_skip win_skip_(__FILE__, __LINE__) + +#define todo_if(is_todo) for (winetest_start_todo(is_todo); \ + winetest_loop_todo(); \ + winetest_end_todo()) +#define todo_wine todo_if(running_under_wine) +#define todo_wine_if(is_todo) todo_if((is_todo) && running_under_wine) + +#endif /* __WINE_WINE_TEST_H */ + +#endif /* __WINE_WINE_TEST_DRIVER_H */ -- 2.33.0