asar_file_validator.cc 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. // Copyright (c) 2021 Slack Technologies, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/net/asar/asar_file_validator.h"
  5. #include <algorithm>
  6. #include <array>
  7. #include <string>
  8. #include <utility>
  9. #include <vector>
  10. #include "base/containers/span.h"
  11. #include "base/logging.h"
  12. #include "base/notreached.h"
  13. #include "base/strings/string_number_conversions.h"
  14. #include "base/strings/string_util.h"
  15. #include "crypto/sha2.h"
  16. namespace asar {
  17. AsarFileValidator::AsarFileValidator(IntegrityPayload integrity,
  18. base::File file)
  19. : file_(std::move(file)), integrity_(std::move(integrity)) {
  20. current_block_ = 0;
  21. max_block_ = integrity_.blocks.size() - 1;
  22. }
  23. AsarFileValidator::~AsarFileValidator() = default;
  24. void AsarFileValidator::EnsureBlockHashExists() {
  25. if (current_hash_)
  26. return;
  27. current_hash_byte_count_ = 0U;
  28. switch (integrity_.algorithm) {
  29. case HashAlgorithm::kSHA256:
  30. current_hash_ = crypto::SecureHash::Create(crypto::SecureHash::SHA256);
  31. break;
  32. case HashAlgorithm::kNone:
  33. NOTREACHED();
  34. }
  35. }
  36. void AsarFileValidator::OnRead(base::span<char> buffer,
  37. mojo::FileDataSource::ReadResult* result) {
  38. DCHECK(!done_reading_);
  39. const uint32_t block_size = integrity_.block_size;
  40. // |buffer| contains the read buffer. |result->bytes_read| is the actual
  41. // bytes number that |source| read that should be less than buffer.size().
  42. auto hashme = base::as_bytes(
  43. buffer.subspan(0U, static_cast<size_t>(result->bytes_read)));
  44. while (!std::empty(hashme)) {
  45. if (current_block_ > max_block_)
  46. LOG(FATAL) << "Unexpected block count while validating ASAR file stream";
  47. EnsureBlockHashExists();
  48. // hash as many bytes as will fit in the current block.
  49. const auto n_left_in_block = block_size - current_hash_byte_count_;
  50. const auto n_now =
  51. std::min(static_cast<size_t>(n_left_in_block), std::size(hashme));
  52. DCHECK_GT(n_now, 0U);
  53. const auto [hashme_now, hashme_next] = hashme.split_at(n_now);
  54. current_hash_->Update(hashme_now);
  55. current_hash_byte_count_ += n_now;
  56. total_hash_byte_count_ += n_now;
  57. if (current_hash_byte_count_ == block_size && !FinishBlock())
  58. LOG(FATAL) << "Streaming ASAR file block hash failed: " << current_block_;
  59. hashme = hashme_next;
  60. }
  61. }
  62. bool AsarFileValidator::FinishBlock() {
  63. if (current_hash_byte_count_ == 0) {
  64. if (!done_reading_ || current_block_ > max_block_) {
  65. return true;
  66. }
  67. }
  68. if (!current_hash_) {
  69. // This happens when we fail to read the resource. Compute empty content's
  70. // hash in this case.
  71. current_hash_ = crypto::SecureHash::Create(crypto::SecureHash::SHA256);
  72. }
  73. // If the file reader is done we need to make sure we've either read up to the
  74. // end of the file (the check below) or up to the end of a block_size byte
  75. // boundary. If the below check fails we compute the next block boundary, how
  76. // many bytes are needed to get there and then we manually read those bytes
  77. // from our own file handle ensuring the data producer is unaware but we can
  78. // validate the hash still.
  79. if (done_reading_ &&
  80. total_hash_byte_count_ - extra_read_ != read_max_ - read_start_) {
  81. uint64_t bytes_needed = std::min(
  82. integrity_.block_size - current_hash_byte_count_,
  83. read_max_ - read_start_ - total_hash_byte_count_ + extra_read_);
  84. uint64_t offset = read_start_ + total_hash_byte_count_ - extra_read_;
  85. std::vector<uint8_t> abandoned_buffer(bytes_needed);
  86. if (!file_.ReadAndCheck(offset, abandoned_buffer)) {
  87. LOG(FATAL) << "Failed to read required portion of streamed ASAR archive";
  88. }
  89. current_hash_->Update(abandoned_buffer);
  90. }
  91. auto actual = std::array<uint8_t, crypto::kSHA256Length>{};
  92. current_hash_->Finish(actual);
  93. current_hash_.reset();
  94. current_hash_byte_count_ = 0;
  95. const auto& expected_hash = integrity_.blocks[current_block_];
  96. const auto actual_hex_hash = base::ToLowerASCII(base::HexEncode(actual));
  97. if (expected_hash != actual_hex_hash)
  98. return false;
  99. current_block_++;
  100. return true;
  101. }
  102. void AsarFileValidator::OnDone() {
  103. DCHECK(!done_reading_);
  104. done_reading_ = true;
  105. if (!FinishBlock()) {
  106. LOG(FATAL) << "Failed to validate block while ending ASAR file stream: "
  107. << current_block_;
  108. }
  109. }
  110. void AsarFileValidator::SetRange(uint64_t read_start,
  111. uint64_t extra_read,
  112. uint64_t read_max) {
  113. read_start_ = read_start;
  114. extra_read_ = extra_read;
  115. read_max_ = read_max;
  116. }
  117. void AsarFileValidator::SetCurrentBlock(int current_block) {
  118. current_block_ = current_block;
  119. }
  120. } // namespace asar