./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
51 / 55
92.7%
Branches:
50 / 62
80.6%
Functions:
7 / 7
100.0%

src/internal/hook_patch_witness.hpp
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