region_private.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #include <array>
  2. #include <atomic>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <cstdio>
  6. #include <intrin.h>
  7. #include <string_view>
  8. #include "../../../core/logging/log.h"
  9. #include "../../../core/settings/settings.h"
  10. #include "../../../state/activity/forced/activity_forced_destination.h"
  11. #include "../../hooking/detour.h"
  12. #include "internal.h"
  13. namespace sunrise::client::hooks::bootflow {
  14. namespace {
  15. /**
  16. * The bubble public-flag reader. The pattern is its whole body: a call to the state-byte getter,
  17. * then a cmovnz that turns the byte into a bool.
  18. */
  19. constexpr std::string_view kReaderSignatureText =
  20. "48 83 EC 28 E8 ? ? ? ? 48 8B C8 32 C0 48 85 C9 74 ? 80 39 00 BA 01 00 00 00 0F B6 C0 0F 45 "
  21. "C2 48 83 C4 28 C3";
  22. /** Compiled pattern bytes of the signature text above. */
  23. constexpr auto kReaderSignature =
  24. signature<signature_length(kReaderSignatureText)>(kReaderSignatureText);
  25. /**
  26. * The region transition starter. Anchored on its stack-cookie prologue and the read of the
  27. * manager's phase byte, which no other function pairs this way.
  28. */
  29. constexpr std::string_view kStarterSignatureText =
  30. "44 89 44 24 18 55 53 56 57 41 54 41 56 41 57 48 8D AC 24 ? ? ? ? 48 81 EC ? ? ? ? 48 8B 05 "
  31. "? ? ? ? 48 33 C4 48 89 85 ? ? ? ? 0F B6 81 09 02 00 00 4D 8B E1 FE C8 4C 63 F2 48 8B F1";
  32. /** Compiled pattern bytes of the signature text above. */
  33. constexpr auto kStarterSignature =
  34. signature<signature_length(kStarterSignatureText)>(kStarterSignatureText);
  35. /** `call rel32`, the encoding the starter uses to reach the reader. */
  36. constexpr std::byte kCallOpcode{0xE8};
  37. /** The call's displacement follows its opcode byte. */
  38. constexpr std::size_t kCallOperandOffset = 1;
  39. /** A near call is its opcode plus a signed 32-bit displacement. */
  40. constexpr std::size_t kCallLength = kCallOperandOffset + 4;
  41. /**
  42. * Bytes of the starter searched for that call. The body is shorter than this, and the search
  43. * needs one match, so a second hit fails the install instead of picking one.
  44. */
  45. constexpr std::size_t kStarterSearchBytes = 0x600;
  46. /** Lines allowed per run. Region transitions are rare, so this shows every one a boot makes. */
  47. constexpr unsigned kMaxReports = 8;
  48. /** Size of one line, set by its stage and slice-set fields. */
  49. constexpr std::size_t kLineCapacity = 96;
  50. using Reader = bool(__fastcall*)(std::uint32_t);
  51. hooking::detour::Handle g_handle{};
  52. std::atomic<Reader> g_original{nullptr};
  53. std::atomic<const std::byte*> g_returnSite{nullptr};
  54. std::atomic<unsigned> g_forced{0};
  55. /**
  56. * Finds the return address of the starter's own call to the reader.
  57. * A stray opcode byte inside another instruction can decode to the reader, so the whole window is
  58. * swept and an unclear result is rejected.
  59. * @return Address after the single matching call, or null when there is not exactly one.
  60. */
  61. [[nodiscard]] const std::byte* find_return_site(const std::byte* starter,
  62. const std::byte* reader) noexcept {
  63. const std::byte* found = nullptr;
  64. for (std::size_t offset = 0; offset + kCallLength <= kStarterSearchBytes; ++offset) {
  65. const std::byte* const site = starter + offset;
  66. if (*site != kCallOpcode) {
  67. continue;
  68. }
  69. const std::byte* const next = site + kCallLength;
  70. if (resolve_relative(site + kCallOperandOffset, next) != reader) {
  71. continue;
  72. }
  73. if (found != nullptr) {
  74. return nullptr;
  75. }
  76. found = next;
  77. }
  78. return found;
  79. }
  80. /**
  81. * Emits one decision event while the per-run budget lasts. Only a public bubble reaches here.
  82. * @param sliceSet Slice-set index whose bubble the reader called public.
  83. * @param forced True when the answer was replaced, false when the region stays public.
  84. */
  85. void report(std::uint32_t sliceSet, bool forced) noexcept {
  86. // One atomic claim per line, so a concurrent transition cannot reuse a budget slot.
  87. if (g_forced.fetch_add(1, std::memory_order_relaxed) >= kMaxReports) {
  88. return;
  89. }
  90. std::array<char, kLineCapacity> line{};
  91. const int written = std::snprintf(line.data(),
  92. line.size(),
  93. "ev=bootflow stage=region result=%s slice_set=%u",
  94. forced ? "forced" : "public",
  95. static_cast<unsigned>(sliceSet));
  96. if (written > 0) {
  97. core::log::write(core::log::Channel::client,
  98. core::log::Level::info,
  99. {line.data(), static_cast<std::size_t>(written)});
  100. }
  101. }
  102. /**
  103. * Reports a bubble as private, for the region transition's own call only.
  104. * A public region holds its slice-set switch until a public activity host connects. The answer
  105. * is public unless `client.region_private` is on, or a destination is forced.
  106. * @return False on the starter's call, otherwise the reader's own answer.
  107. */
  108. __declspec(noinline) bool __fastcall reader(std::uint32_t sliceSet) noexcept {
  109. const Reader original = g_original.load(std::memory_order_acquire);
  110. // The detour is live for a few instructions before install publishes its trampoline.
  111. if (original == nullptr) {
  112. return false;
  113. }
  114. if (!original(sliceSet)) {
  115. return false;
  116. }
  117. const auto* const caller = static_cast<const std::byte*>(_ReturnAddress());
  118. if (caller != g_returnSite.load(std::memory_order_acquire)) {
  119. return true;
  120. }
  121. // No public host serves a forced destination, so that run waits forever. It must load solo.
  122. const bool forced =
  123. core::settings::get().client.regionPrivate || state::activity::forced::override_active();
  124. report(sliceSet, forced);
  125. return !forced;
  126. }
  127. /** @param reason Key naming the step that failed. @return False, for a direct return. */
  128. [[nodiscard]] bool fail(const char* reason) noexcept {
  129. std::array<char, kLineCapacity> line{};
  130. const int written = std::snprintf(
  131. line.data(), line.size(), "ev=bootflow stage=region result=fail reason=%s", reason);
  132. if (written > 0) {
  133. core::log::write(core::log::Channel::client,
  134. core::log::Level::warn,
  135. {line.data(), static_cast<std::size_t>(written)});
  136. }
  137. return false;
  138. }
  139. } // namespace
  140. /** Attaches the private-region force. */
  141. bool install_region_private() noexcept {
  142. if (g_handle.attached) {
  143. return true;
  144. }
  145. std::byte* const target = scan_main_image_unique(kReaderSignature, "slice_set_is_public");
  146. if (target == nullptr) {
  147. return fail("reader");
  148. }
  149. const std::byte* const starter =
  150. scan_main_image_unique(kStarterSignature, "region_start_transition");
  151. if (starter == nullptr) {
  152. return fail("starter");
  153. }
  154. const std::byte* const returnSite = find_return_site(starter, target);
  155. if (returnSite == nullptr) {
  156. return fail("call_site");
  157. }
  158. // Published before the detour attaches, so the first call already has its filter.
  159. g_returnSite.store(returnSite, std::memory_order_release);
  160. const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&reader)};
  161. if (!hooking::detour::install(spec, g_handle)) {
  162. return fail("attach");
  163. }
  164. g_original.store(reinterpret_cast<Reader>(g_handle.original), std::memory_order_release);
  165. core::log::write(
  166. core::log::Channel::client, core::log::Level::info, "ev=bootflow stage=region result=ok");
  167. return true;
  168. }
  169. /** Detaches the private-region force. */
  170. void uninstall_region_private() noexcept {
  171. if (g_handle.attached) {
  172. (void)hooking::detour::uninstall(g_handle);
  173. }
  174. g_original.store(nullptr, std::memory_order_release);
  175. g_returnSite.store(nullptr, std::memory_order_release);
  176. g_forced.store(0, std::memory_order_release);
  177. }
  178. } // namespace sunrise::client::hooks::bootflow