content_manifest_cache_reader.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. #include <Windows.h>
  2. #include <algorithm>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <limits>
  6. #include <span>
  7. #include "../content_manifest_row_validation.h"
  8. #include "../fingerprint/content_manifest_fingerprint.h"
  9. #include "format.h"
  10. #include "internal.h"
  11. namespace sunrise::state::content_manifest::cache {
  12. namespace {
  13. /**
  14. * Reads one fixed object, and refuses a short but successful Windows read.
  15. * @tparam Value Trivially copied cache object.
  16. * @param file Open cache handle at the required sequential offset.
  17. * @param output Cleared destination object.
  18. * @return True when every object byte is read.
  19. */
  20. template <typename Value> [[nodiscard]] bool read_exact(HANDLE file, Value& output) noexcept {
  21. output = {};
  22. DWORD copied = 0;
  23. return sizeof output <= (std::numeric_limits<DWORD>::max)()
  24. && ReadFile(file, &output, static_cast<DWORD>(sizeof output), &copied, nullptr) != FALSE
  25. && copied == sizeof output;
  26. }
  27. /** @param row Stable disk row. @param output Cleared native State row. */
  28. void decode_row(const DiskRow& row, Row& output) noexcept {
  29. output = {};
  30. output.name = row.name;
  31. output.nameLength = row.nameLength;
  32. output.packageId = row.packageId;
  33. output.buildSignature = row.buildSignature;
  34. }
  35. /**
  36. * Works out the one byte length allowed for a declared row count.
  37. * @param rowCount Bounded cache row count.
  38. * @param size Receives the whole cache byte length.
  39. * @return True when the multiply and add fit a signed Windows file size.
  40. */
  41. [[nodiscard]] bool expected_size(std::uint32_t rowCount, std::uint64_t& size) noexcept {
  42. size = 0;
  43. if (rowCount == 0 || rowCount > kRowCapacity) {
  44. return false;
  45. }
  46. const std::uint64_t payload = static_cast<std::uint64_t>(rowCount) * sizeof(DiskRow);
  47. if (payload
  48. > static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) - sizeof(Header)) {
  49. return false;
  50. }
  51. size = sizeof(Header) + payload;
  52. return true;
  53. }
  54. /** @param rows Output storage. @param count Used prefix length. */
  55. void clear_rows(std::span<Row> rows, std::size_t& count) noexcept {
  56. count = 0;
  57. if (!rows.empty()) {
  58. SecureZeroMemory(rows.data(), rows.size_bytes());
  59. }
  60. }
  61. } // namespace
  62. /** Loads and checks one exact cache into fixed caller storage. */
  63. LoadStatus load(const wchar_t* path,
  64. const Fingerprint& directoryFingerprint,
  65. std::span<Row> rows,
  66. std::size_t& count,
  67. Fingerprint& buildFingerprint,
  68. Guid& guidValue) noexcept {
  69. clear_rows(rows, count);
  70. buildFingerprint = {};
  71. guidValue = {};
  72. if (path == nullptr || rows.size() < kRowCapacity) {
  73. return LoadStatus::invalid;
  74. }
  75. const HANDLE file = CreateFileW(path,
  76. GENERIC_READ,
  77. FILE_SHARE_READ,
  78. nullptr,
  79. OPEN_EXISTING,
  80. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
  81. nullptr);
  82. if (file == INVALID_HANDLE_VALUE) {
  83. const DWORD error = GetLastError();
  84. return error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND ? LoadStatus::missing
  85. : LoadStatus::invalid;
  86. }
  87. Header header{};
  88. LARGE_INTEGER fileSize{};
  89. std::uint64_t exactSize = 0;
  90. bool complete = read_exact(file, header) && GetFileSizeEx(file, &fileSize) != FALSE
  91. && fileSize.QuadPart >= 0 && header.magic == kCacheMagic
  92. && header.version == kCacheVersion && header.headerSize == sizeof(Header)
  93. && header.rowSize == sizeof(DiskRow) && header.reserved == 0
  94. && expected_size(header.rowCount, exactSize)
  95. && static_cast<std::uint64_t>(fileSize.QuadPart) == exactSize;
  96. if (!complete) {
  97. CloseHandle(file);
  98. return LoadStatus::invalid;
  99. }
  100. if (header.directoryFingerprint != directoryFingerprint) {
  101. CloseHandle(file);
  102. return LoadStatus::stale;
  103. }
  104. for (std::uint32_t index = 0; index < header.rowCount; ++index) {
  105. DiskRow diskRow{};
  106. if (!read_exact(file, diskRow)) {
  107. complete = false;
  108. break;
  109. }
  110. decode_row(diskRow, rows[index]);
  111. }
  112. CloseHandle(file);
  113. if (!complete) {
  114. clear_rows(rows, count);
  115. return LoadStatus::invalid;
  116. }
  117. count = header.rowCount;
  118. const std::span<const Row> occupied = rows.first(count);
  119. Fingerprint computed{};
  120. if (!valid(occupied) || !fingerprint::catalog(occupied, directoryFingerprint, computed)
  121. || computed != header.buildFingerprint) {
  122. clear_rows(rows, count);
  123. return LoadStatus::invalid;
  124. }
  125. buildFingerprint = computed;
  126. fingerprint::guid(buildFingerprint, guidValue);
  127. return LoadStatus::loaded;
  128. }
  129. } // namespace sunrise::state::content_manifest::cache