src/internal/input_delivery_scope.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * @file input_delivery_scope.cpp | ||
| 3 | * @brief Owned-Win32-TLS depth plus the stack-local teardown registry behind input_delivery_scope.hpp. | ||
| 4 | * @details Every ordinary failure mode reports "not recorded" to the constructing frame and nothing at all to other | ||
| 5 | * threads, so a gate can refuse the delivery it was about to run instead of the marker guessing on its | ||
| 6 | * behalf. The teardown registry has no failure mode: its node lives on the caller's stack and the list is a | ||
| 7 | * pointer splice under a statically initialized SRW lock. | ||
| 8 | */ | ||
| 9 | |||
| 10 | #include "internal/input_delivery_scope.hpp" | ||
| 11 | |||
| 12 | #include "internal/shared_tls_index.hpp" | ||
| 13 | |||
| 14 | #include <windows.h> | ||
| 15 | |||
| 16 | #include <array> | ||
| 17 | #include <atomic> | ||
| 18 | #include <cstddef> | ||
| 19 | #include <cstdint> | ||
| 20 | |||
| 21 | namespace DetourModKit | ||
| 22 | { | ||
| 23 | namespace detail | ||
| 24 | { | ||
| 25 | namespace | ||
| 26 | { | ||
| 27 | // Owned TLS slot that holds this thread's gate-delivery depth as an integer cast into the void* value. | ||
| 28 | constinit SharedTlsIndex s_depth_tls; | ||
| 29 | |||
| 30 | // Teardown registry. SRWLOCK_INIT is a constant initializer, so the lock is usable from the first | ||
| 31 | // teardown in the process without a dynamic initializer that could itself fail or order badly. | ||
| 32 | SRWLOCK s_teardown_lock = SRWLOCK_INIT; | ||
| 33 | TeardownRegistration *s_teardown_head{nullptr}; | ||
| 34 | // Lets the query skip the lock entirely while no teardown span is open, which is the ordinary case on the | ||
| 35 | // delivery path that also asks this question. | ||
| 36 | std::atomic<bool> s_teardown_open{false}; | ||
| 37 | |||
| 38 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 39 | DeliveryScopeReservationSeam s_reservation_seam{nullptr}; | ||
| 40 | // Native thread identities whose depth store must report failure. A fixed array keeps the seam exact and | ||
| 41 | // allocation-free while letting the cross-gate compositions refuse on two threads at once. | ||
| 42 | constexpr std::size_t MAX_STORE_FAILURE_THREADS = 4; | ||
| 43 | std::array<std::atomic<std::uint32_t>, MAX_STORE_FAILURE_THREADS> s_store_failure_threads{}; | ||
| 44 | |||
| 45 | 3284 | [[nodiscard]] bool store_failure_armed(std::uint32_t thread) noexcept | |
| 46 | { | ||
| 47 | 16370 | for (const std::atomic<std::uint32_t> &slot : s_store_failure_threads) | |
| 48 | { | ||
| 49 | 26199 | if (slot.load(std::memory_order_acquire) == thread) | |
| 50 | { | ||
| 51 | 13 | return true; | |
| 52 | } | ||
| 53 | } | ||
| 54 | 3270 | return false; | |
| 55 | } | ||
| 56 | #endif | ||
| 57 | |||
| 58 | 548 | void register_teardown(TeardownRegistration &node) noexcept | |
| 59 | { | ||
| 60 | 548 | ::AcquireSRWLockExclusive(&s_teardown_lock); | |
| 61 | 549 | node.next = s_teardown_head; | |
| 62 | 549 | s_teardown_head = &node; | |
| 63 | 549 | s_teardown_open.store(true, std::memory_order_release); | |
| 64 | 549 | ::ReleaseSRWLockExclusive(&s_teardown_lock); | |
| 65 | 549 | } | |
| 66 | |||
| 67 | 549 | void unregister_teardown(TeardownRegistration &node) noexcept | |
| 68 | { | ||
| 69 | 549 | ::AcquireSRWLockExclusive(&s_teardown_lock); | |
| 70 | 549 | for (TeardownRegistration **link = &s_teardown_head; *link != nullptr; link = &(*link)->next) | |
| 71 | { | ||
| 72 | 549 | if (*link == &node) | |
| 73 | { | ||
| 74 | 549 | *link = node.next; | |
| 75 | 549 | break; | |
| 76 | } | ||
| 77 | } | ||
| 78 | 549 | node.next = nullptr; | |
| 79 | 549 | s_teardown_open.store(s_teardown_head != nullptr, std::memory_order_release); | |
| 80 | 549 | ::ReleaseSRWLockExclusive(&s_teardown_lock); | |
| 81 | 549 | } | |
| 82 | |||
| 83 | 13217 | [[nodiscard]] bool teardown_registered(std::uint32_t thread) noexcept | |
| 84 | { | ||
| 85 | 13217 | if (!s_teardown_open.load(std::memory_order_acquire)) | |
| 86 | { | ||
| 87 | 13197 | return false; | |
| 88 | } | ||
| 89 | 20 | bool found = false; | |
| 90 | 20 | ::AcquireSRWLockShared(&s_teardown_lock); | |
| 91 | 29 | for (const TeardownRegistration *node = s_teardown_head; node != nullptr; node = node->next) | |
| 92 | { | ||
| 93 | 22 | if (node->thread == thread) | |
| 94 | { | ||
| 95 | 13 | found = true; | |
| 96 | 13 | break; | |
| 97 | } | ||
| 98 | } | ||
| 99 | 20 | ::ReleaseSRWLockShared(&s_teardown_lock); | |
| 100 | 20 | return found; | |
| 101 | } | ||
| 102 | |||
| 103 | // The one store whose failure a caller's correctness depends on. Routed through a single function so the | ||
| 104 | // seam sits exactly where the platform call does and cannot drift away from the branch it drives. | ||
| 105 | 3275 | [[nodiscard]] bool store_depth(DWORD index, std::uintptr_t depth) noexcept | |
| 106 | { | ||
| 107 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 108 | 3275 | if (store_failure_armed(static_cast<std::uint32_t>(::GetCurrentThreadId()))) | |
| 109 | { | ||
| 110 | 13 | return false; | |
| 111 | } | ||
| 112 | #endif | ||
| 113 | 3261 | return ::TlsSetValue(index, reinterpret_cast<void *>(depth)) != FALSE; | |
| 114 | } | ||
| 115 | |||
| 116 | 13562 | [[nodiscard]] bool depth_recorded_for_this_thread() noexcept | |
| 117 | { | ||
| 118 | 13562 | const DWORD index = s_depth_tls.index(); | |
| 119 | 13559 | if (index == TLS_OUT_OF_INDEXES) | |
| 120 | { | ||
| 121 | 2919 | return false; | |
| 122 | } | ||
| 123 | 10640 | return reinterpret_cast<std::uintptr_t>(::TlsGetValue(index)) != 0; | |
| 124 | } | ||
| 125 | } // namespace | ||
| 126 | |||
| 127 | 1968 | DeliveryTlsOwner::DeliveryTlsOwner() noexcept | |
| 128 | { | ||
| 129 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 130 | 1968 | if (s_reservation_seam != nullptr && s_depth_tls.index() == TLS_OUT_OF_INDEXES) | |
| 131 | { | ||
| 132 | 2 | s_reservation_seam(); | |
| 133 | } | ||
| 134 | #endif | ||
| 135 | 1968 | m_reserved = s_depth_tls.acquire() != TLS_OUT_OF_INDEXES; | |
| 136 | 1968 | } | |
| 137 | |||
| 138 | 1953 | DeliveryTlsOwner::~DeliveryTlsOwner() noexcept | |
| 139 | { | ||
| 140 | 1953 | s_depth_tls.release(); | |
| 141 | 1958 | } | |
| 142 | |||
| 143 | 15146 | std::uint32_t current_native_thread_id() noexcept | |
| 144 | { | ||
| 145 | 15146 | return static_cast<std::uint32_t>(::GetCurrentThreadId()); | |
| 146 | } | ||
| 147 | |||
| 148 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 149 | 2 | void set_delivery_scope_reservation_seam_for_test(DeliveryScopeReservationSeam seam) noexcept | |
| 150 | { | ||
| 151 | 2 | s_reservation_seam = seam; | |
| 152 | 2 | } | |
| 153 | |||
| 154 | 12 | DWORD delivery_scope_tls_index_for_test() noexcept | |
| 155 | { | ||
| 156 | 12 | return s_depth_tls.index(); | |
| 157 | } | ||
| 158 | |||
| 159 | 2 | std::uint32_t delivery_scope_tls_owners_for_test() noexcept | |
| 160 | { | ||
| 161 | 2 | return s_depth_tls.owners(); | |
| 162 | } | ||
| 163 | |||
| 164 | 20 | bool set_delivery_scope_store_failure_for_test(bool fail) noexcept | |
| 165 | { | ||
| 166 | 20 | const auto thread = static_cast<std::uint32_t>(::GetCurrentThreadId()); | |
| 167 | 20 | if (!fail) | |
| 168 | { | ||
| 169 | 13 | for (std::atomic<std::uint32_t> &slot : s_store_failure_threads) | |
| 170 | { | ||
| 171 | 13 | std::uint32_t expected = thread; | |
| 172 | 26 | if (slot.compare_exchange_strong(expected, 0, std::memory_order_acq_rel, std::memory_order_relaxed)) | |
| 173 | { | ||
| 174 | 10 | return true; | |
| 175 | } | ||
| 176 | } | ||
| 177 | ✗ | return true; | |
| 178 | } | ||
| 179 | 10 | if (store_failure_armed(thread)) | |
| 180 | { | ||
| 181 | ✗ | return true; | |
| 182 | } | ||
| 183 | 13 | for (std::atomic<std::uint32_t> &slot : s_store_failure_threads) | |
| 184 | { | ||
| 185 | 13 | std::uint32_t expected = 0; | |
| 186 | 26 | if (slot.compare_exchange_strong( | |
| 187 | expected, | ||
| 188 | thread, | ||
| 189 | std::memory_order_acq_rel, | ||
| 190 | std::memory_order_relaxed | ||
| 191 | )) | ||
| 192 | { | ||
| 193 | 10 | return true; | |
| 194 | } | ||
| 195 | } | ||
| 196 | ✗ | return false; | |
| 197 | } | ||
| 198 | #endif | ||
| 199 | |||
| 200 | 13561 | bool current_thread_in_delivery() noexcept | |
| 201 | { | ||
| 202 | 13561 | if (depth_recorded_for_this_thread()) | |
| 203 | { | ||
| 204 | 341 | return true; | |
| 205 | } | ||
| 206 | 13220 | return teardown_registered(current_native_thread_id()); | |
| 207 | } | ||
| 208 | |||
| 209 | 3280 | DeliveryScope::DeliveryScope() noexcept : m_index(s_depth_tls.index()), m_admitted(false) | |
| 210 | { | ||
| 211 | // The enclosing gate owns the index, so it stays published until this frame ends. | ||
| 212 | 3280 | if (m_index == TLS_OUT_OF_INDEXES) | |
| 213 | { | ||
| 214 | 6 | return; | |
| 215 | } | ||
| 216 | 3274 | const DWORD index = m_index; | |
| 217 | 3274 | const auto depth = reinterpret_cast<std::uintptr_t>(::TlsGetValue(index)); | |
| 218 | // A store can fail for a high slot index whose lazily heap-allocated TEB expansion array cannot be grown | ||
| 219 | // under OOM. Leaving the depth understated would let a nested release wrongly conclude it is control-plane | ||
| 220 | // and block into the ABBA, so the frame is refused instead and the caller declines the delivery. | ||
| 221 | 3275 | if (!store_depth(index, depth + 1)) | |
| 222 | { | ||
| 223 | 14 | return; | |
| 224 | } | ||
| 225 | 3261 | m_admitted = true; | |
| 226 | } | ||
| 227 | |||
| 228 | 6529 | DeliveryScope::~DeliveryScope() noexcept | |
| 229 | { | ||
| 230 | 3274 | if (!m_admitted) | |
| 231 | { | ||
| 232 | 20 | return; | |
| 233 | } | ||
| 234 | 3254 | const auto depth = reinterpret_cast<std::uintptr_t>(::TlsGetValue(m_index)); | |
| 235 | // The matching push succeeded, so this thread's expansion array for the slot already exists and the store | ||
| 236 | // cannot fail for want of one. Floor at zero defensively. | ||
| 237 | 3255 | (void)::TlsSetValue(m_index, reinterpret_cast<void *>(depth > 0 ? depth - 1 : 0)); | |
| 238 | 3275 | } | |
| 239 | |||
| 240 | 549 | MandatoryDeliveryScope::MandatoryDeliveryScope() noexcept | |
| 241 | { | ||
| 242 | 549 | m_registration.thread = current_native_thread_id(); | |
| 243 | 548 | register_teardown(m_registration); | |
| 244 | 549 | } | |
| 245 | |||
| 246 | 549 | MandatoryDeliveryScope::~MandatoryDeliveryScope() noexcept | |
| 247 | { | ||
| 248 | 549 | unregister_teardown(m_registration); | |
| 249 | 549 | } | |
| 250 | } // namespace detail | ||
| 251 | } // namespace DetourModKit | ||
| 252 |