From: Rose Hellsing <rose@pinkro.se> Adds a regression test to verify the validity of the changes done in the ntdll and ntoskrnl.exe loader to allow for read-only cookies. --- dlls/kernel32/tests/loader.c | 133 +++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/dlls/kernel32/tests/loader.c b/dlls/kernel32/tests/loader.c index 5bb32a5fd5b..8c0a4e3de06 100644 --- a/dlls/kernel32/tests/loader.c +++ b/dlls/kernel32/tests/loader.c @@ -2117,6 +2117,138 @@ static void test_section_access(void) } } +static void test_security_cookie_readonly(void) +{ + /* a PE whose load-config SecurityCookie points into a + * read-only section (e.g. .rdata) must still load successfully. The + * loader is expected to temporarily make the page writable, initialize + * the cookie and restore the original protection. */ +#ifdef _WIN64 + static const ULONG_PTR default_cookie = (((ULONG_PTR)0x00002b99 << 32) | 0x2ddfa232); + static const WORD reloc_type = IMAGE_REL_BASED_DIR64; +#else + static const ULONG_PTR default_cookie = 0xbb40e64e; + static const WORD reloc_type = IMAGE_REL_BASED_HIGHLOW; +#endif + IMAGE_NT_HEADERS nt_header; + IMAGE_SECTION_HEADER sections[2]; + BYTE rdata[0x200]; + BYTE reloc[0x200]; + IMAGE_LOAD_CONFIG_DIRECTORY *cfg = (IMAGE_LOAD_CONFIG_DIRECTORY *)rdata; + IMAGE_BASE_RELOCATION *base_reloc = (IMAGE_BASE_RELOCATION *)reloc; + WORD *reloc_entries = (WORD *)(reloc + sizeof(*base_reloc)); + const DWORD cookie_offset = 0x100; /* inside .rdata, away from cfg */ + ULONG_PTR *cookie_slot = (ULONG_PTR *)(rdata + cookie_offset); + char temp_path[MAX_PATH], dll_name[MAX_PATH]; + HANDLE hfile; + DWORD dummy; + BOOL ret; + HMODULE hlib; + MEMORY_BASIC_INFORMATION info; + SIZE_T size; + ULONG_PTR final_cookie; + + memset(rdata, 0, sizeof(rdata)); + memset(reloc, 0, sizeof(reloc)); + + nt_header = nt_header_template; + nt_header.FileHeader.NumberOfSections = 2; + nt_header.OptionalHeader.SectionAlignment = page_size; + nt_header.OptionalHeader.FileAlignment = 0x200; + nt_header.OptionalHeader.SizeOfImage = page_size * 3; + nt_header.OptionalHeader.SizeOfHeaders = nt_header.OptionalHeader.FileAlignment; + + memset(sections, 0, sizeof(sections)); + + /* .rdata: holds the load config and the cookie slot, READ only */ + memcpy(sections[0].Name, ".rdata", 7); + sections[0].Misc.VirtualSize = sizeof(rdata); + sections[0].VirtualAddress = page_size; + sections[0].SizeOfRawData = sizeof(rdata); + sections[0].PointerToRawData = nt_header.OptionalHeader.FileAlignment; + sections[0].Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ; + + /* .reloc: fixes up cfg->SecurityCookie when the loader relocates the DLL */ + memcpy(sections[1].Name, ".reloc", 7); + sections[1].Misc.VirtualSize = sizeof(reloc); + sections[1].VirtualAddress = page_size * 2; + sections[1].SizeOfRawData = sizeof(reloc); + sections[1].PointerToRawData = sections[0].PointerToRawData + sections[0].SizeOfRawData; + sections[1].Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA + | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE; + + /* place the cookie inside .rdata so the loader decides to write it */ + *cookie_slot = default_cookie; + + /* fill in the load-config directory at the start of .rdata */ + cfg->Size = sizeof(*cfg); + cfg->SecurityCookie = (ULONG_PTR)nt_header.OptionalHeader.ImageBase + + sections[0].VirtualAddress + cookie_offset; + + /* one relocation block covering the page that holds cfg->SecurityCookie, + * with a single entry pointing at the SecurityCookie field plus a zero + * terminator entry (WORD-aligned). */ + base_reloc->VirtualAddress = sections[0].VirtualAddress; + base_reloc->SizeOfBlock = sizeof(*base_reloc) + 2 * sizeof(WORD); + reloc_entries[0] = (reloc_type << 12) + | (offsetof(IMAGE_LOAD_CONFIG_DIRECTORY, SecurityCookie) & 0xfff); + reloc_entries[1] = 0; + + nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress = sections[0].VirtualAddress; + nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size = sizeof(*cfg); + nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = sections[1].VirtualAddress; + nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size = base_reloc->SizeOfBlock; + + /* hand-roll the DLL file: create_test_dll_sections only supports a single + * shared data blob, but we need distinct payloads for .rdata and .reloc. */ + GetTempPathA(MAX_PATH, temp_path); + GetTempFileNameA(temp_path, "ldr", 0, dll_name); + hfile = CreateFileA(dll_name, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, 0); + ok(hfile != INVALID_HANDLE_VALUE, "failed to create %s err %lu\n", dll_name, GetLastError()); + if (hfile == INVALID_HANDLE_VALUE) return; + + ret = WriteFile(hfile, &dos_header, sizeof(dos_header), &dummy, NULL); + ok(ret, "WriteFile error %lu\n", GetLastError()); + ret = WriteFile(hfile, &nt_header, + offsetof(IMAGE_NT_HEADERS, OptionalHeader) + nt_header.FileHeader.SizeOfOptionalHeader, + &dummy, NULL); + ok(ret, "WriteFile error %lu\n", GetLastError()); + ret = WriteFile(hfile, sections, sizeof(sections), &dummy, NULL); + ok(ret, "WriteFile error %lu\n", GetLastError()); + + SetFilePointer(hfile, sections[0].PointerToRawData, NULL, FILE_BEGIN); + ret = WriteFile(hfile, rdata, sizeof(rdata), &dummy, NULL); + ok(ret, "WriteFile error %lu\n", GetLastError()); + + SetFilePointer(hfile, sections[1].PointerToRawData, NULL, FILE_BEGIN); + ret = WriteFile(hfile, reloc, sizeof(reloc), &dummy, NULL); + ok(ret, "WriteFile error %lu\n", GetLastError()); + CloseHandle(hfile); + + SetLastError(0xdeadbeef); + hlib = LoadLibraryA(dll_name); + ok(hlib != NULL, "LoadLibrary failed err %lu\n", GetLastError()); + if (!hlib) + { + DeleteFileA(dll_name); + return; + } + + final_cookie = *(ULONG_PTR *)((char *)hlib + sections[0].VirtualAddress + cookie_offset); + ok(final_cookie != default_cookie, + "security cookie was not initialized (still %#Ix)\n", final_cookie); + + /* page protection should be restored to PAGE_READONLY after cookie init */ + size = VirtualQuery((char *)hlib + sections[0].VirtualAddress, &info, sizeof(info)); + ok(size == sizeof(info), "VirtualQuery error %lu\n", GetLastError()); + ok(info.Protect == PAGE_READONLY, + "section protection not restored: got %#lx, expected PAGE_READONLY\n", + info.Protect); + + FreeLibrary(hlib); + DeleteFileA(dll_name); +} + static void check_tls_index(HANDLE dll, BOOL tls_initialized) { BOOL found_dll = FALSE; @@ -4913,6 +5045,7 @@ START_TEST(loader) test_ResolveDelayLoadedAPI(); test_ImportDescriptors(); test_section_access(); + test_security_cookie_readonly(); test_import_resolution(); test_export_forwarder_dep_chain(); test_ExitProcess(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11001