src/internal/scan_fault_seam.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_INTERNAL_SCAN_FAULT_SEAM_HPP | ||
| 2 | #define DETOURMODKIT_INTERNAL_SCAN_FAULT_SEAM_HPP | ||
| 3 | |||
| 4 | /** | ||
| 5 | * @file internal/scan_fault_seam.hpp | ||
| 6 | * @brief Test-only injection point that raises one access fault at a chosen address inside a guarded scanner body. | ||
| 7 | * @details The scanner's region sweep and string-xref window sweep run their foreign reads inside a fault guard that | ||
| 8 | * declares an exact span. Whether that guard screens the FAULTING ADDRESS, or merely the exception class, is | ||
| 9 | * not observable from outside: a fault at an address the span does not cover must reach the host's handlers, | ||
| 10 | * and the only way to drive one is from inside the guarded frame. This seam is that driver. The whole header | ||
| 11 | * is behind DMK_ENABLE_TEST_SEAMS, so a shipping archive carries neither the storage nor the fire site. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 15 | |||
| 16 | #include <atomic> | ||
| 17 | #include <cstdint> | ||
| 18 | |||
| 19 | namespace DetourModKit | ||
| 20 | { | ||
| 21 | namespace detail | ||
| 22 | { | ||
| 23 | /// Address the page-gated region sweep's guarded body reads once, or 0 when disarmed. | ||
| 24 | inline std::atomic<std::uintptr_t> g_scan_region_fault_for_test{0}; | ||
| 25 | |||
| 26 | /// Address the string-xref narrow window body reads once, or 0 when disarmed. | ||
| 27 | inline std::atomic<std::uintptr_t> g_scan_window_fault_for_test{0}; | ||
| 28 | |||
| 29 | /// Test callback invoked immediately before the narrow-window seam reads its armed address. | ||
| 30 | using ScanFaultPreparationForTest = void (*)(std::uintptr_t) noexcept; | ||
| 31 | |||
| 32 | /// Optional preparation that can reprotect the armed window address after the production gate has accepted it. | ||
| 33 | inline std::atomic<ScanFaultPreparationForTest> g_scan_window_fault_preparation_for_test{nullptr}; | ||
| 34 | |||
| 35 | /** | ||
| 36 | * @brief Reads @p slot's armed address once through a volatile lvalue, then disarms the slot. | ||
| 37 | * @param slot The armed-address storage to consume. | ||
| 38 | * @param preparation Optional one-shot callback invoked before the read. | ||
| 39 | * @details Disarming before the access keeps a swept region that is visited more than once from raising a | ||
| 40 | * second fault the proof's oracle would not expect. The read is volatile so no optimizer may drop the | ||
| 41 | * dereference that is the entire point of the seam. | ||
| 42 | */ | ||
| 43 | 26341 | inline void fire_scan_fault_seam_for_test( | |
| 44 | std::atomic<std::uintptr_t> &slot, | ||
| 45 | std::atomic<ScanFaultPreparationForTest> *preparation = nullptr | ||
| 46 | ) noexcept | ||
| 47 | { | ||
| 48 | 26341 | const std::uintptr_t address = slot.exchange(0, std::memory_order_acq_rel); | |
| 49 |
2/2✓ Branch 4 → 5 taken 26340 times.
✓ Branch 4 → 6 taken 1 time.
|
26341 | if (address == 0) |
| 50 | { | ||
| 51 | 26340 | return; | |
| 52 | } | ||
| 53 |
1/2✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 10 not taken.
|
1 | if (preparation != nullptr) |
| 54 | { | ||
| 55 | 1 | const ScanFaultPreparationForTest callback = preparation->exchange(nullptr, std::memory_order_acq_rel); | |
| 56 |
1/2✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
|
1 | if (callback != nullptr) |
| 57 | { | ||
| 58 | 1 | callback(address); | |
| 59 | } | ||
| 60 | } | ||
| 61 | ✗ | const auto *const probe = reinterpret_cast<volatile const std::uint8_t *>(address); | |
| 62 | ✗ | (void)*probe; | |
| 63 | } | ||
| 64 | } // namespace detail | ||
| 65 | } // namespace DetourModKit | ||
| 66 | |||
| 67 | #endif // DMK_ENABLE_TEST_SEAMS | ||
| 68 | |||
| 69 | #endif // DETOURMODKIT_INTERNAL_SCAN_FAULT_SEAM_HPP | ||
| 70 |