Skip to content

fix: harden scan-path reads and loader-lock teardown#91

Merged
tkhquang merged 2 commits into
mainfrom
fix/v3.4.0-hardening
May 29, 2026
Merged

fix: harden scan-path reads and loader-lock teardown#91
tkhquang merged 2 commits into
mainfrom
fix/v3.4.0-hardening

Conversation

@tkhquang
Copy link
Copy Markdown
Owner

@tkhquang tkhquang commented May 29, 2026

Summary

Hardening pass for the v3.4.0 cut. Strictly additive on v3.3.0; no public API breaks.

  • scanner: RIP-relative displacement reads and is_likely_function_prologue now go through Memory::seh_read instead of is_readable + a raw dereference, closing a TOCTOU window where a page could unmap/reprotect between the check and the read and fault the host during a scan.
  • input: on the loader-lock detach path, shutdown() no longer calls release_active_holds(), so user hold-release callbacks never fire under the loader lock (deadlock risk during FreeLibrary). The join path still releases, race-free.
  • platform: is_loader_lock_held() validates the PEB loader-lock CRITICAL_SECTION pointer is committed/readable before dereferencing it, and fails safe to "held" on any uncertainty (a wrong false would deadlock the loader on unload).
  • config: register_atomic is now a single constrained template (requires + if constexpr) instead of in-class explicit specializations; same int/bool/float support, standard-conforming.
  • logger: get_log_level() and is_async_mode_enabled() are now noexcept.
  • release: bump to 3.4.0 in CMakeLists.txt and retarget tests/test_version.cpp.

Summary by CodeRabbit

  • Chores

    • Project version bumped to 3.4.0
  • Bug Fixes

    • Improved application shutdown behavior under system-level lock conditions
    • Enhanced detection of system-level lock states with memory validation
    • Strengthened memory access safety during pattern scanning operations
  • Improvements

    • Logger API methods now provide stronger exception-safety guarantees

Review Change Stack

Harden the scan path and subsystem teardown for the v3.4.0 cut:

- scanner: read RIP displacement and prologue bytes via Memory::seh_read instead
  of is_readable + raw deref, closing a TOCTOU host-crash window during scans
- input: skip hold-release callbacks on the loader-lock detach path so user
  callbacks never run under the loader lock
- platform: validate the PEB loader-lock pointer before deref and fail safe
- config: register_atomic is now one constrained template (was explicit
  specializations); logger const accessors are noexcept
- bump version to 3.4.0 and retarget the version test
@tkhquang tkhquang self-assigned this May 29, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Warning

Review limit reached

@tkhquang, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 30 minutes and 37 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d254944-3fb2-463d-a2ac-d8f3fe68153e

📥 Commits

Reviewing files that changed from the base of the PR and between d414a91 and 158b61f.

📒 Files selected for processing (2)
  • include/DetourModKit/logger.hpp
  • src/input.cpp
📝 Walkthrough

Walkthrough

Version 3.4.0 release introducing template API refinements, logger exception-safety declarations, and defensive memory-read patterns. Loader-lock shutdown and scanner symbol resolution now use fault-guarded reads to prevent races and crashes in Windows loader contexts.

Changes

DetourModKit 3.4.0 Release

Layer / File(s) Summary
Version bump to 3.4.0 and test updates
CMakeLists.txt, tests/test_version.cpp
Project version incremented from 3.3.0 to 3.4.0 in CMake declaration. Test assertions for DMK_VERSION_MINOR, DMK_VERSION_STRING, and DMK_VERSION_AT_LEAST macro expectations updated to match new version boundary.
Config::register_atomic template refactoring
include/DetourModKit/config.hpp
<concepts> header added. register_atomic refactored from deleted primary template with explicit int, bool, float specializations to single requires-constrained template using if constexpr dispatch. KeyCombo documentation comment arrow notation updated.
Logger noexcept exception-safety declarations
include/DetourModKit/logger.hpp, src/logger.cpp
is_async_mode_enabled() and get_log_level() const query methods declared noexcept in both header and implementation. Lock-ordering comment formatting adjusted.
Loader-lock detection and shutdown safety
src/platform.hpp, src/input.cpp
is_loader_lock_held() now validates CRITICAL_SECTION pointer region via VirtualQuery before dereferencing; fails safe by returning true if region unconfirmed. InputPoller::shutdown() gains distinct loader-lock branch that pins module, detaches poll thread, and returns immediately without join or hold-release cleanup.
Scanner fault-guarded memory reads
src/scanner.cpp
resolve_rip_relative, resolve_candidate_match, and is_likely_function_prologue unified to use Memory::seh_read for displacement and opcode fetches instead of is_readable + memcpy patterns. Faults trigger typed returns (UnreadableDisplacement, 0, false) without dereferencing invalid memory.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • tkhquang/DetourModKit#60: Introduces CMake-generated version.hpp macros driven by project(VERSION), affected by the same version source in this PR's bump to 3.4.0.
  • tkhquang/DetourModKit#77: Original implementation of Config::register_atomic INI-to-std::atomic API, which this PR refactors into a single constrained template.
  • tkhquang/DetourModKit#46: Modifies RIP-relative scanner resolution, related to this PR's unified fault-guarded Memory::seh_read consolidation pattern across symbol scanning.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: hardening of scan-path reads and loader-lock teardown logic, which are the primary focuses of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
