fix(memory,scanner): guard region reads against TOCTOU faults#113
Conversation
… and EXCEPTION_IN_PAGE_ERROR Wrap the scanner/string-xref per-region and per-window reads in a structured-exception guard so a concurrent decommit/reprotect is skipped (with per-region/per-window match-state rollback) instead of faulting the host on MSVC. Share one is_guarded_read_fault predicate across the four memory.cpp SEH filters and the new guards so EXCEPTION_IN_PAGE_ERROR is swallowed everywhere. Also fix three region-cache lows: misaligned read_ptr_unsafe deref (memcpy), unswept-shard observability, and the init publication window in check_memory_permission.
|
Warning Review limit reached
More reviews will be available in 44 minutes and 29 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR hardens foreign-memory reads and pattern scanning against concurrent memory decommit/reprotect faults. It introduces a centralized SEH exception-filter predicate, applies it across Memory and Scanner modules, fixes misaligned pointer reads with ChangesSEH Guarded Reading and Scanning Resilience
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
tests/test_string_xref.cpp (1)
817-825: 💤 Low valueConsider explicit capture list for the thread lambda.
The coding guidelines specify explicit capture lists for lambdas passed to threads. While the current
[&]capture is safe (thejthreadjoins before captures go out of scope), explicit captures make the dependencies clear.Suggested change
std::jthread toggler( - [&](std::stop_token stop_token) + [decommit_page, page](std::stop_token stop_token) { while (!stop_token.stop_requested()) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_string_xref.cpp` around lines 817 - 825, Replace the generic [&] capture in the std::jthread "toggler" lambda with an explicit capture list for the variables it uses (e.g., decommit_page and page) so dependencies are clear; update the lambda capture to explicitly capture &decommit_page and &page and leave the stop_token as the function parameter used inside, ensuring VirtualFree and VirtualAlloc calls still reference the same captured variables.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/string_xref.cpp`:
- Around line 219-240: The loop in phase 2 (using
Scanner::detail::collect_executable_windows and scan_window_narrow_guarded)
currently returns first_site when found_count == 1 even if faulted_windows > 0,
which hides indeterminate results; change the logic in the block that logs
faulted_windows (and the analogous block at 364-380) so that if faulted_windows
> 0 the function does not return success but instead returns an indeterminate
error (introduce or use a StringXrefError value for "IndeterminateReference" or
adjust find_string_xref's return type to convey this error), i.e., after logging
via log_faulted_windows, if faulted_windows > 0 return the new indeterminate
StringXrefError rather than first_site, otherwise behave as before (return 0 for
none, first_site for a guaranteed single hit).
In `@tests/test_memory.cpp`:
- Around line 2119-2185: The new TESTs should be converted to use the existing
MemoryTest fixture and must restore global cache state: move the body of
MemoryGuardedReadFault.AcceptsForeignReadFaultsAndRejectsOthers and
MemoryUninitializedCache.PermissionChecksFallBackToDirectQuery into TEST_F
suites (or add them to the MemoryTest class), and in
MemoryTest::SetUp()/TearDown() ensure Memory::shutdown_cache() and the cache
re-initialization calls are balanced so the global cache state is always
restored after each test; specifically replace standalone TEST(...) definitions
with TEST_F(MemoryTest, ...) and add calls in TearDown() to reinitialize or
restore the Memory cache (or remove the shutdown call from the test and instead
perform the shutdown in SetUp()/TearDown() with proper backup/restore) so tests
are order-independent and follow the project fixture pattern.
---
Nitpick comments:
In `@tests/test_string_xref.cpp`:
- Around line 817-825: Replace the generic [&] capture in the std::jthread
"toggler" lambda with an explicit capture list for the variables it uses (e.g.,
decommit_page and page) so dependencies are clear; update the lambda capture to
explicitly capture &decommit_page and &page and leave the stop_token as the
function parameter used inside, ensuring VirtualFree and VirtualAlloc calls
still reference the same captured variables.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 97ef8bac-f999-48d1-b93b-6aa9b6ac9065
📒 Files selected for processing (11)
AGENTS.mdREADME.mdinclude/DetourModKit/memory.hppsrc/memory.cppsrc/memory_internal.hppsrc/scanner.cppsrc/scanner_internal.hppsrc/string_xref.cpptests/test_memory.cpptests/test_scanner.cpptests/test_string_xref.cpp
Default by-reference capture into a jthread lambda is the F.53 footgun; the page/address scalars are cheap to copy and the copy removes the lifetime coupling entirely.
Summary
TOCTOU sweep guards
The per-region VirtualQuery gate proves a region readable only at gate time; a concurrent decommit or reprotect between the gate and the unguarded SIMD/Zydis reads faults the host. The per-region scan (scanner) and per-window scans (string-xref narrow + broad) now run inside a structured-exception guard on MSVC: a faulting region/window is skipped and counted instead of terminating the process, with the Nth-match / reference state rolled back so a skipped region contributes nothing. MinGW keeps the VirtualQuery gate as its only guard until the VEH-based handler lands.
EXCEPTION_IN_PAGE_ERROR
A single shared
is_guarded_read_faultpredicate now backs the four memory.cpp SEH filters and the new scanner/string-xref guards, so a file-backed page that fails to page in (0xC0000006) is swallowed everywhere instead of escaping to terminate the host.region-cache lows
read_ptr_unsafereads through memcpy (no misaligned-deref UB);invalidate_range_internalcounts and debug-logs shards left unswept under contention;check_memory_permissionfalls back to a direct VirtualQuery when the cache reports zero shards, closing the init publication window.Summary by CodeRabbit
Release Notes
Documentation
Bug Fixes
Tests