| Line |
Branch |
Exec |
Source |
| 1 |
|
|
#ifndef DETOURMODKIT_INTERNAL_HOOK_PATCH_WITNESS_HPP |
| 2 |
|
|
#define DETOURMODKIT_INTERNAL_HOOK_PATCH_WITNESS_HPP |
| 3 |
|
|
|
| 4 |
|
|
/** |
| 5 |
|
|
* @file hook_patch_witness.hpp |
| 6 |
|
|
* @brief Ownership witness for inline-hook patch windows. |
| 7 |
|
|
*/ |
| 8 |
|
|
|
| 9 |
|
|
#include "internal/hook_fault_boundary.hpp" |
| 10 |
|
|
#include "internal/memory_guarded.hpp" |
| 11 |
|
|
|
| 12 |
|
|
#include <algorithm> |
| 13 |
|
|
#include <array> |
| 14 |
|
|
#include <cstddef> |
| 15 |
|
|
#include <cstdint> |
| 16 |
|
|
#include <string_view> |
| 17 |
|
|
|
| 18 |
|
|
namespace DetourModKit::detail |
| 19 |
|
|
{ |
| 20 |
|
|
/** |
| 21 |
|
|
* @brief Who owns the bytes currently at an inline backend's patch window. |
| 22 |
|
|
* @details Original means no patch is installed; OwnedPatch means the bytes exactly match an encoding this backend |
| 23 |
|
|
* committed; Foreign means the readable window matches neither; Indeterminate means it could not be read. |
| 24 |
|
|
* LayeredOver means the window holds the patch of another coordinator layer, which an arm recaptures |
| 25 |
|
|
* (`[B-110]`). Only the arm witness reports it. |
| 26 |
|
|
*/ |
| 27 |
|
|
enum class PatchWitness : std::uint8_t |
| 28 |
|
|
{ |
| 29 |
|
|
Original, |
| 30 |
|
|
OwnedPatch, |
| 31 |
|
|
Foreign, |
| 32 |
|
|
Indeterminate, |
| 33 |
|
|
LayeredOver |
| 34 |
|
|
}; |
| 35 |
|
|
|
| 36 |
|
|
/// Names a patch witness for an operator-facing diagnostic. |
| 37 |
|
280 |
[[nodiscard]] constexpr std::string_view witness_description(PatchWitness witness) noexcept |
| 38 |
|
|
{ |
| 39 |
4/6
✓ Branch 2 → 3 taken 7 times.
✓ Branch 2 → 4 taken 59 times.
✓ Branch 2 → 5 taken 209 times.
✗ Branch 2 → 6 not taken.
✓ Branch 2 → 7 taken 5 times.
✗ Branch 2 → 8 not taken.
|
280 |
switch (witness) |
| 40 |
|
|
{ |
| 41 |
|
7 |
case PatchWitness::Original: |
| 42 |
|
7 |
return "prologue is original"; |
| 43 |
|
59 |
case PatchWitness::OwnedPatch: |
| 44 |
|
59 |
return "our patch is still installed"; |
| 45 |
|
209 |
case PatchWitness::Foreign: |
| 46 |
|
209 |
return "another writer owns the prologue"; |
| 47 |
|
✗ |
case PatchWitness::LayeredOver: |
| 48 |
|
✗ |
return "another layer owns the prologue"; |
| 49 |
|
5 |
case PatchWitness::Indeterminate: |
| 50 |
|
5 |
break; |
| 51 |
|
|
} |
| 52 |
|
5 |
return "prologue could not be read"; |
| 53 |
|
|
} |
| 54 |
|
|
|
| 55 |
|
|
/** |
| 56 |
|
|
* @brief Reads a backend target and classifies ownership of its patch window. |
| 57 |
|
|
* @details OwnedPatch requires both exact bytes and persistent provenance that the backend completed a patch-byte |
| 58 |
|
|
* capture. Pre-sized or default-initialized storage is not evidence that the backend emitted those bytes. |
| 59 |
|
|
*/ |
| 60 |
|
76151 |
template <class Backend> [[nodiscard]] PatchWitness witness_patch(const Backend &backend) noexcept |
| 61 |
|
|
{ |
| 62 |
|
76151 |
const auto &original = backend.original_bytes(); |
| 63 |
|
76151 |
std::array<std::uint8_t, BACKEND_MAX_STEAL_WINDOW> current{}; |
| 64 |
8/16
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_patch<safetyhook::InlineHook>(safetyhook::InlineHook const&):
✓ Branch 4 → 5 taken 56085 times.
✗ Branch 4 → 11 not taken.
✓ Branch 8 → 9 taken 56085 times.
✗ Branch 8 → 11 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 56085 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 56085 times.
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_patch<safetyhook::MidHook>(safetyhook::MidHook const&):
✓ Branch 4 → 5 taken 20066 times.
✗ Branch 4 → 11 not taken.
✓ Branch 8 → 9 taken 20066 times.
✗ Branch 8 → 11 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 20066 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 20066 times.
|
152302 |
if (original.empty() || original.size() > current.size() || backend.target() == nullptr) |
| 65 |
|
|
{ |
| 66 |
|
✗ |
return PatchWitness::Indeterminate; |
| 67 |
|
|
} |
| 68 |
|
76151 |
const std::size_t count = original.size(); |
| 69 |
4/4
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_patch<safetyhook::InlineHook>(safetyhook::InlineHook const&):
✓ Branch 20 → 21 taken 8 times.
✓ Branch 20 → 22 taken 56077 times.
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_patch<safetyhook::MidHook>(safetyhook::MidHook const&):
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 22 taken 20065 times.
|
76151 |
if (!guarded_read_bytes(reinterpret_cast<std::uintptr_t>(backend.target()), current.data(), count)) |
| 70 |
|
|
{ |
| 71 |
|
9 |
return PatchWitness::Indeterminate; |
| 72 |
|
|
} |
| 73 |
|
76142 |
const auto span = static_cast<std::ptrdiff_t>(count); |
| 74 |
4/4
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_patch<safetyhook::InlineHook>(safetyhook::InlineHook const&):
✓ Branch 29 → 30 taken 20603 times.
✓ Branch 29 → 31 taken 35474 times.
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_patch<safetyhook::MidHook>(safetyhook::MidHook const&):
✓ Branch 29 → 30 taken 9023 times.
✓ Branch 29 → 31 taken 11042 times.
|
152284 |
if (std::equal(original.begin(), original.begin() + span, current.begin())) |
| 75 |
|
|
{ |
| 76 |
|
29626 |
return PatchWitness::Original; |
| 77 |
|
|
} |
| 78 |
|
46516 |
const auto &emitted = backend.patch_bytes(); |
| 79 |
6/8
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_patch<safetyhook::InlineHook>(safetyhook::InlineHook const&):
✓ Branch 33 → 34 taken 34463 times.
✓ Branch 33 → 45 taken 1011 times.
✓ Branch 35 → 36 taken 34463 times.
✗ Branch 35 → 45 not taken.
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_patch<safetyhook::MidHook>(safetyhook::MidHook const&):
✓ Branch 33 → 34 taken 10040 times.
✓ Branch 33 → 45 taken 1002 times.
✓ Branch 35 → 36 taken 10040 times.
✗ Branch 35 → 45 not taken.
|
91019 |
if (backend.patch_bytes_valid() && emitted.size() == count && |
| 80 |
8/8
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_patch<safetyhook::InlineHook>(safetyhook::InlineHook const&):
✓ Branch 43 → 44 taken 26084 times.
✓ Branch 43 → 45 taken 8379 times.
✓ Branch 46 → 47 taken 26084 times.
✓ Branch 46 → 48 taken 9390 times.
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_patch<safetyhook::MidHook>(safetyhook::MidHook const&):
✓ Branch 43 → 44 taken 10021 times.
✓ Branch 43 → 45 taken 19 times.
✓ Branch 46 → 47 taken 10021 times.
✓ Branch 46 → 48 taken 1021 times.
|
135522 |
std::equal(emitted.begin(), emitted.begin() + span, current.begin())) |
| 81 |
|
|
{ |
| 82 |
|
36105 |
return PatchWitness::OwnedPatch; |
| 83 |
|
|
} |
| 84 |
|
10411 |
return PatchWitness::Foreign; |
| 85 |
|
|
} |
| 86 |
|
|
|
| 87 |
|
|
/// Returns whether a witness authorizes the backend to overwrite the target. |
| 88 |
|
24048 |
[[nodiscard]] constexpr bool witness_permits_write(PatchWitness witness) noexcept |
| 89 |
|
|
{ |
| 90 |
4/4
✓ Branch 2 → 3 taken 23459 times.
✓ Branch 2 → 4 taken 589 times.
✓ Branch 3 → 4 taken 21646 times.
✓ Branch 3 → 5 taken 1813 times.
|
24048 |
return witness == PatchWitness::Original || witness == PatchWitness::OwnedPatch; |
| 91 |
|
|
} |
| 92 |
|
|
|
| 93 |
|
|
/// Returns whether an arm over @p witness makes the hook the newest layer of its target (`[B-110]`). |
| 94 |
|
15377 |
[[nodiscard]] constexpr bool witness_promotes_arm(PatchWitness witness) noexcept |
| 95 |
|
|
{ |
| 96 |
4/4
✓ Branch 2 → 3 taken 2033 times.
✓ Branch 2 → 4 taken 13344 times.
✓ Branch 3 → 4 taken 2018 times.
✓ Branch 3 → 5 taken 15 times.
|
15377 |
return witness == PatchWitness::Original || witness == PatchWitness::LayeredOver; |
| 97 |
|
|
} |
| 98 |
|
|
|
| 99 |
|
|
/** |
| 100 |
|
|
* @brief Classifies a target for an arm: @ref witness_patch, with LayeredOver in place of Foreign when the backend |
| 101 |
|
|
* reports the patch of another coordinator layer. |
| 102 |
|
|
* @note The backend query takes the process coordinator, so a caller that holds a target slot keeps the order of |
| 103 |
|
|
* `[B-109]`. |
| 104 |
|
|
*/ |
| 105 |
|
15377 |
template <class Backend> [[nodiscard]] PatchWitness witness_arm(const Backend &backend) noexcept |
| 106 |
|
|
{ |
| 107 |
|
15377 |
const PatchWitness witness = witness_patch(backend); |
| 108 |
8/8
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_arm<safetyhook::InlineHook>(safetyhook::InlineHook const&):
✓ Branch 3 → 4 taken 1017 times.
✓ Branch 3 → 6 taken 9353 times.
✓ Branch 5 → 6 taken 4 times.
✓ Branch 5 → 7 taken 1013 times.
DetourModKit::detail::PatchWitness DetourModKit::detail::witness_arm<safetyhook::MidHook>(safetyhook::MidHook const&):
✓ Branch 3 → 4 taken 1006 times.
✓ Branch 3 → 6 taken 4001 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 1005 times.
|
15377 |
return witness == PatchWitness::Foreign && backend.window_holds_layer() ? PatchWitness::LayeredOver : witness; |
| 109 |
|
|
} |
| 110 |
|
|
} // namespace DetourModKit::detail |
| 111 |
|
|
|
| 112 |
|
|
#endif // DETOURMODKIT_INTERNAL_HOOK_PATCH_WITNESS_HPP |
| 113 |
|
|
|