Skip to content

fix(windows): use tryProtect in BufferMemoryHandle destructor to prevent mprotect crash#170

Open
robobun wants to merge 1 commit intooven-sh:mainfrom
robobun:claude/fix-windows-mprotect-crash
Open

fix(windows): use tryProtect in BufferMemoryHandle destructor to prevent mprotect crash#170
robobun wants to merge 1 commit intooven-sh:mainfrom
robobun:claude/fix-windows-mprotect-crash

Conversation

@robobun
Copy link

@robobun robobun commented Mar 1, 2026

Summary

  • Use OSAllocator::tryProtect() instead of OSAllocator::protect() in BufferMemoryHandle::~BufferMemoryHandle() on Windows for both the Signaling and Shared BoundsChecking destructor paths
  • Prevents a flaky crash (mprotect failed: 487 / exit code 0xC0000409) in Bun's buffer tests on Windows

Problem

On Windows, the BufferMemoryHandle destructor calls OSAllocator::protect() to restore page permissions (from PAGE_NOACCESS guard pages back to PAGE_READWRITE) before freeing memory. This translates to VirtualAlloc(addr, size, MEM_COMMIT, PAGE_READWRITE).

This call fails with ERROR_INVALID_ADDRESS (487) when the underlying virtual memory reservation has already been released by the allocator (libpas recycling virtual pages). The fatal protect() then calls RELEASE_ASSERT_NOT_REACHED(), crashing the process.

The crash manifests in Bun CI as:

mprotect failed: 487
(exit code 3221226505 / 0xC0000409 STATUS_STACK_BUFFER_OVERRUN)

This is flaky because it depends on GC timing and allocator behavior after running hundreds of tests.

Why the fix is correct

The protect-before-free exists to make PAGE_NOACCESS guard pages accessible before releasing them. On POSIX, some free paths use madvise which may require accessible pages. On Windows, VirtualFree(MEM_RELEASE) releases memory regardless of page protection state, so restoring permissions before freeing is not necessary.

By using tryProtect() (which returns false on failure instead of crashing), the destructor gracefully handles the case where the reservation is already gone, and proceeds to free the memory normally.

Non-Windows platforms continue to use the fatal protect() call, preserving existing behavior.

Test plan

  • Verify buffer.test.js no longer crashes on Windows x64-baseline CI
  • No behavior change on POSIX platforms (code is #if OS(WINDOWS) guarded)

🤖 Generated with Claude Code

…ent mprotect crash

On Windows, the BufferMemoryHandle destructor calls OSAllocator::protect()
to restore page permissions before freeing memory. This calls
VirtualAlloc(MEM_COMMIT) which fails with ERROR_INVALID_ADDRESS (487) if
the underlying virtual memory reservation was already released by the
allocator (libpas recycling virtual pages).

This manifests as a flaky crash in Bun's buffer tests on Windows:
  mprotect failed: 487
  (exit code 0xC0000409 / STATUS_STACK_BUFFER_OVERRUN)

The protect-before-free pattern is unnecessary on Windows because
VirtualFree(MEM_RELEASE) releases memory regardless of page protection
state (unlike POSIX where some free paths use madvise which may require
accessible pages).

Replace OSAllocator::protect() with OSAllocator::tryProtect() in both
the Signaling and Shared BoundsChecking destructor paths on Windows.
Non-Windows platforms continue to use the fatal protect() call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5197a6a and da2fca2.

📒 Files selected for processing (1)
  • Source/JavaScriptCore/runtime/BufferMemoryHandle.cpp

Walkthrough

BufferMemoryHandle.cpp destructor on Windows builds now uses OSAllocator::tryProtect() instead of OSAllocator::protect() for memory protection (fast and growable bounds memory). Non-Windows platforms retain existing behavior. CMakeLists.txt updated with 16 new lines.

Changes

Cohort / File(s) Summary
Windows Memory Protection Refinement
Source/JavaScriptCore/runtime/BufferMemoryHandle.cpp, CMakeLists.txt
Destructor paths on Windows now invoke OSAllocator::tryProtect() instead of OSAllocator::protect() for both fast memory (Signaling) and growable bounds memory (BoundsChecking, Shared). Non-Windows platforms continue using OSAllocator::protect(). This enables graceful handling when memory has already been released by the allocator.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: using tryProtect instead of protect in BufferMemoryHandle destructor on Windows to fix an mprotect crash.
Description check ✅ Passed The description provides comprehensive context including the problem, solution, and test verification, though it doesn't explicitly reference a Bugzilla bug ID as the template suggests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

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