diff options
-rw-r--r-- | patch_read_write_unittest.cc | 152 | ||||
-rw-r--r-- | patch_reader.cc | 91 | ||||
-rw-r--r-- | patch_reader.h | 13 |
3 files changed, 177 insertions, 79 deletions
diff --git a/patch_read_write_unittest.cc b/patch_read_write_unittest.cc index ed0a6ed..681e3c2 100644 --- a/patch_read_write_unittest.cc +++ b/patch_read_write_unittest.cc @@ -60,13 +60,13 @@ ByteVector CreatePatchElement() { 0x01, 0, 0, 0, // old_offset 0x03, 0, 0, 0, // new_offset 0x51, 0, 0, 0, // old_length - 0x50, 0, 0, 0, // new_length + 0x13, 0, 0, 0, // new_length 'P', 'x', '8', '6', // EXE_TYPE_WIN32_X86 1, 0, 0, 0, // src_skip size 0x10, // src_skip content 1, 0, 0, 0, // dst_skip size - 0x11, // dst_skip content + 0x00, // dst_skip content 1, 0, 0, 0, // copy_count size 0x12, // copy_count content @@ -91,8 +91,18 @@ ByteVector CreatePatchElement() { }; } -// Helper to mutate test |data| (e.g., from CreatePatchElement()) at |idx| from |from_val| (as -// sanity check) to |to_val|. +ByteVector CreateElementMatch() { + return { + 0x01, 0, 0, 0, // old_offset + 0x03, 0, 0, 0, // new_offset + 0x02, 0, 0, 0, // old_length + 0x04, 0, 0, 0, // new_length + 'D', 'E', 'X', ' ', // kExeTypeDex + }; +} + +// Helper to mutate test |data| (e.g., from CreatePatchElement()) at |idx| from +// |from_val| (as sanity check) to |to_val|. void ModifyByte(size_t idx, uint8_t from_val, uint8_t to_val, @@ -108,13 +118,7 @@ bool operator==(const ByteVector& a, ConstBufferView b) { } TEST(PatchTest, ParseSerializeElementMatch) { - ByteVector data = { - 0x01, 0, 0, 0, // old_offset - 0x03, 0, 0, 0, // new_offset - 0x02, 0, 0, 0, // old_length - 0x04, 0, 0, 0, // new_length - 'D', 'E', 'X', ' ', // kExeTypeDex - }; + ByteVector data = CreateElementMatch(); BufferSource buffer_source(data.data(), data.size()); ElementMatch element_match = {}; EXPECT_TRUE(patch::ParseElementMatch(&buffer_source, &element_match)); @@ -141,6 +145,34 @@ TEST(PatchTest, ParseElementMatchTooSmall) { EXPECT_FALSE(patch::ParseElementMatch(&buffer_source, &element_match)); } +TEST(PatchTest, ParseElementMatchNoLength) { + // Set old_length to 0 to trigger an error. + { + ByteVector data = CreateElementMatch(); + ModifyByte(8U, 0x02, 0x00, &data); // Make the old_length = 0. + BufferSource buffer_source(data.data(), data.size()); + ElementMatch element_match = {}; + EXPECT_FALSE(patch::ParseElementMatch(&buffer_source, &element_match)); + } + // Set new_length to 0 to trigger an error. + { + ByteVector data = CreateElementMatch(); + ModifyByte(12U, 0x04, 0x00, &data); // Make the new_length = 0. + BufferSource buffer_source(data.data(), data.size()); + ElementMatch element_match = {}; + EXPECT_FALSE(patch::ParseElementMatch(&buffer_source, &element_match)); + } + // Set both new_length and old_length to 0 to trigger an error. + { + ByteVector data = CreateElementMatch(); + ModifyByte(8U, 0x02, 0x00, &data); // Make the old_length = 0. + ModifyByte(12U, 0x04, 0x00, &data); // Make the new_length = 0. + BufferSource buffer_source(data.data(), data.size()); + ElementMatch element_match = {}; + EXPECT_FALSE(patch::ParseElementMatch(&buffer_source, &element_match)); + } +} + TEST(PatchTest, ParseSerializeElementMatchExeMismatch) { ByteVector buffer(28); BufferSink buffer_sink(buffer.data(), buffer.size()); @@ -356,6 +388,18 @@ TEST(RawDeltaSinkSourceSinkTest, Normal) { TestSerialize(data, raw_delta_sink); } +TEST(RawDeltaSourceSinkTest, InvalidContent) { + ByteVector data = { + 2, 0, 0, 0, // raw_delta_skip size + 1, 3, // raw_delta_skip content + 2, 0, 0, 0, // raw_delta_diff size + 0, 4, // raw_delta_diff content + }; + RawDeltaSource raw_delta_source = TestInitialize<RawDeltaSource>(&data); + EXPECT_FALSE(raw_delta_source.GetNext()); + EXPECT_FALSE(raw_delta_source.Done()); +} + TEST(ReferenceDeltaSourceSinkTest, Empty) { ByteVector data = { 0, 0, 0, 0, // reference_delta size @@ -449,12 +493,12 @@ TEST(PatchElementTest, Normal) { EXPECT_EQ(0x1U, element_match.old_element.offset); EXPECT_EQ(0x51U, element_match.old_element.size); EXPECT_EQ(0x3U, element_match.new_element.offset); - EXPECT_EQ(0x50U, element_match.new_element.size); + EXPECT_EQ(0x13U, element_match.new_element.size); EquivalenceSource equivalence_source = patch_element_reader.GetEquivalenceSource(); EXPECT_EQ(ByteVector({0x10}), equivalence_source.src_skip()); - EXPECT_EQ(ByteVector({0x11}), equivalence_source.dst_skip()); + EXPECT_EQ(ByteVector({0x00}), equivalence_source.dst_skip()); EXPECT_EQ(ByteVector({0x12}), equivalence_source.copy_count()); ExtraDataSource extra_data_source = patch_element_reader.GetExtraDataSource(); @@ -481,7 +525,7 @@ TEST(PatchElementTest, Normal) { PatchElementWriter patch_element_writer(element_match); patch_element_writer.SetEquivalenceSink( - EquivalenceSink({0x10}, {0x11}, {0x12})); + EquivalenceSink({0x10}, {0x00}, {0x12})); patch_element_writer.SetExtraDataSink(ExtraDataSink({0x13})); patch_element_writer.SetRawDeltaSink(RawDeltaSink({0x14}, {0x15})); patch_element_writer.SetReferenceDeltaSink(ReferenceDeltaSink({0x16})); @@ -501,7 +545,23 @@ TEST(PatchElementTest, BadEquivalence) { // If the "new" element is too small the test should fail. { ByteVector data = CreatePatchElement(); - ModifyByte(12, 0x50, 0x05, &data); // new_length (too small) + ModifyByte(12, 0x13, 0x05, &data); // new_length (too small) + TestInvalidInitialize<PatchElementReader>(&data); + } +} + +TEST(PatchElementTest, WrongExtraData) { + // Make "new" too large so there is insufficient extra data to cover the + // image. + { + ByteVector data = CreatePatchElement(); + ModifyByte(12, 0x13, 0x14, &data); // new_length (too large) + TestInvalidInitialize<PatchElementReader>(&data); + } + // Make "new" too small so there is too much extra data. + { + ByteVector data = CreatePatchElement(); + ModifyByte(12, 0x13, 0x14, &data); // new_length (too small) TestInvalidInitialize<PatchElementReader>(&data); } } @@ -511,24 +571,25 @@ TEST(EnsemblePatchTest, RawPatch) { 0x5A, 0x75, 0x63, 0x00, // magic 0x10, 0x32, 0x54, 0x76, // old_size 0x00, 0x11, 0x22, 0x33, // old_crc - 0x98, 0xBA, 0xDC, 0xFE, // new_size + 0x01, 0, 0, 0, // new_size 0x44, 0x55, 0x66, 0x77, // new_crc 1, 0, 0, 0, // number of element - 0x01, 0, 0, 0, // old_offset - 0x00, 0, 0, 0, // new_offset - 0x02, 0, 0, 0, // old_length - 0x98, 0xBA, 0xDC, 0xFE, // new_length - 'P', 'x', '8', '6', // EXE_TYPE_WIN32_X86 - 0, 0, 0, 0, // src_skip size - 0, 0, 0, 0, // dst_skip size - 0, 0, 0, 0, // copy_count size - 0, 0, 0, 0, // extra_data size - 0, 0, 0, 0, // raw_delta_skip size - 0, 0, 0, 0, // raw_delta_diff size - 0, 0, 0, 0, // reference_delta size - 0, 0, 0, 0, // pool count + 0x01, 0, 0, 0, // old_offset + 0x00, 0, 0, 0, // new_offset + 0x02, 0, 0, 0, // old_length + 0x01, 0, 0, 0, // new_length + 'P', 'x', '8', '6', // EXE_TYPE_WIN32_X86 + 0, 0, 0, 0, // src_skip size + 0, 0, 0, 0, // dst_skip size + 0, 0, 0, 0, // copy_count size + 0x01, 0, 0, 0, // extra_data size + 0x04, // extra_data content + 0, 0, 0, 0, // raw_delta_skip size + 0, 0, 0, 0, // raw_delta_diff size + 0, 0, 0, 0, // reference_delta size + 0, 0, 0, 0, // pool count }; EnsemblePatchReader ensemble_patch_reader = @@ -538,7 +599,7 @@ TEST(EnsemblePatchTest, RawPatch) { EXPECT_EQ(PatchHeader::kMagic, header.magic); EXPECT_EQ(0x76543210U, header.old_size); EXPECT_EQ(0x33221100U, header.old_crc); - EXPECT_EQ(0xFEDCBA98U, header.new_size); + EXPECT_EQ(0x01U, header.new_size); EXPECT_EQ(0x77665544U, header.new_crc); const std::vector<PatchElementReader>& elements = @@ -548,7 +609,7 @@ TEST(EnsemblePatchTest, RawPatch) { EnsemblePatchWriter ensemble_patch_writer(header); PatchElementWriter patch_element_writer(elements[0].element_match()); patch_element_writer.SetEquivalenceSink({}); - patch_element_writer.SetExtraDataSink({}); + patch_element_writer.SetExtraDataSink(ExtraDataSink({0x04})); patch_element_writer.SetRawDeltaSink({}); patch_element_writer.SetReferenceDeltaSink({}); ensemble_patch_writer.AddElement(std::move(patch_element_writer)); @@ -574,7 +635,8 @@ TEST(EnsemblePatchTest, CheckFile) { 0, 0, 0, 0, // src_skip size 0, 0, 0, 0, // dst_skip size 0, 0, 0, 0, // copy_count size - 0, 0, 0, 0, // extra_data size + 0x03, 0, 0, 0, // extra_data size + 'A', 'B', 'C', // extra_data content 0, 0, 0, 0, // raw_delta_skip size 0, 0, 0, 0, // raw_delta_diff size 0, 0, 0, 0, // reference_delta size @@ -606,19 +668,19 @@ TEST(EnsemblePatchTest, InvalidMagic) { 1, 0, 0, 0, // number of element - 0x01, 0, 0, 0, // old_offset - 0x00, 0, 0, 0, // new_offset - 0x02, 0, 0, 0, // old_length - 0x03, 0, 0, 0, // new_length - 1, 0, 0, 0, // EXE_TYPE_WIN32_X86 - 0, 0, 0, 0, // src_skip size - 0, 0, 0, 0, // dst_skip size - 0, 0, 0, 0, // copy_count size - 0, 0, 0, 0, // extra_data size - 0, 0, 0, 0, // raw_delta_skip size - 0, 0, 0, 0, // raw_delta_diff size - 0, 0, 0, 0, // reference_delta size - 0, 0, 0, 0, // pool count + 0x01, 0, 0, 0, // old_offset + 0x00, 0, 0, 0, // new_offset + 0x02, 0, 0, 0, // old_length + 0x03, 0, 0, 0, // new_length + 'P', 'x', '8', '6', // EXE_TYPE_WIN32_X86 + 0, 0, 0, 0, // src_skip size + 0, 0, 0, 0, // dst_skip size + 0, 0, 0, 0, // copy_count size + 0, 0, 0, 0, // extra_data size + 0, 0, 0, 0, // raw_delta_skip size + 0, 0, 0, 0, // raw_delta_diff size + 0, 0, 0, 0, // reference_delta size + 0, 0, 0, 0, // pool count }; TestInvalidInitialize<EnsemblePatchReader>(&data); diff --git a/patch_reader.cc b/patch_reader.cc index 179f0fd..0215bc8 100644 --- a/patch_reader.cc +++ b/patch_reader.cc @@ -16,19 +16,27 @@ namespace zucchini { namespace patch { bool ParseElementMatch(BufferSource* source, ElementMatch* element_match) { - PatchElementHeader element_header; - if (!source->GetValue(&element_header)) { + PatchElementHeader unsafe_element_header; + if (!source->GetValue(&unsafe_element_header)) { LOG(ERROR) << "Impossible to read ElementMatch from source."; - LOG(ERROR) << base::debug::StackTrace().ToString(); return false; } ExecutableType exe_type = - static_cast<ExecutableType>(element_header.exe_type); - if (CastToExecutableType(exe_type) == kExeTypeUnknown) { - LOG(ERROR) << "Invalid ExecutableType encountered."; - LOG(ERROR) << base::debug::StackTrace().ToString(); + CastToExecutableType(unsafe_element_header.exe_type); + if (exe_type == kExeTypeUnknown) { + LOG(ERROR) << "Invalid ExecutableType found."; 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->new_element.offset = element_header.new_offset; element_match->old_element.size = element_header.old_length; @@ -39,17 +47,20 @@ bool ParseElementMatch(BufferSource* source, ElementMatch* element_match) { } bool ParseBuffer(BufferSource* source, BufferSource* buffer) { - uint32_t size = 0; - if (!source->GetValue(&size)) { + 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."; - LOG(ERROR) << base::debug::StackTrace().ToString(); return false; } - if (!source->GetRegion(base::checked_cast<size_t>(size), buffer)) { + if (!source->GetRegion(static_cast<size_t>(unsafe_size), buffer)) { LOG(ERROR) << "Impossible to read buffer content from source."; - LOG(ERROR) << base::debug::StackTrace().ToString(); 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; } @@ -104,6 +115,9 @@ base::Optional<Equivalence> EquivalenceSource::GetNext() { if (!previous_dst_offset_.IsValid()) return base::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; } @@ -121,6 +135,7 @@ base::Optional<ConstBufferView> ExtraDataSource::GetNext(offset_t size) { ConstBufferView buffer; if (!extra_data_.GetRegion(size, &buffer)) return base::nullopt; + // |buffer| is assumed to always be safe/valid. return buffer; } @@ -139,7 +154,7 @@ base::Optional<RawDeltaUnit> RawDeltaSource::GetNext() { if (raw_delta_skip_.empty() || raw_delta_diff_.empty()) return base::nullopt; - RawDeltaUnit delta = {}; + RawDeltaUnit raw_delta = {}; uint32_t copy_offset_diff = 0; if (!patch::ParseVarUInt<uint32_t>(&raw_delta_skip_, ©_offset_diff)) return base::nullopt; @@ -147,17 +162,22 @@ base::Optional<RawDeltaUnit> RawDeltaSource::GetNext() { copy_offset_diff + copy_offset_compensation_; if (!copy_offset.IsValid()) return base::nullopt; - delta.copy_offset = copy_offset.ValueOrDie(); + raw_delta.copy_offset = copy_offset.ValueOrDie(); - if (!raw_delta_diff_.GetValue<int8_t>(&delta.diff)) + if (!raw_delta_diff_.GetValue<int8_t>(&raw_delta.diff)) + return base::nullopt; + + // A 0 value for a delta.diff is considered invalid since it has no meaning. + if (!raw_delta.diff) return base::nullopt; // We keep track of the compensation needed for next offset, taking into - // accound delta encoding and bias of -1. + // account delta encoding and bias of -1. copy_offset_compensation_ = copy_offset + 1; if (!copy_offset_compensation_.IsValid()) return base::nullopt; - return delta; + // |raw_delta| is assumed to always be safe/valid. + return raw_delta; } /******** ReferenceDeltaSource ********/ @@ -168,16 +188,17 @@ ReferenceDeltaSource::ReferenceDeltaSource(const ReferenceDeltaSource&) = ReferenceDeltaSource::~ReferenceDeltaSource() = default; bool ReferenceDeltaSource::Initialize(BufferSource* source) { - return patch::ParseBuffer(source, &reference_delta_); + return patch::ParseBuffer(source, &source_); } base::Optional<int32_t> ReferenceDeltaSource::GetNext() { - if (reference_delta_.empty()) + if (source_.empty()) return base::nullopt; - int32_t delta = 0; - if (!patch::ParseVarInt<int32_t>(&reference_delta_, &delta)) + int32_t ref_delta = 0; + if (!patch::ParseVarInt<int32_t>(&source_, &ref_delta)) return base::nullopt; - return delta; + // |ref_delta| is assumed to always be safe/valid. + return ref_delta; } /******** TargetSource ********/ @@ -202,10 +223,12 @@ base::Optional<offset_t> TargetSource::GetNext() { return base::nullopt; // We keep track of the compensation needed for next target, taking into - // accound delta encoding and bias of -1. + // account delta encoding and bias of -1. target_compensation_ = target + 1; if (!target_compensation_.IsValid()) return base::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()); } @@ -216,10 +239,11 @@ PatchElementReader::PatchElementReader(PatchElementReader&&) = default; PatchElementReader::~PatchElementReader() = default; bool PatchElementReader::Initialize(BufferSource* source) { - bool ok = patch::ParseElementMatch(source, &element_match_) && - equivalences_.Initialize(source) && ValidateEquivalences() && - extra_data_.Initialize(source) && raw_delta_.Initialize(source) && - reference_delta_.Initialize(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; @@ -249,12 +273,13 @@ bool PatchElementReader::Initialize(BufferSource* source) { return true; } -bool PatchElementReader::ValidateEquivalences() { +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<uint32_t> 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; @@ -272,6 +297,15 @@ bool PatchElementReader::ValidateEquivalences() { 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<size_t>(total_length.ValueOrDie())) { + LOG(ERROR) << "Incorrect amount of extra_data."; + return false; } return true; } @@ -300,6 +334,7 @@ bool EnsemblePatchReader::Initialize(BufferSource* source) { LOG(ERROR) << "Patch contains invalid magic."; return false; } + // |header_| is assumed to be safe from this point forward. uint32_t element_count = 0; if (!source->GetValue(&element_count)) { diff --git a/patch_reader.h b/patch_reader.h index 5d3c6dd..515da50 100644 --- a/patch_reader.h +++ b/patch_reader.h @@ -168,13 +168,13 @@ class ReferenceDeltaSource { // Core functions. bool Initialize(BufferSource* source); base::Optional<int32_t> GetNext(); - bool Done() const { return reference_delta_.empty(); } + bool Done() const { return source_.empty(); } // Accessors for unittest. - BufferSource reference_delta() const { return reference_delta_; } + BufferSource reference_delta() const { return source_; } private: - BufferSource reference_delta_; + BufferSource source_; }; // Source for additional targets. @@ -236,9 +236,10 @@ class PatchElementReader { private: // Checks that "old" and "new" blocks of each item in |equivalences_| satisfy - // basic order and image bound constraints (using |element_match_| data). - // Returns true if successful. - bool ValidateEquivalences(); + // basic order and image bound constraints (using |element_match_| data). Also + // validates that the amount of extra data is correct. Returns true if + // successful. + bool ValidateEquivalencesAndExtraData(); ElementMatch element_match_; |