// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/zucchini/patch_reader.h" #include #include #include "base/numerics/safe_conversions.h" #include "components/zucchini/algorithm.h" #include "components/zucchini/crc32.h" #include "components/zucchini/element_detection.h" #include "components/zucchini/version_info.h" namespace zucchini { namespace patch { bool ParseElementMatch(BufferSource* source, ElementMatch* element_match) { PatchElementHeader unsafe_element_header; if (!source->GetValue(&unsafe_element_header)) { LOG(ERROR) << "Impossible to read ElementMatch from source."; return false; } ExecutableType exe_type = CastToExecutableType(unsafe_element_header.exe_type); if (exe_type == kExeTypeUnknown) { LOG(ERROR) << "Invalid ExecutableType found."; return false; } uint16_t element_version = DisassemblerVersionOfType(exe_type); if (element_version != unsafe_element_header.version) { LOG(ERROR) << "Element version doesn't match. Expected: " << element_version << ", Actual:" << unsafe_element_header.version; return false; } if (!unsafe_element_header.old_length || !unsafe_element_header.new_length) { LOG(ERROR) << "Empty patch element found."; return false; } // |unsafe_element_header| is now considered to be safe as it has a valid // |exe_type| and the length fields are of sufficient size. const auto& element_header = unsafe_element_header; // Caveat: Element offsets and lengths can still be invalid (e.g., exceeding // archive bounds), but this will be checked later. element_match->old_element.offset = element_header.old_offset; element_match->old_element.size = element_header.old_length; element_match->new_element.offset = element_header.new_offset; element_match->new_element.size = element_header.new_length; element_match->old_element.exe_type = exe_type; element_match->new_element.exe_type = exe_type; return true; } bool ParseBuffer(BufferSource* source, BufferSource* buffer) { uint32_t unsafe_size = 0; // Bytes. static_assert(sizeof(size_t) >= sizeof(unsafe_size), "size_t is expected to be larger than uint32_t."); if (!source->GetValue(&unsafe_size)) { LOG(ERROR) << "Impossible to read buffer size from source."; return false; } if (!source->GetRegion(static_cast(unsafe_size), buffer)) { LOG(ERROR) << "Impossible to read buffer content from source."; return false; } // Caveat: |buffer| is considered to be safe as it was possible to extract it // from the patch. However, this does not mean its contents are safe and when // parsed must be validated if possible. return true; } } // namespace patch /******** EquivalenceSource ********/ EquivalenceSource::EquivalenceSource() = default; EquivalenceSource::EquivalenceSource(const EquivalenceSource&) = default; EquivalenceSource::~EquivalenceSource() = default; bool EquivalenceSource::Initialize(BufferSource* source) { return patch::ParseBuffer(source, &src_skip_) && patch::ParseBuffer(source, &dst_skip_) && patch::ParseBuffer(source, ©_count_); } absl::optional EquivalenceSource::GetNext() { if (src_skip_.empty() || dst_skip_.empty() || copy_count_.empty()) return absl::nullopt; Equivalence equivalence = {}; uint32_t length = 0; if (!patch::ParseVarUInt(©_count_, &length)) return absl::nullopt; equivalence.length = base::strict_cast(length); int32_t src_offset_diff = 0; // Intentionally signed. if (!patch::ParseVarInt(&src_skip_, &src_offset_diff)) return absl::nullopt; base::CheckedNumeric src_offset = previous_src_offset_ + src_offset_diff; if (!src_offset.IsValid()) return absl::nullopt; equivalence.src_offset = src_offset.ValueOrDie(); previous_src_offset_ = src_offset + equivalence.length; if (!previous_src_offset_.IsValid()) return absl::nullopt; uint32_t dst_offset_diff = 0; // Intentionally unsigned. if (!patch::ParseVarUInt(&dst_skip_, &dst_offset_diff)) return absl::nullopt; base::CheckedNumeric dst_offset = previous_dst_offset_ + dst_offset_diff; if (!dst_offset.IsValid()) return absl::nullopt; equivalence.dst_offset = dst_offset.ValueOrDie(); previous_dst_offset_ = equivalence.dst_offset + equivalence.length; if (!previous_dst_offset_.IsValid()) return absl::nullopt; // Caveat: |equivalence| is assumed to be safe only once the // ValidateEquivalencesAndExtraData() method has returned true. Prior to this // any equivalence returned is assumed to be unsafe. return equivalence; } /******** ExtraDataSource ********/ ExtraDataSource::ExtraDataSource() = default; ExtraDataSource::ExtraDataSource(const ExtraDataSource&) = default; ExtraDataSource::~ExtraDataSource() = default; bool ExtraDataSource::Initialize(BufferSource* source) { return patch::ParseBuffer(source, &extra_data_); } absl::optional ExtraDataSource::GetNext(offset_t size) { ConstBufferView buffer; if (!extra_data_.GetRegion(size, &buffer)) return absl::nullopt; // |buffer| is assumed to always be safe/valid. return buffer; } /******** RawDeltaSource ********/ RawDeltaSource::RawDeltaSource() = default; RawDeltaSource::RawDeltaSource(const RawDeltaSource&) = default; RawDeltaSource::~RawDeltaSource() = default; bool RawDeltaSource::Initialize(BufferSource* source) { return patch::ParseBuffer(source, &raw_delta_skip_) && patch::ParseBuffer(source, &raw_delta_diff_); } absl::optional RawDeltaSource::GetNext() { if (raw_delta_skip_.empty() || raw_delta_diff_.empty()) return absl::nullopt; RawDeltaUnit raw_delta = {}; uint32_t copy_offset_diff = 0; if (!patch::ParseVarUInt(&raw_delta_skip_, ©_offset_diff)) return absl::nullopt; base::CheckedNumeric copy_offset = copy_offset_diff + copy_offset_compensation_; if (!copy_offset.IsValid()) return absl::nullopt; raw_delta.copy_offset = copy_offset.ValueOrDie(); if (!raw_delta_diff_.GetValue(&raw_delta.diff)) return absl::nullopt; // A 0 value for a delta.diff is considered invalid since it has no meaning. if (!raw_delta.diff) return absl::nullopt; // We keep track of the compensation needed for next offset, taking into // account delta encoding and bias of -1. copy_offset_compensation_ = copy_offset + 1; if (!copy_offset_compensation_.IsValid()) return absl::nullopt; // |raw_delta| is assumed to always be safe/valid. return raw_delta; } /******** ReferenceDeltaSource ********/ ReferenceDeltaSource::ReferenceDeltaSource() = default; ReferenceDeltaSource::ReferenceDeltaSource(const ReferenceDeltaSource&) = default; ReferenceDeltaSource::~ReferenceDeltaSource() = default; bool ReferenceDeltaSource::Initialize(BufferSource* source) { return patch::ParseBuffer(source, &source_); } absl::optional ReferenceDeltaSource::GetNext() { if (source_.empty()) return absl::nullopt; int32_t ref_delta = 0; if (!patch::ParseVarInt(&source_, &ref_delta)) return absl::nullopt; // |ref_delta| is assumed to always be safe/valid. return ref_delta; } /******** TargetSource ********/ TargetSource::TargetSource() = default; TargetSource::TargetSource(const TargetSource&) = default; TargetSource::~TargetSource() = default; bool TargetSource::Initialize(BufferSource* source) { return patch::ParseBuffer(source, &extra_targets_); } absl::optional TargetSource::GetNext() { if (extra_targets_.empty()) return absl::nullopt; uint32_t target_diff = 0; if (!patch::ParseVarUInt(&extra_targets_, &target_diff)) return absl::nullopt; base::CheckedNumeric target = target_diff + target_compensation_; if (!target.IsValid()) return absl::nullopt; // We keep track of the compensation needed for next target, taking into // account delta encoding and bias of -1. target_compensation_ = target + 1; if (!target_compensation_.IsValid()) return absl::nullopt; // Caveat: |target| will be a valid offset_t, but it's up to the caller to // check whether it's a valid offset for an image. return offset_t(target.ValueOrDie()); } /******** PatchElementReader ********/ PatchElementReader::PatchElementReader() = default; PatchElementReader::PatchElementReader(PatchElementReader&&) = default; PatchElementReader::~PatchElementReader() = default; bool PatchElementReader::Initialize(BufferSource* source) { bool ok = patch::ParseElementMatch(source, &element_match_) && equivalences_.Initialize(source) && extra_data_.Initialize(source) && ValidateEquivalencesAndExtraData() && raw_delta_.Initialize(source) && reference_delta_.Initialize(source); if (!ok) return false; uint32_t pool_count = 0; if (!source->GetValue(&pool_count)) { LOG(ERROR) << "Impossible to read pool_count from source."; return false; } for (uint32_t i = 0; i < pool_count; ++i) { uint8_t pool_tag_value = 0; if (!source->GetValue(&pool_tag_value)) { LOG(ERROR) << "Impossible to read pool_tag from source."; return false; } PoolTag pool_tag(pool_tag_value); if (pool_tag == kNoPoolTag) { LOG(ERROR) << "Invalid pool_tag encountered in ExtraTargetList."; return false; } auto insert_result = extra_targets_.insert({pool_tag, {}}); if (!insert_result.second) { // Element already present. LOG(ERROR) << "Multiple ExtraTargetList found for the same pool_tag."; return false; } if (!insert_result.first->second.Initialize(source)) return false; } return true; } bool PatchElementReader::ValidateEquivalencesAndExtraData() { EquivalenceSource equivalences_copy = equivalences_; const size_t old_region_size = element_match_.old_element.size; const size_t new_region_size = element_match_.new_element.size; base::CheckedNumeric total_length = 0; // Validate that each |equivalence| falls within the bounds of the // |element_match_| and are in order. offset_t prev_dst_end = 0; for (auto equivalence = equivalences_copy.GetNext(); equivalence.has_value(); equivalence = equivalences_copy.GetNext()) { if (!RangeIsBounded(equivalence->src_offset, equivalence->length, old_region_size) || !RangeIsBounded(equivalence->dst_offset, equivalence->length, new_region_size)) { LOG(ERROR) << "Out of bounds equivalence detected."; return false; } if (prev_dst_end > equivalence->dst_end()) { LOG(ERROR) << "Out of order equivalence detected."; return false; } prev_dst_end = equivalence->dst_end(); total_length += equivalence->length; } if (!total_length.IsValid() || element_match_.new_element.region().size < total_length.ValueOrDie() || extra_data_.extra_data().size() != element_match_.new_element.region().size - static_cast(total_length.ValueOrDie())) { LOG(ERROR) << "Incorrect amount of extra_data."; return false; } return true; } /******** EnsemblePatchReader ********/ absl::optional EnsemblePatchReader::Create( ConstBufferView buffer) { BufferSource source(buffer); EnsemblePatchReader patch; if (!patch.Initialize(&source)) return absl::nullopt; return patch; } EnsemblePatchReader::EnsemblePatchReader() = default; EnsemblePatchReader::EnsemblePatchReader(EnsemblePatchReader&&) = default; EnsemblePatchReader::~EnsemblePatchReader() = default; bool EnsemblePatchReader::Initialize(BufferSource* source) { if (!source->GetValue(&header_)) { LOG(ERROR) << "Impossible to read header from source."; return false; } if (header_.magic != PatchHeader::kMagic) { LOG(ERROR) << "Patch contains invalid magic."; return false; } if (header_.major_version != kMajorVersion) { LOG(ERROR) << "Patch major version doesn't match. Expected: " << kMajorVersion << ", Actual:" << header_.major_version; return false; } // |header_| is assumed to be safe from this point forward. uint32_t element_count = 0; if (!source->GetValue(&element_count)) { LOG(ERROR) << "Impossible to read element_count from source."; return false; } offset_t current_dst_offset = 0; for (uint32_t i = 0; i < element_count; ++i) { PatchElementReader element_patch; if (!element_patch.Initialize(source)) return false; if (!element_patch.old_element().FitsIn(header_.old_size) || !element_patch.new_element().FitsIn(header_.new_size)) { LOG(ERROR) << "Invalid element encountered."; return false; } if (element_patch.new_element().offset != current_dst_offset) { LOG(ERROR) << "Invalid element encountered."; return false; } current_dst_offset = element_patch.new_element().EndOffset(); elements_.push_back(std::move(element_patch)); } if (current_dst_offset != header_.new_size) { LOG(ERROR) << "Patch elements don't fully cover new image file."; return false; } if (!source->empty()) { LOG(ERROR) << "Patch was not fully consumed."; return false; } return true; } bool EnsemblePatchReader::CheckOldFile(ConstBufferView old_image) const { return old_image.size() == header_.old_size && CalculateCrc32(old_image.begin(), old_image.end()) == header_.old_crc; } bool EnsemblePatchReader::CheckNewFile(ConstBufferView new_image) const { return new_image.size() == header_.new_size && CalculateCrc32(new_image.begin(), new_image.end()) == header_.new_crc; } } // namespace zucchini