include/DetourModKit/logger.hpp (1)

122-122: ⚡ Quick win

Consider adding [[nodiscard]] to these status queries.

Both is_async_mode_enabled() (a bool status query) and get_log_level() (a status query) discard usefully signal an accidentally-ignored return. Since these declarations are already being touched, adding [[nodiscard]] aligns them with the rest of the public surface.

♻️ Proposed change
-        bool is_async_mode_enabled() const noexcept;
+        [[nodiscard]] bool is_async_mode_enabled() const noexcept;
-        LogLevel get_log_level() const noexcept
+        [[nodiscard]] LogLevel get_log_level() const noexcept
         {
             return current_log_level_.load(std::memory_order_acquire);
         }

As per coding guidelines: "Apply [[nodiscard]] to factory functions, status queries, bool success/failure returns".

Also applies to: 144-147

🤖 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 `@include/DetourModKit/logger.hpp` at line 122, Mark the status-query functions
as [[nodiscard]] so callers cannot silently ignore their return values: add the
[[nodiscard]] attribute to the declarations of is_async_mode_enabled() and
get_log_level() (and any other status-query/public surface functions noted in
the review) in logger.hpp; keep their signatures otherwise (e.g., bool
is_async_mode_enabled() const noexcept and the get_log_level() declaration) to
align with the coding guideline for factory/status-query returns.
🤖 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/input.cpp`:
- Around line 297-311: The shutdown path detaches poll_thread_ under
is_loader_lock_held() but may destroy the InputPoller while the detached
poll_loop() still accesses members (cv_mutex_, cv_, poll_interval_), causing a
use-after-free; update InputManager::shutdown() so when local_poller->shutdown()
runs while is_loader_lock_held() is true you intentionally keep the InputPoller
alive (e.g., move local_poller into a static container) instead of letting it be
destroyed, or centralize the is_loader_lock_held() check into
InputManager::shutdown() to leak the shared_ptr and prevent destruction until
process exit, ensuring the detached thread cannot access freed members.

In `@tests/test_version.cpp`:
- Around line 9-33: The tests in VersionTest are plain TEST cases but must be
converted to a fixture-based suite: create a class VersionTest : public
::testing::Test with public SetUp() and TearDown() (can be empty or used for
cleanup), change each TEST(VersionTest, ...) to TEST_F(VersionTest, ...) and
keep the same test bodies that assert DMK_VERSION_MAJOR/MINOR/PATCH,
DMK_VERSION_STRING and DMK_VERSION_AT_LEAST checks; ensure the fixture class
name is exactly VersionTest so the TEST_F references resolve.

---

Nitpick comments:
In `@include/DetourModKit/logger.hpp`:
- Line 122: Mark the status-query functions as [[nodiscard]] so callers cannot
silently ignore their return values: add the [[nodiscard]] attribute to the
declarations of is_async_mode_enabled() and get_log_level() (and any other
status-query/public surface functions noted in the review) in logger.hpp; keep
their signatures otherwise (e.g., bool is_async_mode_enabled() const noexcept
and the get_log_level() declaration) to align with the coding guideline for
factory/status-query returns.
🪄 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: f94e9b83-06c4-4d5f-a339-285591e85b69

📥 Commits

Reviewing files that changed from the base of the PR and between a43818a and d414a91.

📒 Files selected for processing (8)
  • CMakeLists.txt
  • include/DetourModKit/config.hpp
  • include/DetourModKit/logger.hpp
  • src/input.cpp
  • src/logger.cpp
  • src/platform.hpp
  • src/scanner.cpp
  • tests/test_version.cpp

Comment thread src/input.cpp
Comment thread tests/test_version.cpp
…lock

The loader-lock detach path detaches the poll jthread, but InputManager::shutdown()
then dropped the last reference and freed InputPoller members the detached poll_loop
still reads (use-after-free). Leak the poller into a nothrow heap cell under loader lock
so it outlives the detached thread, matching the leak-on-loader-lock discipline used for
Logger and ConfigWatcher.

Also mark the const Logger status queries (get_log_level, is_async_mode_enabled,
is_enabled) [[nodiscard]].
@tkhquang tkhquang merged commit b6b7b1c into main May 29, 2026
2 checks passed
@tkhquang tkhquang deleted the fix/v3.4.0-hardening branch May 29, 2026 22:55
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