From: Las@protonmail.ch Subject: (Resend) [PATCH] Fix handling of zero_bits argument in NtAllocateVirtualMemory Message-Id: Date: Fri, 15 Feb 2019 14:32:57 +0000 Patch attached. This is my first contribution to wine, and I have checked through the wiki for what I should do, and have attempted to fulfill all criteria. Commit message from patch: Before, Wine would (very incorrectly) use the argument to specify how many *low-order* address bits should be 0, when in fact, in Windows it's used to specify how many *high-order* address bits should be zero! The functionality is taken from the documentation for NtAllocateVirtualMemory: The number of high-order address bits that must be zero in the base address of the section view. Used only when the operating system determines where to allocate the region, as when BaseAddress is NULL. Note that when ZeroBits is larger than 32, it becomes a bitmask. and NtMapViewOfSection: Specifies the number of high-order address bits that must be zero in the base address of the section view. The value of this parameter must be less than 21 and is used only if BaseAddress is NULL—in other words, when the caller allows the system to determine where to allocate the view. and from documentation for LuaJIT's allocator: /* Number of top bits of the lower 32 bits of an address that must be zero. ** Apparently 0 gives us full 64 bit addresses and 1 gives us the lower 2GB. */ \#define NTAVM_ZEROBITS 1 Thus the interpretation done here is: If zero_bits is 0, use the full address space. If zero_bits is over 20, err (12-bit pointers are the smallest allowed). Otherwise only the lower (32-zero_bits) bits should be used. A lot of internal Wine functionality unfortunately depends on the old Wine behavior, but thankfully, all of the uses seem to be redundant, as no function requested an alignment higher than the minimum! It may however be that I have not fully understood the code, as it is not always clear what alignment is requested. In addition, to implement this odd Windows API, Wine somehow has to mmap an address below the maximum, which is not possible efficiently on POSIX systems compared to the Windows API. Linux has the MAP_32BIT flag, but it restricts the address space to just 1 GiB, which is not enough. For that reason, the implementation of this API in Wine uses pseudorandom heuristics ripped from the LuaJIT project (which also seems to be the only project to use this Windows API) to achieve this. It is not optimal, but it works. A proper implementation would require extensions to the mmap API.
Patch attached. This is my first contribution to wine, and I have checked through the wiki for what I should do, and have attempted to fulfill all criteria.

Commit message from patch:

Before, Wine would (very incorrectly) use the argument to specify
how many *low-order* address bits should be 0, when in fact,
in Windows it's used to specify how many *high-order* address bits
should be zero!

The functionality is taken from the documentation for NtAllocateVirtualMemory:
The number of high-order address bits that must be zero in the base address
of the section view. Used only when the operating system determines where
to allocate the region, as when BaseAddress is NULL. Note that when
ZeroBits is larger than 32, it becomes a bitmask.

and NtMapViewOfSection:
Specifies the number of high-order address bits that must be zero in the
base address of the section view. The value of this parameter must be less
than 21 and is used only if BaseAddress is NULL—in other words, when the
caller allows the system to determine where to allocate the view.

and from documentation for LuaJIT's allocator:
/* Number of top bits of the lower 32 bits of an address that must be zero.
** Apparently 0 gives us full 64 bit addresses and 1 gives us the lower 2GB.
*/
\#define NTAVM_ZEROBITS 1

Thus the interpretation done here is:
If zero_bits is 0, use the full address space.
If zero_bits is over 20, err (12-bit pointers are the smallest allowed).
Otherwise only the lower (32-zero_bits) bits should be used.

A lot of internal Wine functionality unfortunately depends on the old
Wine behavior, but thankfully, all of the uses seem to be redundant,
as no function requested an alignment higher than the minimum!
It may however be that I have not fully understood the code,
as it is not always clear what alignment is requested.

