[PATCH 0/1] MR11736: ntdll: Keep reserved area bounds page aligned when avoiding 4GB wrap-around
A latent issue found in ntdll while adding Apple Silicon macOS host support for building Proton (https://github.com/ValveSoftware/Proton/pull/10087). Proton's Wine version builds and then runs 64-bit and 32-bit versions of wine in order to create a base prefix as part of a redistributable package phase, all within a guest linux/amd64 container. 32-bit i386 processes translate with QEMU in this host context, while 64-bit x86_64 uses the host's Rosetta 2 translation system. It is likely but not confirmed that QEMU would similarly be used and 32-bit wine would fail to run when virtualizing linux/amd64 on linux/arm64 hardware. mmap_add_reserved_area() and mmap_remove_reserved_area() trim a single byte when addr + size wraps to 0, which leaves an area whose end is 0xffffffff. alloc_virtual_heap() then derives its mapping address from that end and passes an unaligned address to anon_mmap_fixed(), tripping the alignment assertions added in d813ffc3557 (ntdll: Align virtual memory allocations to the host page size). Trim a whole page instead so the area bounds stay aligned. The wrap-around trim dates to 94d74b5fedf (2004), long before the alignment invariant existed. It is only reached when a reserved area ends exactly at 4GB, which requires mmap_init() to take the reserve_area( user_space_limit, 0 ) branch -- that is, when the initial stack is not near the top of the 32-bit address space. Native Linux and all other native x86 platforms place it near the top, so the branch is dead there; qemu-user lays out the guest address space itself and maps the stack around 0x40000000, making it reachable and aborting every 32-bit process at startup. Nothing verifies requested against recorded area bounds, and the top 64k allocation granularity block was already unusable due to the one-byte trim, so widening it to a page costs nothing allocatable. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11736
From: Nat Brown <natbro@gmail.com> mmap_add_reserved_area() and mmap_remove_reserved_area() trim a single byte when addr + size wraps to 0, which leaves an area whose end is 0xffffffff. alloc_virtual_heap() then derives its mapping address from that end and passes an unaligned address to anon_mmap_fixed(), tripping the alignment assertions added in d813ffc3557 (ntdll: Align virtual memory allocations to the host page size). Trim a whole page instead so the area bounds stay aligned. The wrap-around trim dates to 94d74b5fedf (2004), long before the alignment invariant existed. It is only reached when a reserved area ends exactly at 4GB, which requires mmap_init() to take the reserve_area( user_space_limit, 0 ) branch -- that is, when the initial stack is not near the top of the 32-bit address space. Native Linux places it near the top, so the branch is dead there; qemu-user lays out the guest address space itself and maps the stack around 0x40000000, making it reachable and aborting every 32-bit process at startup. Nothing verifies requested against recorded area bounds, and the top 64k allocation granularity block was already unusable due to the one-byte trim, so widening it to a page costs nothing allocatable. Signed-off-by: Nat Brown <natbro@gmail.com> --- dlls/ntdll/unix/virtual.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c index d6038c4dcb6..4acc5716359 100644 --- a/dlls/ntdll/unix/virtual.c +++ b/dlls/ntdll/unix/virtual.c @@ -442,7 +442,12 @@ static void mmap_add_reserved_area( void *addr, SIZE_T size ) assert( !((UINT_PTR)addr & host_page_mask) ); assert( !(size & host_page_mask) ); - if (!((intptr_t)addr + size)) size--; /* avoid wrap-around */ + /* An area ending exactly at the 4GB boundary wraps addr+size to 0. Back it off + * by a whole page rather than a single byte, so that the area bounds stay page + * aligned as the asserts above require; consumers such as alloc_virtual_heap() + * derive addresses from the area end and would otherwise be handed an + * unaligned one. */ + if (size >= host_page_size && !((intptr_t)addr + size)) size -= host_page_size; end = (char *)addr + size; LIST_FOR_EACH( ptr, &reserved_areas ) @@ -494,7 +499,12 @@ static void mmap_remove_reserved_area( void *addr, SIZE_T size ) assert( !((UINT_PTR)addr & host_page_mask) ); assert( !(size & host_page_mask) ); - if (!((intptr_t)addr + size)) size--; /* avoid wrap-around */ + /* An area ending exactly at the 4GB boundary wraps addr+size to 0. Back it off + * by a whole page rather than a single byte, so that the area bounds stay page + * aligned as the asserts above require; consumers such as alloc_virtual_heap() + * derive addresses from the area end and would otherwise be handed an + * unaligned one. */ + if (size >= host_page_size && !((intptr_t)addr + size)) size -= host_page_size; ptr = list_head( &reserved_areas ); /* find the first area covering address */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11736
参加者 (2)
-
Nat Brown -
Nat Brown (@natbro)