Code is only used for non-page-aligned device drivers. Previously if relocation was happening on page boundary it crashed because only one page had changed protection in load_driver_module. Now code is copied from ntdll and changes protection for all module pages before actual relocation starts. Fixes running MTA: San Andreas game.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=28254 Signed-off-by: Rafał Harabień rafalh92@outlook.com --- dlls/ntoskrnl.exe/ntoskrnl.c | 102 +++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 22 deletions(-)
diff --git a/dlls/ntoskrnl.exe/ntoskrnl.c b/dlls/ntoskrnl.exe/ntoskrnl.c index ec78f4bc46..e15a75a810 100644 --- a/dlls/ntoskrnl.exe/ntoskrnl.c +++ b/dlls/ntoskrnl.exe/ntoskrnl.c @@ -25,6 +25,7 @@ #include "wine/port.h"
#include <stdarg.h> +#include <assert.h>
#define NONAMELESSUNION #define NONAMELESSSTRUCT @@ -3224,6 +3225,84 @@ static LDR_MODULE *find_ldr_module( HMODULE module ) return ldr; }
+/* convert PE image VirtualAddress to Real Address */ +static inline void *get_rva( HMODULE module, DWORD va ) +{ + return (void *)((char *)module + va); +} + +/* Copied from ntdll with checks for page alignment and characteristics removed */ +static NTSTATUS perform_relocations( void *module, SIZE_T len ) +{ + IMAGE_NT_HEADERS *nt; + char *base; + IMAGE_BASE_RELOCATION *rel, *end; + const IMAGE_DATA_DIRECTORY *relocs; + const IMAGE_SECTION_HEADER *sec; + INT_PTR delta; + ULONG protect_old[96], i; + + nt = RtlImageNtHeader( module ); + base = (char *)nt->OptionalHeader.ImageBase; + + assert( module != base ); + + relocs = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; + + if (nt->FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED) + { + WARN( "Need to relocate module from %p to %p, but there are no relocation records\n", + base, module ); + return STATUS_CONFLICTING_ADDRESSES; + } + + if (!relocs->Size) return STATUS_SUCCESS; + if (!relocs->VirtualAddress) return STATUS_CONFLICTING_ADDRESSES; + + if (nt->FileHeader.NumberOfSections > ARRAY_SIZE( protect_old )) + return STATUS_INVALID_IMAGE_FORMAT; + + sec = (const IMAGE_SECTION_HEADER *)((const char *)&nt->OptionalHeader + + nt->FileHeader.SizeOfOptionalHeader); + for (i = 0; i < nt->FileHeader.NumberOfSections; i++) + { + void *addr = get_rva( module, sec[i].VirtualAddress ); + SIZE_T size = sec[i].SizeOfRawData; + NtProtectVirtualMemory( NtCurrentProcess(), &addr, + &size, PAGE_READWRITE, &protect_old[i] ); + } + + TRACE( "relocating from %p-%p to %p-%p\n", + base, base + len, module, (char *)module + len ); + + rel = get_rva( module, relocs->VirtualAddress ); + end = get_rva( module, relocs->VirtualAddress + relocs->Size ); + delta = (char *)module - base; + + while (rel < end - 1 && rel->SizeOfBlock) + { + if (rel->VirtualAddress >= len) + { + WARN( "invalid address %p in relocation %p\n", get_rva( module, rel->VirtualAddress ), rel ); + return STATUS_ACCESS_VIOLATION; + } + rel = LdrProcessRelocationBlock( get_rva( module, rel->VirtualAddress ), + (rel->SizeOfBlock - sizeof(*rel)) / sizeof(USHORT), + (USHORT *)(rel + 1), delta ); + if (!rel) return STATUS_INVALID_IMAGE_FORMAT; + } + + for (i = 0; i < nt->FileHeader.NumberOfSections; i++) + { + void *addr = get_rva( module, sec[i].VirtualAddress ); + SIZE_T size = sec[i].SizeOfRawData; + NtProtectVirtualMemory( NtCurrentProcess(), &addr, + &size, protect_old[i], &protect_old[i] ); + } + + return STATUS_SUCCESS; +} + /* load the driver module file */ static HMODULE load_driver_module( const WCHAR *name ) { @@ -3247,28 +3326,7 @@ static HMODULE load_driver_module( const WCHAR *name ) if (nt->OptionalHeader.SectionAlignment < info.PageSize || !(nt->FileHeader.Characteristics & IMAGE_FILE_DLL)) { - DWORD old; - IMAGE_BASE_RELOCATION *rel, *end; - - if ((rel = RtlImageDirectoryEntryToData( module, TRUE, IMAGE_DIRECTORY_ENTRY_BASERELOC, &size ))) - { - TRACE( "%s: relocating from %p to %p\n", wine_dbgstr_w(name), (char *)module - delta, module ); - end = (IMAGE_BASE_RELOCATION *)((char *)rel + size); - while (rel < end && rel->SizeOfBlock) - { - void *page = (char *)module + rel->VirtualAddress; - VirtualProtect( page, info.PageSize, PAGE_EXECUTE_READWRITE, &old ); - rel = LdrProcessRelocationBlock( page, (rel->SizeOfBlock - sizeof(*rel)) / sizeof(USHORT), - (USHORT *)(rel + 1), delta ); - if (old != PAGE_EXECUTE_READWRITE) VirtualProtect( page, info.PageSize, old, &old ); - if (!rel) goto error; - } - /* make sure we don't try again */ - size = FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) + nt->FileHeader.SizeOfOptionalHeader; - VirtualProtect( nt, size, PAGE_READWRITE, &old ); - nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = 0; - VirtualProtect( nt, size, old, &old ); - } + perform_relocations(module, nt->OptionalHeader.SizeOfImage); }
/* make sure imports are relocated too */
Rafał H rafalh92@outlook.com writes:
/* make sure we don't try again */
size = FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) + nt->FileHeader.SizeOfOptionalHeader;
VirtualProtect( nt, size, PAGE_READWRITE, &old );
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = 0;
VirtualProtect( nt, size, old, &old );
You most likely need to keep that part.
You are right. Thanks for such a detailed review. I fixed it in v2 patch. I changed zeroed field from VirtualAddress to Size to make perform_relocations return STATUS_SUCCESS next time.
W dniu 05.02.2019 o 11:04, Alexandre Julliard pisze:
Rafał H rafalh92@outlook.com writes:
/* make sure we don't try again */
size = FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) + nt->FileHeader.SizeOfOptionalHeader;
VirtualProtect( nt, size, PAGE_READWRITE, &old );
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = 0;
VirtualProtect( nt, size, old, &old );
You most likely need to keep that part.