Skip to content

0.13.5 --- a large allocation is a mapping, and a mapping is whole pages - #34

Merged
Sunrisepeak merged 1 commit into
mainfrom
mmap-whole-pages
Sep 14, 2026
Merged

0.13.5 --- a large allocation is a mapping, and a mapping is whole pages#34
Sunrisepeak merged 1 commit into
mainfrom
mmap-whole-pages

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

Every program on Windows that allocated 131,052 bytes or more wrote past the memory it had obtained. The overrun reached up to a page beyond the block, into the next heap block's header. The program went on until the heap next walked there, then stopped with an access violation or ended without a word.

musl's allocator obtains such an allocation as a mapping of its own, mmap(n + IB + UNIT), and uses the mapping up to the end of its last page:

  • get_stride: the slot of an individually mapped allocation is maplen * 4096 - UNIT bytes, where maplen is the length rounded up to pages.
  • enframe: the block starts at a cycling offset of up to slack units into the slot, so up to a page past the start.
  • set_size: the slot's footer, *(uint32_t *)(end - 4), is written just below the end of the last page.

On Linux those bytes exist, because the kernel maps whole pages. This port's do_mmap obtained kal_alloc(len, 4096) for exactly len. Where kal_alloc hands out that length and no more, which is the process heap on Windows, the footer and the tail of the block lay beyond the allocation.

Measured under Wine, x86_64-windows-gnu, both profiles:

std::string grown by push_back:  196,607 bytes ok, 196,700 bytes: the program ends, exit 0, nothing more printed
operator new: 16 → 131072 held, then 262144, then delete 131072: ends at the delete
examples/malloc-large before this change:
  FAIL: the last page of a mapping reads as zero (8192)
  wine: Unhandled page fault on read access to FFFFFFFFFFFFFFFF

Found through lsp-mcpp. On windows-2022 its conformance runner, a release build over openkal-llvm-runtime 0.9.5, stopped with an access violation while reading a build tree with a file of about 200 KB.

  • port/src/okm_syscall.c: SYS_mmap and SYS_munmap round the length up to whole pages, and a length that cannot be rounded is ENOMEM.
  • examples/malloc-large observes three things, and CI runs it on every row:
    1. A mapping whose length ends inside a page: the rest of that page reads as zero and can be written, and blocks allocated beside it keep their contents.
    2. Blocks from half the threshold to 8 MB, each grown from the one before the way a string grows, with small allocations walking the heap between them.
    3. realloc across the threshold in both directions.
  • 0.13.5, and the README's table row for it.

Measured locally: the probe passes on x86_64 Linux, and on x86_64-windows-gnu under Wine in dev and release. With the change, the string and operator new patterns above complete, as does a 300 KB read through std::istreambuf_iterator. Without it, the probe fails and then faults.

musl's allocator obtains an allocation of MMAP_THRESHOLD bytes or more as a
mapping of n + IB + UNIT bytes and uses it up to the end of its last page: the
block starts up to a page into the slot and the slot's footer is written just
below the page's end. The port's mmap obtained exactly the length asked for, so
where kal_alloc hands out that length and no more (the process heap on
Windows) the footer and up to a page of the block lay over the next heap
block's header. A std::string grown past 196,607 bytes ended the program under
Wine; on windows-2022 lsp-mcpp's conformance runner stopped with an access
violation reading a build tree.

SYS_mmap and SYS_munmap round the length up to whole pages.
examples/malloc-large observes the tail of a mapping, blocks grown from half the
threshold to eight megabytes, and realloc across the threshold; CI runs it on
every row.
@Sunrisepeak
Sunrisepeak merged commit aa9419c into main Sep 14, 2026
6 checks passed
Sunrisepeak added a commit to Sunrisepeak/lsp-mcpp-private that referenced this pull request Sep 14, 2026
On Windows an allocation of 131,052 bytes or more wrote past its heap block:
musl uses a mapping to the end of its last page and the port had obtained only
the length asked for (mcpplibs/openkal-musl#34, mcpplibs/openkal-llvm-runtime#21,
mcpp-index#424 and #425). The conformance runner stopped on build trees with it,
and the server would have on any large file or message.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant