Repository navigation
sceKernelVirtualQuery reports the whole mapping after a partial munmap #1894
Description
What
After munmap of part of a mapping, sceKernelVirtualQuery keeps answering for the whole original mapping: an address in the unmapped part returns success, and the extent it reports for the pages that are left still spans the hole. The host has unmapped the page (Windows reports it MEM_RESERVE, and Linux's /proc/self/maps no longer lists it). Only once the last piece is unmapped does the query fail.
Repro
With the guest's mmap/munmap (PrivateAnonymous 0x1002, 16 KiB pages):
auto* mapped = static_cast<char*>(mmap_nid_postfix(nullptr, 3 * Page, 3, 0x1002, -1, 0));
munmap_nid_postfix(mapped + Page, Page);
sceKernelVirtualQuery(mapped + Page, 0, &info, sizeof(info));Output of a probe test, Windows 11 (MinGW GCC 15.2.0, Release) on main d64134f (DirectMemory/ and GuestAllocations unchanged on f145139). The part after | is the host's own view of the address:
mmap 3 pages: 0000000200000000
munmap page 1: 0
page 0 0000000200000000 flags 0: result 0 start 0000000200000000 end 000000020000c000 prot 3 | host: MEM_COMMIT base 0000000200000000 size 0000000000004000
page 1 (unmapped) 0000000200004000 flags 0: result 0 start 0000000200000000 end 000000020000c000 prot 3 | host: MEM_RESERVE base 0000000200004000 size 0000000000004000
page 2 0000000200008000 flags 0: result 0 start 0000000200000000 end 000000020000c000 prot 3 | host: MEM_COMMIT base 0000000200008000 size 0000000000004000
find-next from page 1 0000000200004000 flags 1: result 0 start 0000000200000000 end 000000020000c000 prot 3 | host: MEM_RESERVE base 0000000200004000 size 0000000000004000
munmap page 0: 0
page 0 after 0000000200000000 flags 0: result 0 start 0000000200000000 end 000000020000c000 prot 3 | host: MEM_RESERVE base 0000000200000000 size 0000000000004000
munmap page 2: 0
page 2 after 0000000200008000 flags 0: result -2147352563 start 0000000000000000 end 0000000000000000 prot 0 | host: MEM_RESERVE base 0000000200008000 size 0000000000004000
Linux (Ubuntu 24.04 container, GCC 14.2, Release), same main, same results:
mmap 3 pages: 000070d316600000
munmap page 1: 0
page 0 000070d316600000 flags 0: result 0 start 000070d316600000 end 000070d31660c000 prot 3 | host: mapped 70d316600000-70d316604000 rw-p 00000000 00:00 0
page 1 (unmapped) 000070d316604000 flags 0: result 0 start 000070d316600000 end 000070d31660c000 prot 3 | host: not mapped
page 2 000070d316608000 flags 0: result 0 start 000070d316600000 end 000070d31660c000 prot 3 | host: mapped 70d316608000-70d31660c000 rw-p 00000000 00:00 0
find-next from page 1 000070d316604000 flags 1: result 0 start 000070d316600000 end 000070d31660c000 prot 3 | host: not mapped
munmap page 0: 0
page 0 after 000070d316600000 flags 0: result 0 start 000070d316600000 end 000070d31660c000 prot 3 | host: not mapped
munmap page 2: 0
page 2 after 000070d316608000 flags 0: result -2147352563 start 0000000000000000 end 0000000000000000 prot 0 | host: not mapped
FreeBSD splits the map entry on a partial munmap, so I'd expect the unmapped page to be EACCES (0x8002000D, as the last line returns), the next mapping (page 2) with SCE_KERNEL_VQ_FIND_NEXT, and page 0 and page 2 each to be reported as their own extent. That's from FreeBSD's behaviour, not measured on a console.
Where
sceKernelVirtualQuery matches and reports each registry range by allocationAddress/allocationBytes. After a partial unmap, UnmapRegistered leaves the surviving pieces with their own address/bytes but the original allocationAddress/allocationBytes, so any surviving piece matches the whole allocation. The comment above it says titles need the whole allocation rather than a page, so a fix probably has to match on the live pieces while keeping that whole-allocation answer for allocations nothing was unmapped from.
sceKernelQueryMemoryProtection goes through the same query. I ran into this while writing msync (#1857), which uses the query to find unmapped pages; its TechnicalDebt line records the limitation.
AI-assisted: yes (Claude Code).
Activity
MarcZX10 commentedon Oct 9, 2026
I opened #2002 with a fix and a
guest_memoryregression covering partial unmap, the surviving extents, andSCE_KERNEL_VQ_FIND_NEXT. The Linux test and local conventions check pass. The PR is a draft because upstream Build and Conventions workflows are waiting on maintainer approval (action_required); Windows CI has not run yet.