activity_sdk_loader.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #include <Windows.h>
  2. #include <array>
  3. #include <cstring>
  4. #include <limits>
  5. #include <string_view>
  6. #include "../../core/filesystem/path.h"
  7. #include "../../middleware/crypto/sha256.h"
  8. #include "internal.h"
  9. namespace sunrise::state::activity_sdk {
  10. namespace {
  11. /** The SDK pack is installed beneath the module directory without creating it at read time. */
  12. constexpr std::wstring_view kPackSuffix = L"Sunrise\\activity_sdk.pack";
  13. /** Each section stride is fixed by the current runtime-pack ABI. */
  14. constexpr std::array<std::uint32_t, format::kSectionCount> kExpectedStrides{
  15. 1,
  16. sizeof(format::Activity),
  17. sizeof(format::Scenario),
  18. sizeof(format::Bubble),
  19. sizeof(format::State),
  20. sizeof(format::Object),
  21. sizeof(format::Occurrence),
  22. sizeof(format::Slot),
  23. sizeof(format::Text),
  24. sizeof(format::Capability),
  25. sizeof(format::Gate),
  26. sizeof(format::Refusal),
  27. sizeof(format::ActorClass),
  28. sizeof(format::RsatDescriptor),
  29. sizeof(format::RsatSchema),
  30. sizeof(format::RsatField),
  31. sizeof(format::Squad),
  32. sizeof(format::SquadMember),
  33. sizeof(format::SquadAnchor),
  34. sizeof(format::AuthoredSceneResource),
  35. sizeof(format::AuthoredSceneSquadEdge),
  36. sizeof(format::TaskTarget),
  37. sizeof(format::DialogueCueText),
  38. sizeof(format::DirectiveElement),
  39. sizeof(format::ActivityBindingTag),
  40. sizeof(format::ActivityBindingLocator),
  41. sizeof(format::BehaviorProgram),
  42. sizeof(format::BehaviorInput),
  43. sizeof(format::BehaviorChannelWrite),
  44. sizeof(format::BehaviorOwner),
  45. sizeof(format::BehaviorActivityBinding),
  46. sizeof(format::ActorMessageSchema),
  47. sizeof(format::ActorCommandDefinition),
  48. sizeof(format::ActorBehaviorProfile),
  49. sizeof(format::SimulationEventDefinition),
  50. sizeof(format::RuntimeSchema),
  51. sizeof(format::RuntimeField),
  52. sizeof(format::SobjectRsat),
  53. sizeof(format::SobjectRsatDescriptor),
  54. sizeof(format::EntityTypeDefinition),
  55. sizeof(format::SobjectRsatFieldBinding),
  56. sizeof(format::RuntimeTypeDefinition),
  57. sizeof(format::ActorStateName),
  58. sizeof(format::ActorSequenceTable),
  59. sizeof(format::ActorSequenceEntry),
  60. sizeof(format::ActorSequenceBinding),
  61. sizeof(format::DialogueCue),
  62. sizeof(format::CombatObjectiveGroup),
  63. sizeof(format::ActorAbility),
  64. sizeof(format::ActorAbilityTarget),
  65. sizeof(format::AuthoredSceneEventKey),
  66. };
  67. /** Checks all section bounds before any typed row pointer is formed. */
  68. [[nodiscard]] bool valid_sections(const format::Header& header) noexcept {
  69. std::uint64_t priorEnd = header.headerSize;
  70. for (std::size_t index = 0; index < header.sections.size(); ++index) {
  71. const format::Section& section = header.sections[index];
  72. const std::uint64_t stride = kExpectedStrides[index];
  73. if (section.stride != stride
  74. || section.count > (std::numeric_limits<std::uint64_t>::max)() / stride) {
  75. return false;
  76. }
  77. const std::uint64_t bytes = static_cast<std::uint64_t>(section.count) * stride;
  78. if (section.offset != priorEnd || section.offset > header.fileSize
  79. || bytes > header.fileSize - section.offset) {
  80. return false;
  81. }
  82. priorEnd = section.offset + bytes;
  83. }
  84. return priorEnd == header.fileSize;
  85. }
  86. /** Checks every compiled provenance pin and hashes the exact mapped payload. */
  87. [[nodiscard]] bool valid_header(const format::Header& header,
  88. std::span<const std::byte> file,
  89. const ExpectedIdentity& expected,
  90. Status& result) noexcept {
  91. if (header.magic != format::kMagic || header.version != format::kVersion
  92. || header.headerSize != sizeof(format::Header) || header.fileSize != file.size()
  93. || header.sectionCount != format::kSectionCount || header.reserved != 0
  94. || !valid_sections(header)) {
  95. return false;
  96. }
  97. if (header.sdkBuildSha256 != expected.sdkBuildSha256) {
  98. result = Status::wrongSdkBuild;
  99. return false;
  100. }
  101. if (header.payloadSha256 != expected.payloadSha256
  102. || header.contentKeySha256 != expected.contentKeySha256
  103. || header.logicalIrSha256 != expected.logicalIrSha256) {
  104. return false;
  105. }
  106. middleware::crypto::sha256::Digest digest{};
  107. const auto payload = file.subspan(header.headerSize);
  108. return middleware::crypto::sha256::hash(payload, digest) && digest == expected.payloadSha256;
  109. }
  110. /** Converts an exact Windows file size to process address space. */
  111. [[nodiscard]] bool mapped_size(HANDLE file, std::size_t& output) noexcept {
  112. output = 0;
  113. LARGE_INTEGER size{};
  114. if (GetFileSizeEx(file, &size) == FALSE || size.QuadPart < sizeof(format::Header)
  115. || static_cast<std::uint64_t>(size.QuadPart)
  116. > static_cast<std::uint64_t>((std::numeric_limits<std::size_t>::max)())) {
  117. return false;
  118. }
  119. output = static_cast<std::size_t>(size.QuadPart);
  120. return true;
  121. }
  122. /** Resolves the exact parent directory before the pack mapping becomes public. */
  123. [[nodiscard]] bool pack_directory(const wchar_t* path, core::path::Buffer& output) noexcept {
  124. output = {};
  125. core::path::Buffer absolute;
  126. wchar_t* filePart = nullptr;
  127. const DWORD copied = GetFullPathNameW(
  128. path, static_cast<DWORD>(absolute.chars.size()), absolute.chars.data(), &filePart);
  129. if (copied == 0 || copied >= absolute.chars.size() || filePart == nullptr
  130. || filePart <= absolute.chars.data()) {
  131. return false;
  132. }
  133. std::size_t length = static_cast<std::size_t>(filePart - absolute.chars.data());
  134. while (length != 0
  135. && (absolute.chars[length - 1] == L'\\' || absolute.chars[length - 1] == L'/')) {
  136. --length;
  137. }
  138. if (length == 0) {
  139. return false;
  140. }
  141. return core::path::assign(output, std::wstring_view(absolute.chars.data(), length));
  142. }
  143. } // namespace
  144. /** Maps one explicit pack only after an independent expected identity is supplied. */
  145. bool load_path_expected(const wchar_t* path,
  146. const ExpectedIdentity& expected,
  147. std::shared_ptr<Catalog>& output,
  148. Status& result) noexcept {
  149. output.reset();
  150. result = Status::catalogInvalid;
  151. if (path == nullptr || path[0] == L'\0') {
  152. return false;
  153. }
  154. const HANDLE file = CreateFileW(path,
  155. GENERIC_READ,
  156. FILE_SHARE_READ | FILE_SHARE_DELETE,
  157. nullptr,
  158. OPEN_EXISTING,
  159. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
  160. nullptr);
  161. if (file == INVALID_HANDLE_VALUE) {
  162. const DWORD error = GetLastError();
  163. result = error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND
  164. ? Status::missing
  165. : Status::catalogInvalid;
  166. return false;
  167. }
  168. std::shared_ptr<Catalog> pending;
  169. try {
  170. pending = std::make_shared<Catalog>();
  171. } catch (...) {
  172. CloseHandle(file);
  173. return false;
  174. }
  175. pending->file_ = file;
  176. if (!pack_directory(path, pending->artifactDirectory_)) {
  177. return false;
  178. }
  179. if (!mapped_size(file, pending->size_)) {
  180. return false;
  181. }
  182. const HANDLE mapping = CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr);
  183. if (mapping == nullptr) {
  184. return false;
  185. }
  186. pending->mapping_ = mapping;
  187. const void* const view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
  188. if (view == nullptr) {
  189. return false;
  190. }
  191. pending->view_ = static_cast<const std::byte*>(view);
  192. pending->header_ = reinterpret_cast<const format::Header*>(pending->view_);
  193. const std::span<const std::byte> bytes(pending->view_, pending->size_);
  194. if (!identity::valid(expected.sdkBuildSha256) || !identity::valid(expected.payloadSha256)
  195. || !identity::valid(expected.contentKeySha256) || !identity::valid(expected.logicalIrSha256)
  196. || !valid_header(*pending->header_, bytes, expected, result) || !valid_catalog(*pending)) {
  197. return false;
  198. }
  199. output = std::move(pending);
  200. result = Status::ready;
  201. return true;
  202. }
  203. #if defined(SUNRISE_ACTIVITY_SDK_TESTING)
  204. /** Loads the compile-pinned regression fixture without exposing that trust path in production. */
  205. bool load_path(const wchar_t* path, std::shared_ptr<Catalog>& output, Status& result) noexcept {
  206. const ExpectedIdentity expected{format::kExpectedSdkBuildDigest,
  207. format::kExpectedPayloadDigest,
  208. format::kExpectedContentKeyDigest,
  209. format::kExpectedLogicalIrDigest};
  210. return load_path_expected(path, expected, output, result);
  211. }
  212. /** Applies one scoped synthetic payload pin. Production builds have no such entry point. */
  213. bool load_path_for_test(const wchar_t* path,
  214. const std::array<std::byte, 32>& expectedPayloadSha256,
  215. std::shared_ptr<Catalog>& output,
  216. Status& result) noexcept {
  217. const ExpectedIdentity expected{format::kExpectedSdkBuildDigest,
  218. expectedPayloadSha256,
  219. format::kExpectedContentKeyDigest,
  220. format::kExpectedLogicalIrDigest};
  221. return load_path_expected(path, expected, output, result);
  222. }
  223. #endif
  224. /** Resolves the read-only installed pack path without creating its parent directory. */
  225. bool load(void* module,
  226. const ExpectedIdentity& expected,
  227. std::shared_ptr<Catalog>& output,
  228. Status& result) noexcept {
  229. core::path::Buffer path;
  230. if (!core::path::module_directory(module, path) || !core::path::append(path, kPackSuffix)) {
  231. output.reset();
  232. result = Status::catalogInvalid;
  233. return false;
  234. }
  235. return load_path_expected(path.chars.data(), expected, output, result);
  236. }
  237. } // namespace sunrise::state::activity_sdk