From: Andrey Turkin Subject: [1/2] Try2: imagehlp: Add tests for ImageGetDigestStream Message-Id: <201007181438.48191.andrey.turkin@gmail.com> Date: Sun, 18 Jul 2010 14:38:48 +0400 The patch is based on patch of Juan Lang (http://www.winehq.org/pipermail/wine-patches/2008-August/059572.html); big blob is replaced with structure and there are some small changes. try2: don't use nameless unions in the test --- dlls/imagehlp/tests/Makefile.in | 1 + dlls/imagehlp/tests/image.c | 356 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 357 insertions(+), 0 deletions(-) create mode 100644 dlls/imagehlp/tests/image.c diff --git a/dlls/imagehlp/tests/Makefile.in b/dlls/imagehlp/tests/Makefile.in index 3e17a0d..3b09c3f 100644 --- a/dlls/imagehlp/tests/Makefile.in +++ b/dlls/imagehlp/tests/Makefile.in @@ -6,6 +6,7 @@ TESTDLL = imagehlp.dll IMPORTS = kernel32 C_SRCS = \ + image.c \ integrity.c @MAKE_TEST_RULES@ diff --git a/dlls/imagehlp/tests/image.c b/dlls/imagehlp/tests/image.c new file mode 100644 index 0000000..2024c0b --- /dev/null +++ b/dlls/imagehlp/tests/image.c @@ -0,0 +1,356 @@ +/* + * Copyright 2008 Juan Lang + * Copyright 2010 Andrey Turkin + * + * 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 + +#define NONAMELESSUNION +#include +#include +#include +#include +#include + +#include "wine/test.h" + +static HMODULE hImageHlp; + +static BOOL (WINAPI *pImageGetDigestStream)(HANDLE, DWORD, DIGEST_FUNCTION, DIGEST_HANDLE); + +/* minimal PE file image */ +#define VA_START 0x400000 +#define FILE_PE_START 0x50 +#define NUM_SECTIONS 3 +#define FILE_TEXT 0x200 +#define RVA_TEXT 0x1000 +#define RVA_BSS 0x2000 +#define FILE_IDATA 0x400 +#define RVA_IDATA 0x3000 +#define FILE_TOTAL 0x600 +#define RVA_TOTAL 0x4000 +#include +struct Imports { + IMAGE_IMPORT_DESCRIPTOR descriptors[2]; + IMAGE_THUNK_DATA32 original_thunks[2]; + IMAGE_THUNK_DATA32 thunks[2]; + struct __IMPORT_BY_NAME { + WORD hint; + char funcname[0x20]; + } ibn; + char dllname[0x10]; +}; +#define EXIT_PROCESS (VA_START+RVA_IDATA+FIELD_OFFSET(struct Imports, thunks[0])) + +static struct _PeImage { + IMAGE_DOS_HEADER dos_header; + char __alignment1[FILE_PE_START - sizeof(IMAGE_DOS_HEADER)]; + IMAGE_NT_HEADERS32 nt_headers; + IMAGE_SECTION_HEADER sections[NUM_SECTIONS]; + char __alignment2[FILE_TEXT - FILE_PE_START - sizeof(IMAGE_NT_HEADERS32) - + NUM_SECTIONS * sizeof(IMAGE_SECTION_HEADER)]; + unsigned char text_section[FILE_IDATA-FILE_TEXT]; + struct Imports idata_section; + char __alignment3[FILE_TOTAL-FILE_IDATA-sizeof(struct Imports)]; +} bin = { + /* dos header */ + {IMAGE_DOS_SIGNATURE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {}, 0, 0, {}, FILE_PE_START}, + /* alignment before PE header */ + {}, + /* nt headers */ + {IMAGE_NT_SIGNATURE, + /* basic headers - 3 sections, no symbols, EXE file */ + {IMAGE_FILE_MACHINE_I386, NUM_SECTIONS, 0, 0, 0, sizeof(IMAGE_OPTIONAL_HEADER32), + IMAGE_FILE_32BIT_MACHINE | IMAGE_FILE_EXECUTABLE_IMAGE}, + /* optional header */ + {IMAGE_NT_OPTIONAL_HDR32_MAGIC, 4, 0, FILE_IDATA-FILE_TEXT, + FILE_TOTAL-FILE_IDATA + FILE_IDATA-FILE_TEXT, 0x400, + RVA_TEXT, RVA_TEXT, RVA_BSS, VA_START, 0x1000, 0x200, 4, 0, 1, 0, 4, 0, 0, + RVA_TOTAL, FILE_TEXT, 0, IMAGE_SUBSYSTEM_WINDOWS_GUI, 0, + 0x200000, 0x1000, 0x100000, 0x1000, 0, 0x10, + {{0, 0}, + {RVA_IDATA, sizeof(struct Imports)} + } + } + }, + /* sections */ + { + {".text", {0x100}, RVA_TEXT, FILE_IDATA-FILE_TEXT, FILE_TEXT, + 0, 0, 0, 0, IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ}, + {".bss", {0x400}, RVA_BSS, 0, 0, 0, 0, 0, 0, + IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE}, + {".idata", {sizeof(struct Imports)}, RVA_IDATA, FILE_TOTAL-FILE_IDATA, FILE_IDATA, 0, + 0, 0, 0, IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE} + }, + /* alignment before first section */ + {}, + /* .text section */ + { + 0x31, 0xC0, /* xor eax, eax */ + 0xFF, 0x25, EXIT_PROCESS&0xFF, (EXIT_PROCESS>>8)&0xFF, (EXIT_PROCESS>>16)&0xFF, + (EXIT_PROCESS>>24)&0xFF, /* jmp ExitProcess */ + 0 + }, + /* .idata section */ + { + { + {{RVA_IDATA + FIELD_OFFSET(struct Imports, original_thunks)}, 0, 0, + RVA_IDATA + FIELD_OFFSET(struct Imports, dllname), + RVA_IDATA + FIELD_OFFSET(struct Imports, thunks) + }, + {{0}, 0, 0, 0, 0} + }, + {{{RVA_IDATA+FIELD_OFFSET(struct Imports, ibn)}}, {{0}}}, + {{{RVA_IDATA+FIELD_OFFSET(struct Imports, ibn)}}, {{0}}}, + {0,"ExitProcess"}, + "KERNEL32.DLL" + }, + /* final alignment */ + {} +}; +#include + +struct blob +{ + DWORD cb; + BYTE *pb; +}; + +struct expected_blob +{ + DWORD cb; + const void *pb; +}; + +struct update_accum +{ + DWORD cUpdates; + struct blob *updates; +}; + +struct expected_update_accum +{ + DWORD cUpdates; + const struct expected_blob *updates; +}; + +static BOOL WINAPI accumulating_stream_output(DIGEST_HANDLE handle, BYTE *pb, + DWORD cb) +{ + struct update_accum *accum = (struct update_accum *)handle; + BOOL ret = FALSE; + + if (accum->cUpdates) + accum->updates = HeapReAlloc(GetProcessHeap(), 0, accum->updates, + (accum->cUpdates + 1) * sizeof(struct blob)); + else + accum->updates = HeapAlloc(GetProcessHeap(), 0, sizeof(struct blob)); + if (accum->updates) + { + struct blob *blob = &accum->updates[accum->cUpdates]; + + blob->pb = HeapAlloc(GetProcessHeap(), 0, cb); + if (blob->pb) + { + memcpy(blob->pb, pb, cb); + blob->cb = cb; + ret = TRUE; + } + accum->cUpdates++; + } + return ret; +} + +static void check_updates(LPCSTR header, const struct expected_update_accum *expected, + const struct update_accum *got) +{ + DWORD i; + + ok(expected->cUpdates == got->cUpdates, "%s: expected %d updates, got %d\n", + header, expected->cUpdates, got->cUpdates); + for (i = 0; i < min(expected->cUpdates, got->cUpdates); i++) + { + ok(expected->updates[i].cb == got->updates[i].cb, "%s, update %d: expected %d bytes, got %d\n", + header, i, expected->updates[i].cb, got->updates[i].cb); + if (expected->updates[i].cb && expected->updates[i].cb == got->updates[i].cb) + ok(!memcmp(expected->updates[i].pb, got->updates[i].pb, got->updates[i].cb), + "%s, update %d: unexpected value\n", header, i); + } +} + +/* Frees the updates stored in accum */ +static void free_updates(struct update_accum *accum) +{ + DWORD i; + + for (i = 0; i < accum->cUpdates; i++) + HeapFree(GetProcessHeap(), 0, accum->updates[i].pb); + HeapFree(GetProcessHeap(), 0, accum->updates); + accum->updates = NULL; + accum->cUpdates = 0; +} + +static const struct expected_blob b1[] = { + {FILE_PE_START, &bin}, + /* with zeroed Checksum/SizeOfInitializedData/SizeOfImage fields */ + {sizeof(bin.nt_headers), &bin.nt_headers}, + {sizeof(bin.sections), &bin.sections}, + {FILE_IDATA-FILE_TEXT, &bin.text_section}, + {sizeof(bin.idata_section.descriptors[0].u.OriginalFirstThunk), + &bin.idata_section.descriptors[0].u.OriginalFirstThunk}, + {FIELD_OFFSET(struct Imports, thunks)-FIELD_OFFSET(struct Imports, descriptors[0].Name), + &bin.idata_section.descriptors[0].Name}, + {FILE_TOTAL-FILE_IDATA-FIELD_OFFSET(struct Imports, ibn), + &bin.idata_section.ibn} +}; +static const struct expected_update_accum a1 = { sizeof(b1) / sizeof(b1[0]), b1 }; + +static const struct expected_blob b2[] = { + {FILE_PE_START, &bin}, + /* with zeroed Checksum/SizeOfInitializedData/SizeOfImage fields */ + {sizeof(bin.nt_headers), &bin.nt_headers}, + {sizeof(bin.sections), &bin.sections}, + {FILE_IDATA-FILE_TEXT, &bin.text_section}, + {FILE_TOTAL-FILE_IDATA, &bin.idata_section} +}; +static const struct expected_update_accum a2 = { sizeof(b2) / sizeof(b2[0]), b2 }; + +/* Creates a test file and returns a handle to it. The file's path is returned + * in temp_file, which must be at least MAX_PATH characters in length. + */ +static HANDLE create_temp_file(char *temp_file) +{ + HANDLE file = INVALID_HANDLE_VALUE; + char temp_path[MAX_PATH]; + + if (GetTempPathA(sizeof(temp_path), temp_path)) + { + if (GetTempFileNameA(temp_path, "img", 0, temp_file)) + file = CreateFileA(temp_file, GENERIC_READ | GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + } + return file; +} + +static void update_checksum(void) +{ + WORD const * ptr; + DWORD size; + DWORD sum = 0; + + bin.nt_headers.OptionalHeader.CheckSum = 0; + + for(ptr = (WORD const *)&bin, size = (sizeof(bin)+1)/sizeof(WORD); size > 0; ptr++, size--) + { + sum += *ptr; + if (HIWORD(sum) != 0) + { + sum = LOWORD(sum) + HIWORD(sum); + } + } + sum = (WORD)(LOWORD(sum) + HIWORD(sum)); + sum += sizeof(bin); + + bin.nt_headers.OptionalHeader.CheckSum = sum; +} + +static void test_get_digest_stream(void) +{ + BOOL ret; + HANDLE file; + char temp_file[MAX_PATH]; + DWORD count; + struct update_accum accum = { 0, NULL }; + + SetLastError(0xdeadbeef); + ret = pImageGetDigestStream(NULL, 0, NULL, NULL); + todo_wine + ok(!ret && GetLastError() == ERROR_INVALID_PARAMETER, + "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError()); + file = create_temp_file(temp_file); + if (file == INVALID_HANDLE_VALUE) + { + skip("couldn't create temp file\n"); + return; + } + SetLastError(0xdeadbeef); + ret = pImageGetDigestStream(file, 0, NULL, NULL); + todo_wine + ok(!ret && GetLastError() == ERROR_INVALID_PARAMETER, + "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError()); + SetLastError(0xdeadbeef); + ret = pImageGetDigestStream(NULL, 0, accumulating_stream_output, &accum); + todo_wine + ok(!ret && GetLastError() == ERROR_INVALID_PARAMETER, + "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError()); + /* Even with "valid" parameters, it fails with an empty file */ + SetLastError(0xdeadbeef); + ret = pImageGetDigestStream(file, 0, accumulating_stream_output, &accum); + todo_wine + ok(!ret && GetLastError() == ERROR_INVALID_PARAMETER, + "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError()); + /* Finally, with a valid executable in the file, it succeeds. Note that + * the file pointer need not be positioned at the beginning. + */ + update_checksum(); + WriteFile(file, &bin, sizeof(bin), &count, NULL); + FlushFileBuffers(file); + + /* zero out some fields ImageGetDigestStream would zero out */ + bin.nt_headers.OptionalHeader.CheckSum = 0; + bin.nt_headers.OptionalHeader.SizeOfInitializedData = 0; + bin.nt_headers.OptionalHeader.SizeOfImage = 0; + + ret = pImageGetDigestStream(file, 0, accumulating_stream_output, &accum); + todo_wine + ok(ret, "ImageGetDigestStream failed: %d\n", GetLastError()); + todo_wine + check_updates("flags = 0", &a1, &accum); + free_updates(&accum); + ret = pImageGetDigestStream(file, CERT_PE_IMAGE_DIGEST_ALL_IMPORT_INFO, + accumulating_stream_output, &accum); + todo_wine + ok(ret, "ImageGetDigestStream failed: %d\n", GetLastError()); + todo_wine + check_updates("flags = CERT_PE_IMAGE_DIGEST_ALL_IMPORT_INFO", &a2, &accum); + free_updates(&accum); + CloseHandle(file); + DeleteFileA(temp_file); +} + +START_TEST(image) +{ + hImageHlp = LoadLibraryA("imagehlp.dll"); + + if (!hImageHlp) + { + win_skip("ImageHlp unavailable\n"); + return; + } + + pImageGetDigestStream = (void *) GetProcAddress(hImageHlp, "ImageGetDigestStream"); + + if (!pImageGetDigestStream) + { + win_skip("ImageGetDigestStream function is not available\n"); + } else + { + test_get_digest_stream(); + } + + FreeLibrary(hImageHlp); +}