snapshot_storage.cpp 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. #include "snapshot_storage.h"
  2. #include <Windows.h>
  3. #include <algorithm>
  4. #include <cstdint>
  5. #include <span>
  6. namespace sunrise::server::bap::encrypted::push::snapshot {
  7. namespace {
  8. /**
  9. * Finds a borrowed payload's end offset without comparing unrelated pointers.
  10. * @param owner Scratch buffer that may own the payload.
  11. * @param endOffset Gets the first owner byte after the payload.
  12. * @return True when every payload byte belongs to the owner.
  13. */
  14. [[nodiscard]] bool contained_end(std::span<const std::byte> owner,
  15. std::span<const std::byte> payload,
  16. std::size_t& endOffset) noexcept {
  17. endOffset = 0;
  18. if (payload.empty()) {
  19. return true;
  20. }
  21. const auto ownerAddress = reinterpret_cast<std::uintptr_t>(owner.data());
  22. const auto payloadAddress = reinterpret_cast<std::uintptr_t>(payload.data());
  23. if (payloadAddress < ownerAddress) {
  24. return false;
  25. }
  26. const std::uintptr_t offset = payloadAddress - ownerAddress;
  27. if (offset > owner.size()) {
  28. return false;
  29. }
  30. const auto narrowedOffset = static_cast<std::size_t>(offset);
  31. if (payload.size() > owner.size() - narrowedOffset) {
  32. return false;
  33. }
  34. endOffset = narrowedOffset + payload.size();
  35. return true;
  36. }
  37. } // namespace
  38. /** Finds payload prefixes borrowed by a caller's earlier prepared snapshot. */
  39. Reservation reserve_prior(const Scratch& scratch, const Prepared& prepared) noexcept {
  40. const std::size_t objectCount = prepared.family.objects.size();
  41. if (prepared.rawClearSize > scratch.plaintext.size()
  42. || prepared.compressedClearSize > scratch.sealed.size()
  43. || objectCount > prepared.objects.size()
  44. || (objectCount != 0 && prepared.family.objects.data() != prepared.objects.data())) {
  45. return {};
  46. }
  47. Reservation reservation{
  48. 0,
  49. 0,
  50. prepared.rawClearSize,
  51. prepared.compressedClearSize,
  52. };
  53. const std::span<const std::byte> rawStorage{scratch.plaintext};
  54. const std::span<const std::byte> compressedStorage{scratch.sealed};
  55. for (const middleware::queuez::Object& object : prepared.family.objects) {
  56. if (object.payload.empty()) {
  57. continue;
  58. }
  59. std::size_t payloadEnd = 0;
  60. // Reserve the tail after each borrowed payload so staging cannot overwrite prior output.
  61. if (contained_end(rawStorage, object.payload, payloadEnd)) {
  62. reservation.rawWriteOffset = (std::max)(reservation.rawWriteOffset, payloadEnd);
  63. } else if (contained_end(compressedStorage, object.payload, payloadEnd)) {
  64. reservation.compressedWriteOffset =
  65. (std::max)(reservation.compressedWriteOffset, payloadEnd);
  66. } else {
  67. // A foreign payload makes the prior snapshot unsafe to keep.
  68. return {};
  69. }
  70. }
  71. if (reservation.rawWriteOffset > reservation.rawClearSize
  72. || reservation.compressedWriteOffset > reservation.compressedClearSize) {
  73. return {};
  74. }
  75. return reservation;
  76. }
  77. /** Wipes the staging tails but keeps a reserved prior snapshot valid. */
  78. void clear_after(Scratch& scratch, const Reservation& reservation) noexcept {
  79. if (reservation.rawWriteOffset < scratch.plaintext.size()) {
  80. SecureZeroMemory(scratch.plaintext.data() + reservation.rawWriteOffset,
  81. scratch.plaintext.size() - reservation.rawWriteOffset);
  82. }
  83. if (reservation.compressedWriteOffset < scratch.sealed.size()) {
  84. SecureZeroMemory(scratch.sealed.data() + reservation.compressedWriteOffset,
  85. scratch.sealed.size() - reservation.compressedWriteOffset);
  86. }
  87. }
  88. /** Publishes a finished snapshot and re-points its object span at the caller's storage. */
  89. bool commit(const Prepared& staged, Prepared& output) noexcept {
  90. const std::size_t objectCount = staged.family.objects.size();
  91. if (objectCount > staged.objects.size()
  92. || (objectCount != 0 && staged.family.objects.data() != staged.objects.data())) {
  93. return false;
  94. }
  95. output.objects = staged.objects;
  96. output.rawClearSize = staged.rawClearSize;
  97. output.compressedClearSize = staged.compressedClearSize;
  98. output.family = middleware::queuez::Family{
  99. staged.family.type,
  100. staged.family.rootSoid,
  101. staged.family.version,
  102. staged.family.flags,
  103. std::span(output.objects).first(objectCount),
  104. };
  105. return true;
  106. }
  107. } // namespace sunrise::server::bap::encrypted::push::snapshot