In addition, to implement this odd Windows API, Wine somehow has to
mmap an address below the maximum, which is not possible efficiently
on POSIX systems compared to the Windows API. Linux has the MAP_32BIT
flag, but it restricts the address space to just 1 GiB, which is not
enough. For that reason, the implementation of this API in Wine
uses pseudorandom heuristics ripped from the LuaJIT project (which
also seems to be the only project to use this Windows API) to achieve
this. It is not optimal, but it works. A proper implementation would
require extensions to the mmap API.
From 41d8a123f95538da6b7b73e09ef44c37b995916a Mon Sep 17 00:00:00 2001 From: Las Date: Thu, 14 Feb 2019 18:27:47 +0100 Subject: [PATCH] Fix handling of zero_bits argument in NtAllocateVirtualMemory and friends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, Wine would (very incorrectly) use the argument to specify how many *low-order* address bits should be 0, when in fact, in Windows it's used to specify how many *high-order* address bits should be zero! The functionality is taken from the documentation for NtAllocateVirtualMemory: The number of high-order address bits that must be zero in the base address of the section view. Used only when the operating system determines where to allocate the region, as when BaseAddress is NULL. Note that when ZeroBits is larger than 32, it becomes a bitmask. and NtMapViewOfSection: Specifies the number of high-order address bits that must be zero in the base address of the section view. The value of this parameter must be less than 21 and is used only if BaseAddress is NULL—in other words, when the caller allows the system to determine where to allocate the view. and from documentation for LuaJIT's allocator: /* Number of top bits of the lower 32 bits of an address that must be zero. ** Apparently 0 gives us full 64 bit addresses and 1 gives us the lower 2GB. */ \#define NTAVM_ZEROBITS 1 Thus the interpretation done here is: If zero_bits is 0, use the full address space. If zero_bits is over 20, err (12-bit pointers are the smallest allowed). Otherwise only the lower (32-zero_bits) bits should be used. A lot of internal Wine functionality unfortunately depends on the old Wine behavior, but thankfully, all of the uses seem to be redundant, as no function requested an alignment higher than the minimum! It may however be that I have not fully understood the code, as it is not always clear what alignment is requested. In addition, to implement this odd Windows API, Wine somehow has to mmap an address below the maximum, which is not possible efficiently on POSIX systems compared to the Windows API. Linux has the MAP_32BIT flag, but it restricts the address space to just 1 GiB, which is not enough. For that reason, the implementation of this API in Wine uses pseudorandom heuristics ripped from the LuaJIT project (which also seems to be the only project to use this Windows API) to achieve this. It is not optimal, but it works. A proper implementation would require extensions to the mmap API. --- dlls/commdlg.dll16/filedlg.c | 2 +- dlls/kernel32/tests/virtual.c | 24 +++---- dlls/ntdll/directory.c | 4 +- dlls/ntdll/heap.c | 4 +- dlls/ntdll/signal_arm.c | 2 +- dlls/ntdll/signal_arm64.c | 2 +- dlls/ntdll/signal_i386.c | 2 +- dlls/ntdll/signal_powerpc.c | 2 +- dlls/ntdll/signal_x86_64.c | 2 +- dlls/ntdll/thread.c | 2 +- dlls/ntdll/virtual.c | 57 ++++++++--------- include/wine/library.h | 1 + libs/wine/mmap.c | 115 +++++++++++++++++++++++++++++++++- libs/wine/wine.map | 1 + 14 files changed, 162 insertions(+), 58 deletions(-) diff --git a/dlls/commdlg.dll16/filedlg.c b/dlls/commdlg.dll16/filedlg.c index 99da47a229..6194b30038 100644 --- a/dlls/commdlg.dll16/filedlg.c +++ b/dlls/commdlg.dll16/filedlg.c @@ -511,7 +511,7 @@ static LPOFNHOOKPROC alloc_hook( LPOFNHOOKPROC16 hook16 ) SIZE_T size = 0x1000; unsigned int i; - if (!hooks && NtAllocateVirtualMemory( GetCurrentProcess(), (void **)&hooks, 12, &size, + if (!hooks && NtAllocateVirtualMemory( GetCurrentProcess(), (void **)&hooks, 0, &size, MEM_COMMIT, PAGE_EXECUTE_READWRITE )) return NULL; diff --git a/dlls/kernel32/tests/virtual.c b/dlls/kernel32/tests/virtual.c index 78bc17a09c..491cce701e 100644 --- a/dlls/kernel32/tests/virtual.c +++ b/dlls/kernel32/tests/virtual.c @@ -447,28 +447,24 @@ static void test_VirtualAlloc(void) MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); ok(status == STATUS_CONFLICTING_ADDRESSES, "NtAllocateVirtualMemory returned %08x\n", status); - /* it should conflict, even when zero_bits is explicitly set */ + /* allocate memory with 31-bit address */ size = 0x1000; - addr2 = (char *)addr1 + 0x1000; - status = pNtAllocateVirtualMemory(GetCurrentProcess(), &addr2, 12, &size, + addr2 = NULL; + status = pNtAllocateVirtualMemory(GetCurrentProcess(), &addr2, 1, &size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); - todo_wine - ok(status == STATUS_CONFLICTING_ADDRESSES, "NtAllocateVirtualMemory returned %08x\n", status); - if (status == STATUS_SUCCESS) ok(VirtualFree(addr2, 0, MEM_RELEASE), "VirtualFree failed\n"); + ok(status == STATUS_SUCCESS && (UINT_PTR)addr2 >> 24 == 0, "NtAllocateVirtualMemory returned status %08x and pointer %p\n", status, addr2); - /* 21 zero bits is valid */ - size = 0x1000; + /* allocate memory with 12-bit address (WTF?) */ + size = 0x10; /* needs to be smaller than the address space of course */ addr2 = NULL; - status = pNtAllocateVirtualMemory(GetCurrentProcess(), &addr2, 21, &size, + status = pNtAllocateVirtualMemory(GetCurrentProcess(), &addr2, 20, &size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); - ok(status == STATUS_SUCCESS || status == STATUS_NO_MEMORY, - "NtAllocateVirtualMemory returned %08x\n", status); - if (status == STATUS_SUCCESS) ok(VirtualFree(addr2, 0, MEM_RELEASE), "VirtualFree failed\n"); + ok(status == STATUS_SUCCESS && (UINT_PTR)addr2 >> 12 == 0, "NtAllocateVirtualMemory returned status %08x and pointer %p\n", status, addr2); - /* 22 zero bits is invalid */ + /* attempt to allocate memory with 11-bit address */ size = 0x1000; addr2 = NULL; - status = pNtAllocateVirtualMemory(GetCurrentProcess(), &addr2, 22, &size, + status = pNtAllocateVirtualMemory(GetCurrentProcess(), &addr2, 21, &size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); ok(status == STATUS_INVALID_PARAMETER_3, "NtAllocateVirtualMemory returned %08x\n", status); if (status == STATUS_SUCCESS) ok(VirtualFree(addr2, 0, MEM_RELEASE), "VirtualFree failed\n"); diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c index b46c2a6736..a415f73842 100644 --- a/dlls/ntdll/directory.c +++ b/dlls/ntdll/directory.c @@ -1603,14 +1603,14 @@ static KERNEL_DIRENT *start_vfat_ioctl( int fd ) SIZE_T size = 2 * sizeof(*de) + page_size; void *addr = NULL; - if (NtAllocateVirtualMemory( GetCurrentProcess(), &addr, 1, &size, MEM_RESERVE, PAGE_READWRITE )) + if (NtAllocateVirtualMemory( GetCurrentProcess(), &addr, 0, &size, MEM_RESERVE, PAGE_READWRITE )) return NULL; /* commit only the size needed for the dir entries */ /* this leaves an extra unaccessible page, which should make the kernel */ /* fail with -EFAULT before it stomps all over our memory */ de = addr; size = 2 * sizeof(*de); - NtAllocateVirtualMemory( GetCurrentProcess(), &addr, 1, &size, MEM_COMMIT, PAGE_READWRITE ); + NtAllocateVirtualMemory( GetCurrentProcess(), &addr, 0, &size, MEM_COMMIT, PAGE_READWRITE ); } /* set d_reclen to 65535 to work around an AFS kernel bug */ diff --git a/dlls/ntdll/heap.c b/dlls/ntdll/heap.c index bdadc7932d..a81c943287 100644 --- a/dlls/ntdll/heap.c +++ b/dlls/ntdll/heap.c @@ -726,7 +726,7 @@ static void *allocate_large_block( HEAP *heap, DWORD flags, SIZE_T size ) LPVOID address = NULL; if (block_size < size) return NULL; /* overflow */ - if (NtAllocateVirtualMemory( NtCurrentProcess(), &address, 5, + if (NtAllocateVirtualMemory( NtCurrentProcess(), &address, 0, &block_size, MEM_COMMIT, get_protection_type( flags ) )) { WARN("Could not allocate block for %08lx bytes\n", size ); @@ -1521,7 +1521,7 @@ void heap_set_debug_flags( HANDLE handle ) void *ptr = NULL; SIZE_T size = MAX_FREE_PENDING * sizeof(*heap->pending_free); - if (!NtAllocateVirtualMemory( NtCurrentProcess(), &ptr, 4, &size, MEM_COMMIT, PAGE_READWRITE )) + if (!NtAllocateVirtualMemory( NtCurrentProcess(), &ptr, 0, &size, MEM_COMMIT, PAGE_READWRITE )) { heap->pending_free = ptr; heap->pending_pos = 0; diff --git a/dlls/ntdll/signal_arm.c b/dlls/ntdll/signal_arm.c index e22480878d..b2d9f5d0c3 100644 --- a/dlls/ntdll/signal_arm.c +++ b/dlls/ntdll/signal_arm.c @@ -966,7 +966,7 @@ NTSTATUS signal_alloc_thread( TEB **teb ) size = 1 << sigstack_zero_bits; *teb = NULL; - if (!(status = NtAllocateVirtualMemory( NtCurrentProcess(), (void **)teb, sigstack_zero_bits, + if (!(status = NtAllocateVirtualMemory( NtCurrentProcess(), (void **)teb, 0, &size, MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE ))) { (*teb)->Tib.Self = &(*teb)->Tib; diff --git a/dlls/ntdll/signal_arm64.c b/dlls/ntdll/signal_arm64.c index c063be5c85..15f94ed9d9 100644 --- a/dlls/ntdll/signal_arm64.c +++ b/dlls/ntdll/signal_arm64.c @@ -887,7 +887,7 @@ NTSTATUS signal_alloc_thread( TEB **teb ) size = 1 << sigstack_zero_bits; *teb = NULL; - if (!(status = NtAllocateVirtualMemory( NtCurrentProcess(), (void **)teb, sigstack_zero_bits, + if (!(status = NtAllocateVirtualMemory( NtCurrentProcess(), (void **)teb, 0, &size, MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE ))) { (*teb)->Tib.Self = &(*teb)->Tib; diff --git a/dlls/ntdll/signal_i386.c b/dlls/ntdll/signal_i386.c index 91ecfa7e5d..11456371f5 100644 --- a/dlls/ntdll/signal_i386.c +++ b/dlls/ntdll/signal_i386.c @@ -2284,7 +2284,7 @@ NTSTATUS signal_alloc_thread( TEB **teb ) } size = signal_stack_mask + 1; - if (!(status = NtAllocateVirtualMemory( NtCurrentProcess(), &addr, sigstack_zero_bits, + if (!(status = NtAllocateVirtualMemory( NtCurrentProcess(), &addr, 0, &size, MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE ))) { *teb = addr; diff --git a/dlls/ntdll/signal_powerpc.c b/dlls/ntdll/signal_powerpc.c index 1c96d62a87..d93c43458b 100644 --- a/dlls/ntdll/signal_powerpc.c +++ b/dlls/ntdll/signal_powerpc.c @@ -1032,7 +1032,7 @@ NTSTATUS signal_alloc_thread( TEB **teb ) size = 1 << sigstack_zero_bits; *teb = NULL; - if (!(status = NtAllocateVirtualMemory( NtCurrentProcess(), (void **)teb, sigstack_zero_bits, + if (!(status = NtAllocateVirtualMemory( NtCurrentProcess(), (void **)teb, 0, &size, MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE ))) { (*teb)->Tib.Self = &(*teb)->Tib; diff --git a/dlls/ntdll/signal_x86_64.c b/dlls/ntdll/signal_x86_64.c index c3cc3d8b5e..99506311dc 100644 --- a/dlls/ntdll/signal_x86_64.c +++ b/dlls/ntdll/signal_x86_64.c @@ -3276,7 +3276,7 @@ NTSTATUS signal_alloc_thread( TEB **teb ) size = 1 << sigstack_zero_bits; *teb = NULL; - if (!(status = NtAllocateVirtualMemory( NtCurrentProcess(), (void **)teb, sigstack_zero_bits, + if (!(status = NtAllocateVirtualMemory( NtCurrentProcess(), (void **)teb, 0, &size, MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE ))) { (*teb)->Tib.Self = &(*teb)->Tib; diff --git a/dlls/ntdll/thread.c b/dlls/ntdll/thread.c index 9f4a08fdb9..cb1e3d5b5d 100644 --- a/dlls/ntdll/thread.c +++ b/dlls/ntdll/thread.c @@ -185,7 +185,7 @@ void thread_init(void) addr = NULL; size = sizeof(*peb); - NtAllocateVirtualMemory( NtCurrentProcess(), &addr, 1, &size, + NtAllocateVirtualMemory( NtCurrentProcess(), &addr, 0, &size, MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE ); peb = addr; diff --git a/dlls/ntdll/virtual.c b/dlls/ntdll/virtual.c index c008db7806..51feb9983c 100644 --- a/dlls/ntdll/virtual.c +++ b/dlls/ntdll/virtual.c @@ -406,18 +406,6 @@ static struct file_view *VIRTUAL_FindView( const void *addr, size_t size ) } -/*********************************************************************** - * get_mask - */ -static inline UINT_PTR get_mask( ULONG zero_bits ) -{ - if (!zero_bits) return 0xffff; /* allocations are aligned to 64K by default */ - if (zero_bits < page_shift) zero_bits = page_shift; - if (zero_bits > 21) return 0; - return (1 << zero_bits) - 1; -} - - /*********************************************************************** * is_write_watch_range */ @@ -1083,7 +1071,7 @@ static NTSTATUS map_fixed_area( void *base, size_t size, unsigned int vprot ) * The csVirtual section must be held by caller. */ static NTSTATUS map_view( struct file_view **view_ret, void *base, size_t size, size_t mask, - int top_down, unsigned int vprot ) + int top_down, unsigned int vprot, ULONG zero_bits ) { void *ptr; NTSTATUS status; @@ -1104,7 +1092,7 @@ static NTSTATUS map_view( struct file_view **view_ret, void *base, size_t size, alloc.size = size; alloc.mask = mask; alloc.top_down = top_down; - alloc.limit = user_space_limit; + alloc.limit = zero_bits ? (void *)min((UINT_PTR)user_space_limit, (UINT_PTR)1 << (32 - zero_bits)) : user_space_limit; if (wine_mmap_enum_reserved_areas( alloc_reserved_area_callback, &alloc, top_down )) { ptr = alloc.result; @@ -1116,7 +1104,8 @@ static NTSTATUS map_view( struct file_view **view_ret, void *base, size_t size, for (;;) { - if ((ptr = wine_anon_mmap( NULL, view_size, VIRTUAL_GetUnixProt(vprot), 0 )) == (void *)-1) + if ((ptr = wine_anon_mmap_restricted( NULL, view_size, VIRTUAL_GetUnixProt(vprot), 0, + (32 - zero_bits) % 32)) == (void *)-1) { if (errno == ENOMEM) return STATUS_NO_MEMORY; return STATUS_INVALID_PARAMETER; @@ -1284,7 +1273,7 @@ static NTSTATUS allocate_dos_memory( struct file_view **view, unsigned int vprot if (addr != low_64k) { if (addr != (void *)-1) munmap( addr, dosmem_size - 0x10000 ); - return map_view( view, NULL, dosmem_size, 0xffff, 0, vprot ); + return map_view( view, NULL, dosmem_size, 0xffff, 0, vprot, 0 ); } } @@ -1358,7 +1347,8 @@ static NTSTATUS map_pe_header( void *ptr, size_t size, int fd, BOOL *removable ) * Map an executable (PE format) image into memory. */ static NTSTATUS map_image( HANDLE hmapping, ACCESS_MASK access, int fd, SIZE_T mask, - pe_image_info_t *image_info, int shared_fd, BOOL removable, PVOID *addr_ptr ) + pe_image_info_t *image_info, int shared_fd, BOOL removable, PVOID *addr_ptr, + ULONG zero_bits ) { IMAGE_DOS_HEADER *dos; IMAGE_NT_HEADERS *nt; @@ -1388,11 +1378,11 @@ static NTSTATUS map_image( HANDLE hmapping, ACCESS_MASK access, int fd, SIZE_T m if (base >= (char *)address_space_start) /* make sure the DOS area remains free */ status = map_view( &view, base, total_size, mask, FALSE, SEC_IMAGE | SEC_FILE | - VPROT_COMMITTED | VPROT_READ | VPROT_EXEC | VPROT_WRITECOPY ); + VPROT_COMMITTED | VPROT_READ | VPROT_EXEC | VPROT_WRITECOPY, zero_bits ); if (status != STATUS_SUCCESS) status = map_view( &view, NULL, total_size, mask, FALSE, SEC_IMAGE | SEC_FILE | - VPROT_COMMITTED | VPROT_READ | VPROT_EXEC | VPROT_WRITECOPY ); + VPROT_COMMITTED | VPROT_READ | VPROT_EXEC | VPROT_WRITECOPY, zero_bits ); if (status != STATUS_SUCCESS) goto error; @@ -1614,7 +1604,8 @@ NTSTATUS virtual_map_section( HANDLE handle, PVOID *addr_ptr, ULONG zero_bits, S NTSTATUS res; mem_size_t full_size; ACCESS_MASK access; - SIZE_T size, mask = get_mask( zero_bits ); + SIZE_T size; + SIZE_T mask = 0xffff; int unix_handle = -1, needs_close; unsigned int vprot, sec_flags; struct file_view *view; @@ -1670,13 +1661,13 @@ NTSTATUS virtual_map_section( HANDLE handle, PVOID *addr_ptr, ULONG zero_bits, S if ((res = server_get_unix_fd( shared_file, FILE_READ_DATA|FILE_WRITE_DATA, &shared_fd, &shared_needs_close, NULL, NULL ))) goto done; res = map_image( handle, access, unix_handle, mask, image_info, - shared_fd, needs_close, addr_ptr ); + shared_fd, needs_close, addr_ptr, zero_bits ); if (shared_needs_close) close( shared_fd ); close_handle( shared_file ); } else { - res = map_image( handle, access, unix_handle, mask, image_info, -1, needs_close, addr_ptr ); + res = map_image( handle, access, unix_handle, mask, image_info, -1, needs_close, addr_ptr, zero_bits ); } if (needs_close) close( unix_handle ); if (res >= 0) *size_ptr = image_info->map_size; @@ -1713,7 +1704,7 @@ NTSTATUS virtual_map_section( HANDLE handle, PVOID *addr_ptr, ULONG zero_bits, S get_vprot_flags( protect, &vprot, sec_flags & SEC_IMAGE ); vprot |= sec_flags; if (!(sec_flags & SEC_RESERVE)) vprot |= VPROT_COMMITTED; - res = map_view( &view, *addr_ptr, size, mask, FALSE, vprot ); + res = map_view( &view, *addr_ptr, size, mask, FALSE, vprot, zero_bits ); if (res) { server_leave_uninterrupted_section( &csVirtual, &sigset ); @@ -1870,7 +1861,7 @@ void virtual_get_system_info( SYSTEM_BASIC_INFORMATION *info ) } #endif info->MmNumberOfPhysicalPages = info->MmHighestPhysicalPage - info->MmLowestPhysicalPage; - info->AllocationGranularity = get_mask(0) + 1; + info->AllocationGranularity = 0x10000; info->LowestUserAddress = (void *)0x10000; info->HighestUserAddress = (char *)user_space_limit - 1; info->ActiveProcessorsAffinityMask = get_system_affinity_mask(); @@ -1946,7 +1937,7 @@ NTSTATUS virtual_alloc_thread_stack( TEB *teb, SIZE_T reserve_size, SIZE_T commi server_enter_uninterrupted_section( &csVirtual, &sigset ); if ((status = map_view( &view, NULL, size + extra_size, 0xffff, 0, - VPROT_READ | VPROT_WRITE | VPROT_COMMITTED )) != STATUS_SUCCESS) + VPROT_READ | VPROT_WRITE | VPROT_COMMITTED, 0 )) != STATUS_SUCCESS) goto done; #ifdef VALGRIND_STACK_REGISTER @@ -2464,16 +2455,16 @@ NTSTATUS WINAPI NtAllocateVirtualMemory( HANDLE process, PVOID *ret, ULONG zero_ void *base; unsigned int vprot; SIZE_T size = *size_ptr; - SIZE_T mask = get_mask( zero_bits ); + SIZE_T mask = 0xffff; NTSTATUS status = STATUS_SUCCESS; BOOL is_dos_memory = FALSE; struct file_view *view; sigset_t sigset; - TRACE("%p %p %08lx %x %08x\n", process, *ret, size, type, protect ); + TRACE("%p %p %i %08lx %x %08x\n", process, *ret, zero_bits, size, type, protect ); if (!size) return STATUS_INVALID_PARAMETER; - if (!mask) return STATUS_INVALID_PARAMETER_3; + if (zero_bits > 20) return STATUS_INVALID_PARAMETER_3; if (process != NtCurrentProcess()) { @@ -2505,6 +2496,9 @@ NTSTATUS WINAPI NtAllocateVirtualMemory( HANDLE process, PVOID *ret, ULONG zero_ if (*ret) { + /* disallow if an address is specified manually */ + if (zero_bits) return STATUS_INVALID_PARAMETER_3; + if (type & MEM_RESERVE) /* Round down to 64k boundary */ base = ROUND_ADDR( *ret, mask ); else @@ -2550,7 +2544,7 @@ NTSTATUS WINAPI NtAllocateVirtualMemory( HANDLE process, PVOID *ret, ULONG zero_ if (vprot & VPROT_WRITECOPY) status = STATUS_INVALID_PAGE_PROTECTION; else if (is_dos_memory) status = allocate_dos_memory( &view, vprot ); - else status = map_view( &view, base, size, mask, type & MEM_TOP_DOWN, vprot ); + else status = map_view( &view, base, size, mask, type & MEM_TOP_DOWN, vprot, zero_bits ); if (status == STATUS_SUCCESS) base = view->base; } @@ -2590,6 +2584,7 @@ NTSTATUS WINAPI NtAllocateVirtualMemory( HANDLE process, PVOID *ret, ULONG zero_ } + /*********************************************************************** * NtFreeVirtualMemory (NTDLL.@) * ZwFreeVirtualMemory (NTDLL.@) @@ -3076,7 +3071,7 @@ NTSTATUS WINAPI NtMapViewOfSection( HANDLE handle, HANDLE process, PVOID *addr_p SECTION_INHERIT inherit, ULONG alloc_type, ULONG protect ) { NTSTATUS res; - SIZE_T mask = get_mask( zero_bits ); + SIZE_T mask = 0xffff; pe_image_info_t image_info; LARGE_INTEGER offset; @@ -3087,7 +3082,7 @@ NTSTATUS WINAPI NtMapViewOfSection( HANDLE handle, HANDLE process, PVOID *addr_p /* Check parameters */ - if ((*addr_ptr && zero_bits) || !mask) + if ((*addr_ptr && zero_bits) || zero_bits > 20) return STATUS_INVALID_PARAMETER_4; #ifndef _WIN64 diff --git a/include/wine/library.h b/include/wine/library.h index 242bb69f17..fb652d18e7 100644 --- a/include/wine/library.h +++ b/include/wine/library.h @@ -76,6 +76,7 @@ extern int wine_call_on_stack( int (*func)(void *), void *arg, void *stack ); /* memory mappings */ extern void *wine_anon_mmap( void *start, size_t size, int prot, int flags ); +extern void *wine_anon_mmap_restricted( void *start, size_t size, int prot, int flags, int bits ); extern void wine_mmap_add_reserved_area( void *addr, size_t size ); extern void wine_mmap_remove_reserved_area( void *addr, size_t size, int unmap ); extern int wine_mmap_is_in_reserved_area( void *addr, size_t size ); diff --git a/libs/wine/mmap.c b/libs/wine/mmap.c index 458cc132b5..e554d57ef6 100644 --- a/libs/wine/mmap.c +++ b/libs/wine/mmap.c @@ -188,13 +188,109 @@ static int try_mmap_fixed (void *addr, size_t len, int prot, int flags, #endif /* (__svr4__ || __NetBSD__) && !MAP_TRYFIXED */ +/* + * The functions `mmap_probe_seed` and `mmap_probe` were ripped + * out from LuaJIT, and are subject to the following license: + * + * Link to source: https://github.com/LuaJIT/LuaJIT/blob/f0e865dd4861520258299d0f2a56491bd9d602e1/src/lj_alloc.c#L248 + * + * =============================================================================== + * LuaJIT -- a Just-In-Time Compiler for Lua. http://luajit.org/ + * + * Copyright (C) 2005-2017 Mike Pall. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * [ MIT license: http://www.opensource.org/licenses/mit-license.php ] + * =============================================================================== + */ + +static uintptr_t mmap_probe_seed(void) +{ + uintptr_t val; + int fd = open( "/dev/urandom", O_RDONLY ); + if (fd != -1) { + int ok = ((size_t)read( fd, &val, sizeof(val)) == sizeof(val) ); + (void)close( fd ); + if (ok) return val; + } + return 1; /* Punt. */ +} + +static void *mmap_probe( size_t size, int prot, int flags, int bits ) +{ +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) + /* Even FreeBSD 5.3 does not properly support NULL here. */ + const uintptr_t hint_def = 0x110000; +#else + const uintptr_t hint_def = 0; +#endif + /* Hint for next allocation. Doesn't need to be thread-safe. */ + static uintptr_t hint_addr = hint_def; + static uintptr_t hint_prng = 0; + int retry; + for (retry = 0; retry < 30; retry++) { + void *p = mmap( (void *)hint_addr, size, prot, flags, get_fdzero(), 0 ); + uintptr_t addr = (uintptr_t)p; + if ((addr >> bits) == 0) { + /* We got a suitable address. Bump the hint address. */ + hint_addr = addr + size; + return p; + } + if (p != MAP_FAILED) { + munmap( p, size ); + } else if (errno == ENOMEM) { + return MAP_FAILED; + } + if (hint_addr != hint_def) { + /* First, try linear probing. */ + if (retry < 5) { + hint_addr += 0x1000000; + if (((hint_addr + size) >> bits) != 0) hint_addr = hint_def; + continue; + } else if (retry == 5) { + /* Next, try a no-hint probe to get back an ASLR address. */ + hint_addr = hint_def; + continue; + } + } + /* Finally, try pseudo-random probing. */ + if (hint_prng == 0) { + hint_prng = mmap_probe_seed(); + } + /* The unsuitable address we got has some ASLR PRNG bits. */ + hint_addr ^= addr; + /* The PRNG itself is very weak, but see above. */ + hint_prng = hint_prng * 1103515245 + 12345; + hint_addr ^= hint_prng * (uintptr_t)0x1000; + hint_addr &= (((uintptr_t)1 << bits)-1); + } + errno = ENOMEM; // Out of address space + return MAP_FAILED; +} /*********************************************************************** - * wine_anon_mmap + * wine_anon_mmap_restricted * * Portable wrapper for anonymous mmaps */ -void *wine_anon_mmap( void *start, size_t size, int prot, int flags ) +void *wine_anon_mmap_restricted( void *start, size_t size, int prot, int flags, int bits ) { #ifdef MAP_SHARED flags &= ~MAP_SHARED; @@ -203,6 +299,12 @@ void *wine_anon_mmap( void *start, size_t size, int prot, int flags ) /* Linux EINVAL's on us if we don't pass MAP_PRIVATE to an anon mmap */ flags |= MAP_PRIVATE | MAP_ANON; + /* Restrict the bits in the pointer to the amount specified using pseudorandom heuristics */ + if (bits) { + if (start) return MAP_FAILED; + return mmap_probe(size, prot, flags, bits); + } + if (!(flags & MAP_FIXED)) { #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) @@ -221,6 +323,15 @@ void *wine_anon_mmap( void *start, size_t size, int prot, int flags ) return mmap( start, size, prot, flags, get_fdzero(), 0 ); } +/*********************************************************************** + * wine_anon_mmap + * + * Legacy wrapper for wine_anon_mmap + */ +void *wine_anon_mmap( void *start, size_t size, int prot, int flags ) +{ + return wine_anon_mmap_restricted(start, size, prot, flags, 0); +} /*********************************************************************** * mmap_reserve diff --git a/libs/wine/wine.map b/libs/wine/wine.map index 2159fac852..ef86aae2b6 100644 --- a/libs/wine/wine.map +++ b/libs/wine/wine.map @@ -50,6 +50,7 @@ WINE_1.0 vsnprintfW; vsprintfW; wine_anon_mmap; + wine_anon_mmap_restricted; wine_call_on_stack; wine_casemap_lower; wine_casemap_upper; -- 2.19.2