Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions dlls/ntdll/unix/virtual.c
Original file line number Diff line number Diff line change
Expand Up @@ -2587,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 */ );
Expand All @@ -2603,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))
{
Expand Down Expand Up @@ -2929,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)
Expand Down Expand Up @@ -3227,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;
Expand All @@ -3249,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;
Expand All @@ -3275,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;
Expand Down Expand Up @@ -3327,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;
Expand All @@ -3343,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;
Expand All @@ -3365,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 );
Expand Down Expand Up @@ -3609,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)
Expand Down Expand Up @@ -3751,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 )
Expand Down
Loading