asar_file_validator.cc 4.7 KB

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