Skip to content

fix(linux): protect the full memory range across page boundaries - #37

Merged
qubka merged 1 commit into
untrustedmodders:mainfrom
Mai-Ooizumi:fix-linux-mem-protect-range
Nov 1, 2026
Merged

qubka merged 1 commit into
untrustedmodders:mainfrom
Mai-Ooizumi:fix-linux-mem-protect-range

Conversation

@Mai-Ooizumi

Copy link
Copy Markdown
Contributor

Summary

Fixes a bug in PolyHook2's Linux PLH::MemAccessor::mem_protect implementation where memory ranges that start at a non-page-aligned address and cross a page boundary fail to update protection flags on the trailing page.

Root Cause

Previously, mem_protect rounded dest down to the nearest page boundary (aligned_dest), but rounded size up independently (MEMORY_ROUND_UP(size, pageSize)):

uint64_t aligned_dest = MEMORY_ROUND(dest, PLH::getPageSize());
uint64_t aligned_size = MEMORY_ROUND_UP(size, PLH::getPageSize());

When dest has a large intra-page offset (e.g., pageSize - 16) and size is small (e.g., 32 bytes), [dest, dest + size) spans two pages, whereas MEMORY_ROUND_UP(size, pageSize) only equals 1 * pageSize. As a result, mprotect only updates the first page, leaving the second page of a trampoline or patched region without the requested protection (e.g., non-executable, causing a SIGSEGV when executed).

Changes

  • patches/polyhook.patch (sources/MemAccessor.cpp): Include the intra-page offset (dest - aligned_dest) when rounding up the protection length, while preserving aligned_size = 0 when size == 0 (used for querying current page protection):
    uint64_t aligned_size = size == 0 ? 0 : MEMORY_ROUND_UP(size + (dest - aligned_dest), PLH::getPageSize());
  • patches/polyhook.patch (UnitTests/linux/TestMemProtector.cpp): Add unit tests covering ranges that cross page boundaries, end exactly at a page boundary, start on a page boundary, and have zero length at an unaligned address, as well as verifying restoration of original protections.
  • CMakeLists.txt: Wire up CTest (polyhook_mem_protect_tests) on Linux when BUILD_TESTING is enabled.

Include the first-page offset when calculating the aligned protection
length. Otherwise, a trampoline crossing a page boundary can leave its
last page non-executable and cause SIGSEGV.

Preserve zero-length behavior and add regression coverage for crossing,
aligned, exact-boundary, and zero-length ranges, including restoration.

Signed-off-by: Mai Ooizumi <mai@imomoe.moe>
@qubka

qubka commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Thanks @Mai-Ooizumi - clean fix, well explained.

@qubka
qubka merged commit 0aadc50 into untrustedmodders:main Nov 1, 2026
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.

2 participants