feat: add support for validating asar archives on macOS (#30667)
* feat: add support for validating asar archives on macOS * chore: fix lint * chore: update as per feedback * feat: switch implementation to asar integrity hash checks * feat: make ranged requests work with the asar file validator DataSourceFilter * chore: fix lint * chore: fix missing log include on non-darwin * fix: do not pull block size out of missing optional * fix: match ValidateOrDie symbol on non-darwin * chore: fix up asar specs by repacking archives * fix: maintain integrity chain, do not load file integrity if header integrity was not loaded * debug test * Update node-spec.ts * fix: initialize header_validated_ * chore: update PR per feedback * chore: update per feedback * build: use final asar module * Update fuses.json5
This commit is contained in:
parent
fcad531f2e
commit
57d088517c
35 changed files with 705 additions and 43 deletions
150
shell/browser/net/asar/asar_file_validator.cc
Normal file
150
shell/browser/net/asar/asar_file_validator.cc
Normal file
|
@ -0,0 +1,150 @@
|
|||
// Copyright (c) 2021 Slack Technologies, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/net/asar/asar_file_validator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/notreached.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "crypto/sha2.h"
|
||||
|
||||
namespace asar {
|
||||
|
||||
AsarFileValidator::AsarFileValidator(IntegrityPayload integrity,
|
||||
base::File file)
|
||||
: file_(std::move(file)), integrity_(std::move(integrity)) {
|
||||
current_block_ = 0;
|
||||
max_block_ = integrity_.blocks.size() - 1;
|
||||
}
|
||||
|
||||
void AsarFileValidator::OnRead(base::span<char> buffer,
|
||||
mojo::FileDataSource::ReadResult* result) {
|
||||
DCHECK(!done_reading_);
|
||||
|
||||
uint64_t buffer_size = result->bytes_read;
|
||||
|
||||
// Compute how many bytes we should hash, and add them to the current hash.
|
||||
uint32_t block_size = integrity_.block_size;
|
||||
uint64_t bytes_added = 0;
|
||||
while (bytes_added < buffer_size) {
|
||||
if (current_block_ > max_block_) {
|
||||
LOG(FATAL)
|
||||
<< "Unexpected number of blocks while validating ASAR file stream";
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a hash if we don't have one yet
|
||||
if (!current_hash_) {
|
||||
current_hash_byte_count_ = 0;
|
||||
switch (integrity_.algorithm) {
|
||||
case HashAlgorithm::SHA256:
|
||||
current_hash_ =
|
||||
crypto::SecureHash::Create(crypto::SecureHash::SHA256);
|
||||
break;
|
||||
case HashAlgorithm::NONE:
|
||||
CHECK(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute how many bytes we should hash, and add them to the current hash.
|
||||
// We need to either add just enough bytes to fill up a block (block_size -
|
||||
// current_bytes) or use every remaining byte (buffer_size - bytes_added)
|
||||
int bytes_to_hash = std::min(block_size - current_hash_byte_count_,
|
||||
buffer_size - bytes_added);
|
||||
DCHECK_GT(bytes_to_hash, 0);
|
||||
current_hash_->Update(buffer.data() + bytes_added, bytes_to_hash);
|
||||
bytes_added += bytes_to_hash;
|
||||
current_hash_byte_count_ += bytes_to_hash;
|
||||
total_hash_byte_count_ += bytes_to_hash;
|
||||
|
||||
if (current_hash_byte_count_ == block_size && !FinishBlock()) {
|
||||
LOG(FATAL) << "Failed to validate block while streaming ASAR file: "
|
||||
<< current_block_;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AsarFileValidator::FinishBlock() {
|
||||
if (current_hash_byte_count_ == 0) {
|
||||
if (!done_reading_ || current_block_ > max_block_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!current_hash_) {
|
||||
// This happens when we fail to read the resource. Compute empty content's
|
||||
// hash in this case.
|
||||
current_hash_ = crypto::SecureHash::Create(crypto::SecureHash::SHA256);
|
||||
}
|
||||
|
||||
uint8_t actual[crypto::kSHA256Length];
|
||||
|
||||
// If the file reader is done we need to make sure we've either read up to the
|
||||
// end of the file (the check below) or up to the end of a block_size byte
|
||||
// boundary. If the below check fails we compute the next block boundary, how
|
||||
// many bytes are needed to get there and then we manually read those bytes
|
||||
// from our own file handle ensuring the data producer is unaware but we can
|
||||
// validate the hash still.
|
||||
if (done_reading_ &&
|
||||
total_hash_byte_count_ - extra_read_ != read_max_ - read_start_) {
|
||||
uint64_t bytes_needed = std::min(
|
||||
integrity_.block_size - current_hash_byte_count_,
|
||||
read_max_ - read_start_ - total_hash_byte_count_ + extra_read_);
|
||||
uint64_t offset = read_start_ + total_hash_byte_count_ - extra_read_;
|
||||
std::vector<uint8_t> abandoned_buffer(bytes_needed);
|
||||
if (!file_.ReadAndCheck(offset, abandoned_buffer)) {
|
||||
LOG(FATAL) << "Failed to read required portion of streamed ASAR archive";
|
||||
return false;
|
||||
}
|
||||
|
||||
current_hash_->Update(&abandoned_buffer.front(), bytes_needed);
|
||||
}
|
||||
|
||||
current_hash_->Finish(actual, sizeof(actual));
|
||||
current_hash_.reset();
|
||||
current_hash_byte_count_ = 0;
|
||||
|
||||
const std::string expected_hash = integrity_.blocks[current_block_];
|
||||
const std::string actual_hex_hash =
|
||||
base::ToLowerASCII(base::HexEncode(actual, sizeof(actual)));
|
||||
|
||||
if (expected_hash != actual_hex_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
current_block_++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsarFileValidator::OnDone() {
|
||||
DCHECK(!done_reading_);
|
||||
done_reading_ = true;
|
||||
if (!FinishBlock()) {
|
||||
LOG(FATAL) << "Failed to validate block while ending ASAR file stream: "
|
||||
<< current_block_;
|
||||
}
|
||||
}
|
||||
|
||||
void AsarFileValidator::SetRange(uint64_t read_start,
|
||||
uint64_t extra_read,
|
||||
uint64_t read_max) {
|
||||
read_start_ = read_start;
|
||||
extra_read_ = extra_read;
|
||||
read_max_ = read_max;
|
||||
}
|
||||
|
||||
void AsarFileValidator::SetCurrentBlock(int current_block) {
|
||||
current_block_ = current_block;
|
||||
}
|
||||
|
||||
} // namespace asar
|
Loading…
Add table
Add a link
Reference in a new issue