From 711a1e68d554255866d54ee672bc003bd11865da Mon Sep 17 00:00:00 2001 From: Utkarsh Dalal Date: Fri, 1 May 2026 00:32:40 +0530 Subject: [PATCH 1/2] added mprotect fallback for api 35 --- dlls/ntdll/unix/virtual.c | 67 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c index 963ebd279e2b..e0fd72233d5b 100644 --- a/dlls/ntdll/unix/virtual.c +++ b/dlls/ntdll/unix/virtual.c @@ -2042,10 +2042,65 @@ static NTSTATUS get_vprot_flags( DWORD protect, unsigned int *vprot, BOOL image } +/*********************************************************************** + * try_rebake_anonymous_exec + * + * Strict-W^X kernels (notably Android / arm64 Bionic) reject + * mprotect(PROT_EXEC) on a file-backed MAP_PRIVATE region that was + * ever writable, even if the range is now read-only. Wine's PE loader + * hits this constantly when flipping freshly-relocated text sections + * from RW to RX. Replace the affected range with an anonymous private + * mapping holding identical contents -- anonymous mappings are not + * subject to the W^X restriction -- then apply the requested + * protection. + * + * Returns 0 on success with the range now anonymously mapped at + * `unix_prot`. Returns -1 (without touching the original mapping + * beyond a possible PROT_READ downgrade) on any failure, in which + * case the caller should treat it as the original mprotect failure. + */ +static int try_rebake_anonymous_exec( void *base, size_t size, int unix_prot ) +{ + void *stage; + + /* PROT_READ alone never trips W^X and lets us memcpy the source + * contents out before we replace the mapping. */ + if (mprotect( base, size, PROT_READ ) != 0) return -1; + + if ((stage = anon_mmap_alloc( size, PROT_READ | PROT_WRITE )) == MAP_FAILED) + return -1; + memcpy( stage, base, size ); + + /* MAP_FIXED + MAP_ANONYMOUS replaces whatever VMA(s) cover + * [base, base+size) with a fresh anonymous private mapping. + * Surrounding portions of any larger original VMA stay intact via + * the kernel's automatic VMA splits. */ + if (anon_mmap_fixed( base, size, PROT_READ | PROT_WRITE, 0 ) != base) + { + munmap( stage, size ); + return -1; + } + + memcpy( base, stage, size ); + munmap( stage, size ); + + if (mprotect( base, size, unix_prot ) != 0) return -1; + +#ifdef __aarch64__ + if (unix_prot & PROT_EXEC) + __builtin___clear_cache( (char *)base, (char *)base + size ); +#endif + return 0; +} + + /*********************************************************************** * mprotect_exec * - * Wrapper for mprotect, adds PROT_EXEC if forced by force_exec_prot + * Wrapper for mprotect, adds PROT_EXEC if forced by force_exec_prot. + * On strict-W^X kernels, falls back to converting the range to an + * anonymous private mapping when a genuine PROT_EXEC request is + * rejected on a file-backed VMA. */ static inline int mprotect_exec( void *base, size_t size, int unix_prot ) { @@ -2057,7 +2112,15 @@ static inline int mprotect_exec( void *base, size_t size, int unix_prot ) if (!(unix_prot & PROT_WRITE)) return -1; } - return mprotect( base, size, unix_prot ); + if (!mprotect( base, size, unix_prot )) return 0; + + /* Genuine PROT_EXEC on a file-backed mapping the kernel won't let us + * mark executable -- rebake the range as anonymous and retry. */ + if ((unix_prot & PROT_EXEC) && + try_rebake_anonymous_exec( base, size, unix_prot ) == 0) + return 0; + + return -1; } From cf39704d6e0f79d8f5c8411d6f47edd2dd2d801e Mon Sep 17 00:00:00 2001 From: Utkarsh Dalal Date: Fri, 1 May 2026 00:50:20 +0530 Subject: [PATCH 2/2] match gh's mprotect patch for ntdll --- dlls/ntdll/unix/virtual.c | 110 ++++++++++++-------------------------- 1 file changed, 35 insertions(+), 75 deletions(-) diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c index e0fd72233d5b..133220ccfa05 100644 --- a/dlls/ntdll/unix/virtual.c +++ b/dlls/ntdll/unix/virtual.c @@ -2042,65 +2042,10 @@ static NTSTATUS get_vprot_flags( DWORD protect, unsigned int *vprot, BOOL image } -/*********************************************************************** - * try_rebake_anonymous_exec - * - * Strict-W^X kernels (notably Android / arm64 Bionic) reject - * mprotect(PROT_EXEC) on a file-backed MAP_PRIVATE region that was - * ever writable, even if the range is now read-only. Wine's PE loader - * hits this constantly when flipping freshly-relocated text sections - * from RW to RX. Replace the affected range with an anonymous private - * mapping holding identical contents -- anonymous mappings are not - * subject to the W^X restriction -- then apply the requested - * protection. - * - * Returns 0 on success with the range now anonymously mapped at - * `unix_prot`. Returns -1 (without touching the original mapping - * beyond a possible PROT_READ downgrade) on any failure, in which - * case the caller should treat it as the original mprotect failure. - */ -static int try_rebake_anonymous_exec( void *base, size_t size, int unix_prot ) -{ - void *stage; - - /* PROT_READ alone never trips W^X and lets us memcpy the source - * contents out before we replace the mapping. */ - if (mprotect( base, size, PROT_READ ) != 0) return -1; - - if ((stage = anon_mmap_alloc( size, PROT_READ | PROT_WRITE )) == MAP_FAILED) - return -1; - memcpy( stage, base, size ); - - /* MAP_FIXED + MAP_ANONYMOUS replaces whatever VMA(s) cover - * [base, base+size) with a fresh anonymous private mapping. - * Surrounding portions of any larger original VMA stay intact via - * the kernel's automatic VMA splits. */ - if (anon_mmap_fixed( base, size, PROT_READ | PROT_WRITE, 0 ) != base) - { - munmap( stage, size ); - return -1; - } - - memcpy( base, stage, size ); - munmap( stage, size ); - - if (mprotect( base, size, unix_prot ) != 0) return -1; - -#ifdef __aarch64__ - if (unix_prot & PROT_EXEC) - __builtin___clear_cache( (char *)base, (char *)base + size ); -#endif - return 0; -} - - /*********************************************************************** * mprotect_exec * - * Wrapper for mprotect, adds PROT_EXEC if forced by force_exec_prot. - * On strict-W^X kernels, falls back to converting the range to an - * anonymous private mapping when a genuine PROT_EXEC request is - * rejected on a file-backed VMA. + * Wrapper for mprotect, adds PROT_EXEC if forced by force_exec_prot */ static inline int mprotect_exec( void *base, size_t size, int unix_prot ) { @@ -2112,15 +2057,7 @@ static inline int mprotect_exec( void *base, size_t size, int unix_prot ) if (!(unix_prot & PROT_WRITE)) return -1; } - if (!mprotect( base, size, unix_prot )) return 0; - - /* Genuine PROT_EXEC on a file-backed mapping the kernel won't let us - * mark executable -- rebake the range as anonymous and retry. */ - if ((unix_prot & PROT_EXEC) && - try_rebake_anonymous_exec( base, size, unix_prot ) == 0) - return 0; - - return -1; + return mprotect( base, size, unix_prot ); } @@ -2650,7 +2587,8 @@ static NTSTATUS map_view( struct file_view **view_ret, void *base, size_t size, * virtual_mutex must be held by caller. */ static NTSTATUS map_file_into_view( struct file_view *view, int fd, size_t start, size_t size, - off_t offset, unsigned int vprot, BOOL removable ) + off_t offset, unsigned int vprot, BOOL removable, + BOOL force_anon ) { void *ptr; int prot = get_unix_prot( vprot | VPROT_COMMITTED /* make sure it is accessible */ ); @@ -2666,6 +2604,18 @@ static NTSTATUS map_file_into_view( struct file_view *view, int fd, size_t start prot |= PROT_EXEC; } + /* On strict-W^X kernels (e.g. Android arm64), a file-backed MAP_PRIVATE + * mapping that was ever writable cannot later be flipped to PROT_EXEC, + * which breaks Wine's PE loader after relocations. When the caller + * passes force_anon=TRUE for a private mapping, skip the file mmap and + * load the contents into a fresh anonymous mapping instead. Anonymous + * mappings have no W^X restriction. Shared mappings still need a real + * file mmap and must always pass force_anon=FALSE. */ + if (force_anon && (flags & MAP_PRIVATE)) + { + removable = TRUE; + } + /* only try mmap if media is not removable (or if we require write access) */ if (!removable || (flags & MAP_SHARED)) { @@ -2992,10 +2942,14 @@ static NTSTATUS allocate_dos_memory( struct file_view **view, unsigned int vprot * * Map the header of a PE file into memory. */ -static NTSTATUS map_pe_header( void *ptr, size_t size, int fd, BOOL *removable ) +static NTSTATUS map_pe_header( void *ptr, size_t size, int fd, BOOL *removable, BOOL force_anon ) { if (!size) return STATUS_INVALID_IMAGE_FORMAT; + /* Force the pread fallback path when the caller wants the entire PE + * image to come up as anonymous memory (W^X-safe). */ + if (force_anon) *removable = TRUE; + if (!*removable) { if (mmap( ptr, size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED|MAP_PRIVATE, fd, 0 ) != MAP_FAILED) @@ -3290,7 +3244,7 @@ static IMAGE_BASE_RELOCATION *process_relocation_block( char *page, IMAGE_BASE_R */ static NTSTATUS map_image_into_view( struct file_view *view, const WCHAR *filename, int fd, struct pe_image_info *image_info, USHORT machine, - int shared_fd, BOOL removable ) + int shared_fd, BOOL removable, BOOL force_anon ) { IMAGE_DOS_HEADER *dos; IMAGE_NT_HEADERS *nt; @@ -3312,7 +3266,7 @@ static NTSTATUS map_image_into_view( struct file_view *view, const WCHAR *filena fstat( fd, &st ); header_size = min( image_info->header_size, st.st_size ); - if ((status = map_pe_header( view->base, header_size, fd, &removable ))) return status; + if ((status = map_pe_header( view->base, header_size, fd, &removable, force_anon ))) return status; status = STATUS_INVALID_IMAGE_FORMAT; /* generic error */ dos = (IMAGE_DOS_HEADER *)ptr; @@ -3338,7 +3292,7 @@ static NTSTATUS map_image_into_view( struct file_view *view, const WCHAR *filena total_size = min( total_size, ROUND_SIZE( 0, st.st_size )); if (map_file_into_view( view, fd, 0, total_size, 0, VPROT_COMMITTED | VPROT_READ | VPROT_WRITECOPY, - removable ) != STATUS_SUCCESS) return status; + removable, force_anon ) != STATUS_SUCCESS) return status; /* check that all sections are loaded at the right offset */ if (nt->OptionalHeader.FileAlignment != nt->OptionalHeader.SectionAlignment) return status; @@ -3390,7 +3344,8 @@ static NTSTATUS map_image_into_view( struct file_view *view, const WCHAR *filena (int)sec->PointerToRawData, (int)pos, file_size, map_size, (int)sec->Characteristics ); if (map_file_into_view( view, shared_fd, sec->VirtualAddress, map_size, pos, - VPROT_COMMITTED | VPROT_READ | VPROT_WRITE, FALSE ) != STATUS_SUCCESS) + VPROT_COMMITTED | VPROT_READ | VPROT_WRITE, FALSE, + FALSE /* MAP_SHARED, never anon */ ) != STATUS_SUCCESS) { ERR_(module)( "Could not map %s shared section %.8s\n", debugstr_w(filename), sec->Name ); return status; @@ -3406,7 +3361,8 @@ static NTSTATUS map_image_into_view( struct file_view *view, const WCHAR *filena if (end > base) map_file_into_view( view, shared_fd, base, end - base, pos + (base - sec->VirtualAddress), - VPROT_COMMITTED | VPROT_READ | VPROT_WRITECOPY, FALSE ); + VPROT_COMMITTED | VPROT_READ | VPROT_WRITECOPY, FALSE, + force_anon ); } pos += map_size; continue; @@ -3428,7 +3384,7 @@ static NTSTATUS map_image_into_view( struct file_view *view, const WCHAR *filena end < file_start || map_file_into_view( view, fd, sec->VirtualAddress, file_size, file_start, VPROT_COMMITTED | VPROT_READ | VPROT_WRITECOPY, - removable ) != STATUS_SUCCESS) + removable, force_anon ) != STATUS_SUCCESS) { ERR_(module)( "Could not map %s section %.8s, file probably truncated\n", debugstr_w(filename), sec->Name ); @@ -3672,7 +3628,11 @@ static NTSTATUS virtual_map_image( HANDLE mapping, void **addr_ptr, SIZE_T *size status = map_image_view( &view, image_info, size, limit_low, limit_high, alloc_type ); if (status) goto done; - status = map_image_into_view( view, filename, unix_fd, image_info, machine, shared_fd, needs_close ); + /* Force PE images to come up as anonymous memory so later mprotect() + * calls that flip text sections to PROT_EXEC succeed on strict-W^X + * kernels (e.g. Android arm64). */ + status = map_image_into_view( view, filename, unix_fd, image_info, machine, shared_fd, + needs_close, TRUE ); if (status == STATUS_SUCCESS) { if (offset) @@ -3814,7 +3774,7 @@ static unsigned int virtual_map_section( HANDLE handle, PVOID *addr_ptr, ULONG_P if (res) goto done; TRACE( "handle=%p size=%lx offset=%s\n", handle, size, wine_dbgstr_longlong(offset.QuadPart) ); - res = map_file_into_view( view, unix_handle, 0, size, offset.QuadPart, vprot, needs_close ); + res = map_file_into_view( view, unix_handle, 0, size, offset.QuadPart, vprot, needs_close, FALSE ); if (res == STATUS_SUCCESS) { SERVER_START_REQ( map_view )