Skip to content

Keep mremap growth out of occupied suffixes - #391

Merged
jserv merged 1 commit into
sysprog21:mainfrom
Ksld154:fix-mremap-adjacent-growth
Sep 21, 2026
Merged

jserv merged 1 commit into
sysprog21:mainfrom
Ksld154:fix-mremap-adjacent-growth

Conversation

@Ksld154

@Ksld154 Ksld154 commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Problem

Adjacent anonymous mappings can coalesce into a tracked region that extends
beyond the source range passed to mremap. The in-place growth check skipped
that region when its start matched the source address, so it treated the live
suffix as free and cleared its contents.

Linux permits in-place growth only when the source range ends at the end of
its VMA. An occupied suffix must make a request without MREMAP_MAYMOVE fail
with ENOMEM; with the flag, only the source range may relocate.

This bug caused Alpine apk to crash while resolving packages from local
repository indexes.

Regression coverage

  • Two separately mapped adjacent anonymous pages reproduce the coalesced-region
    case that exposed the bug.
  • One two-page anonymous mapping exercises the occupied-suffix path by
    construction, independent of region-merging policy.

Both cases verify that a request without MREMAP_MAYMOVE returns ENOMEM
without changing either page. They also verify that MREMAP_MAYMOVE relocates
the source data, zeroes the new extension, and preserves the occupied suffix.

Environment

  • macOS 26.7, Darwin 25.6.0
  • Apple Silicon arm64
  • Apple SDK 27.0
  • Alpine 3.21 aarch64 guest

Validation

  • make check-format: passed
  • build/elfuse build/test-mremap: 14 passed
  • bash tests/test-matrix.sh all: the QEMU aarch64 lane passed 271 tests with
    no failures; the elfuse aarch64 lane passed 292 tests, with the unrelated
    test-dup-setfl-race timing out under the full matrix load and passing when
    run alone; Rosetta-independent x86_64 guardrails passed
  • Alpine apk --no-network --simulate add git: three consecutive runs exited
    with status 0
  • make check: all 99 core tests and all mremap suites passed; the final
    BusyBox nslookup smoke test could not reach a DNS server
  • make verify: not completed because Frama-C is unavailable locally

Closes #389


Summary by cubic

Protects adjacent coalesced mappings from being overwritten during in-place mremap growth. Previously, growth could zero live bytes in a neighboring mapping when the region extended past the source range; now it returns ENOMEM without MREMAP_MAYMOVE and relocates with MREMAP_MAYMOVE, preserving both mappings.

  • Adds a regression test covering both growth modes and verifying contents are preserved.

Written for commit 1836227. Summary will update on new commits.

Review in cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

@jserv
jserv requested a review from Max042004 September 20, 2026 18:39
Comment thread src/syscall/mem.c
Comment on lines +3900 to +3901
bool can_grow =
!region_range_overlaps(g, grow_off, grow_off + grow_len);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refusing in-place growth here sends a source whose region extends past grow_off down the move path, which is right, and it removes the old path's worst case: a memset of grow_len zeros straight into a live MAP_SHARED file overlay, which wrote those zeros to the backing file.

One hole survives the move. cleanup_overlays_in_range rounds its teardown up to the host page (ALIGN_UP(end, hps), 16 KiB on Apple Silicon) while guest ranges are 4 KiB granular, and hvf_restore_slab_backing re-mmaps fresh slab without repopulating from the file, so growing the first 4 KiB of a shared file mapping leaves the next 12 KiB of the surviving suffix reading slab instead of file contents. That was already reachable through a separate mapping at grow_off, so it is a follow-up rather than something this change owes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. The 16 KiB overlay teardown is independent of this change and remains reachable through a separate mapping at grow_off. It needs a separate issue and regression test. Will take time to reproduce the follow-up and open a new issue.

Comment thread tests/test-mremap.c

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjacent anonymous mappings can coalesce into a tracked region that
extends beyond the requested source range. In-place growth then cleared
the live suffix, although Linux permits growth only when the source
range ends at the end of its VMA.

Without MREMAP_MAYMOVE, the request returns ENOMEM. With the flag, only
the source range relocates and the suffix remains intact.

Closes sysprog21#389
@Ksld154
Ksld154 force-pushed the fix-mremap-adjacent-growth branch from 60b4185 to 1836227 Compare September 21, 2026 17:31
@Ksld154 Ksld154 changed the title Protect adjacent mappings from mremap growth Keep mremap growth out of occupied suffixes Sep 21, 2026
@jserv
jserv merged commit e7d1027 into sysprog21:main Sep 21, 2026
15 checks passed
@jserv

jserv commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Thank @Ksld154 for contributing!

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.

mremap in-place growth overwrites an adjacent coalesced anonymous mapping

2 participants