diff options
-rw-r--r-- | patch_read_write_unittest.cc | 100 | ||||
-rw-r--r-- | patch_reader.cc | 44 | ||||
-rw-r--r-- | patch_reader.h | 11 | ||||
-rw-r--r-- | zucchini_apply.cc | 13 |
4 files changed, 117 insertions, 51 deletions
diff --git a/patch_read_write_unittest.cc b/patch_read_write_unittest.cc index f6396d6..ed0a6ed 100644 --- a/patch_read_write_unittest.cc +++ b/patch_read_write_unittest.cc @@ -55,6 +55,52 @@ void TestSerialize(const ByteVector& expected, const T& value) { EXPECT_EQ(expected, buffer); } +ByteVector CreatePatchElement() { + return { + 0x01, 0, 0, 0, // old_offset + 0x03, 0, 0, 0, // new_offset + 0x51, 0, 0, 0, // old_length + 0x50, 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 + 1, 0, 0, 0, // copy_count size + 0x12, // copy_count content + + 1, 0, 0, 0, // extra_data size + 0x13, // extra_data content + + 1, 0, 0, 0, // raw_delta_skip size + 0x14, // raw_delta_skip content + 1, 0, 0, 0, // raw_delta_diff size + 0x15, // raw_delta_diff content + + 1, 0, 0, 0, // reference_delta size + 0x16, // reference_delta content + + 2, 0, 0, 0, // pool count + 0, // pool_tag + 1, 0, 0, 0, // extra_targets size + 0x17, // extra_targets content + 2, // pool_tag + 1, 0, 0, 0, // extra_targets size + 0x18, // extra_targets content + }; +} + +// 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, + std::vector<uint8_t>* data) { + ASSERT_EQ(from_val, (*data)[idx]); + (*data)[idx] = to_val; +} + } // namespace bool operator==(const ByteVector& a, ConstBufferView b) { @@ -391,39 +437,7 @@ TEST(TargetSourceSinkTest, Normal) { } TEST(PatchElementTest, Normal) { - 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 - '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 - 1, 0, 0, 0, // copy_count size - 0x12, // copy_count content - - 1, 0, 0, 0, // extra_data size - 0x13, // extra_data content - - 1, 0, 0, 0, // raw_delta_skip size - 0x14, // raw_delta_skip content - 1, 0, 0, 0, // raw_delta_diff size - 0x15, // raw_delta_diff content - - 1, 0, 0, 0, // reference_delta size - 0x16, // reference_delta content - - 2, 0, 0, 0, // pool count - 0, // pool_tag - 1, 0, 0, 0, // extra_targets size - 0x17, // extra_targets content - 2, // pool_tag - 1, 0, 0, 0, // extra_targets size - 0x18, // extra_targets content - }; + ByteVector data = CreatePatchElement(); PatchElementReader patch_element_reader = TestInitialize<PatchElementReader>(&data); @@ -433,9 +447,9 @@ TEST(PatchElementTest, Normal) { EXPECT_EQ(kExeTypeWin32X86, element_match.old_element.exe_type); EXPECT_EQ(kExeTypeWin32X86, element_match.new_element.exe_type); EXPECT_EQ(0x1U, element_match.old_element.offset); - EXPECT_EQ(0x2U, element_match.old_element.size); + EXPECT_EQ(0x51U, element_match.old_element.size); EXPECT_EQ(0x3U, element_match.new_element.offset); - EXPECT_EQ(0x4U, element_match.new_element.size); + EXPECT_EQ(0x50U, element_match.new_element.size); EquivalenceSource equivalence_source = patch_element_reader.GetEquivalenceSource(); @@ -476,6 +490,22 @@ TEST(PatchElementTest, Normal) { TestSerialize(data, patch_element_writer); } +TEST(PatchElementTest, BadEquivalence) { + // If the "old" element is too small the test should fail. + { + ByteVector data = CreatePatchElement(); + ModifyByte(8, 0x51, 0x04, &data); // old_length (too small) + TestInvalidInitialize<PatchElementReader>(&data); + } + + // If the "new" element is too small the test should fail. + { + ByteVector data = CreatePatchElement(); + ModifyByte(12, 0x50, 0x05, &data); // new_length (too small) + TestInvalidInitialize<PatchElementReader>(&data); + } +} + TEST(EnsemblePatchTest, RawPatch) { ByteVector data = { 0x5A, 0x75, 0x63, 0x00, // magic diff --git a/patch_reader.cc b/patch_reader.cc index 8215405..90f35f4 100644 --- a/patch_reader.cc +++ b/patch_reader.cc @@ -13,6 +13,19 @@ namespace zucchini { +namespace { + +bool RangeIsInRegion(offset_t begin, uint32_t size, BufferRegion region) { + // Return false if |begin + size > kOffsetBound|, without overflowing. + if (begin >= kOffsetBound || kOffsetBound - begin < size) + return false; + offset_t end = begin + size; + // Return false if |[begin, end]| leaks outside |region|. + return begin >= region.lo() && end <= region.hi(); +} + +} // namespace + namespace patch { bool ParseElementMatch(BufferSource* source, ElementMatch* element_match) { @@ -217,7 +230,7 @@ PatchElementReader::~PatchElementReader() = default; bool PatchElementReader::Initialize(BufferSource* source) { bool ok = patch::ParseElementMatch(source, &element_match_) && - equivalences_.Initialize(source) && + equivalences_.Initialize(source) && ValidateEquivalences() && extra_data_.Initialize(source) && raw_delta_.Initialize(source) && reference_delta_.Initialize(source); if (!ok) @@ -240,7 +253,7 @@ bool PatchElementReader::Initialize(BufferSource* source) { } 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"; + LOG(ERROR) << "Multiple ExtraTargetList found for the same pool_tag."; return false; } if (!insert_result.first->second.Initialize(source)) @@ -249,6 +262,33 @@ bool PatchElementReader::Initialize(BufferSource* source) { return true; } +bool PatchElementReader::ValidateEquivalences() { + EquivalenceSource equivalences_copy = equivalences_; + + BufferRegion old_region = element_match_.old_element.region(); + BufferRegion new_region = element_match_.new_element.region(); + + // 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 (!RangeIsInRegion(equivalence->src_offset, equivalence->length, + old_region) || + !RangeIsInRegion(equivalence->dst_offset, equivalence->length, + new_region)) { + 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(); + } + return true; +} + /******** EnsemblePatchReader ********/ base::Optional<EnsemblePatchReader> EnsemblePatchReader::Create( diff --git a/patch_reader.h b/patch_reader.h index ad517f5..5d3c6dd 100644 --- a/patch_reader.h +++ b/patch_reader.h @@ -218,7 +218,11 @@ class PatchElementReader { const Element& old_element() const { return element_match_.old_element; } const Element& new_element() const { return element_match_.new_element; } - // The Get*() functions below return copies of cached sources. + // The Get*() functions below return copies of cached sources. Callers may + // assume the following: + // - Equivalences satisfy basic boundary constraints + // - "Old" / "new" blocks lie entirely in "old" / "new" images. + // - "New" blocks are sorted. EquivalenceSource GetEquivalenceSource() const { return equivalences_; } ExtraDataSource GetExtraDataSource() const { return extra_data_; } RawDeltaSource GetRawDeltaSource() const { return raw_delta_; } @@ -231,6 +235,11 @@ 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(); + ElementMatch element_match_; // Cached sources. diff --git a/zucchini_apply.cc b/zucchini_apply.cc index 63b8ce0..b67bf63 100644 --- a/zucchini_apply.cc +++ b/zucchini_apply.cc @@ -26,13 +26,6 @@ bool ApplyEquivalenceAndExtraData(ConstBufferView old_image, for (auto equivalence = equiv_source.GetNext(); equivalence.has_value(); equivalence = equiv_source.GetNext()) { - // TODO(ckitagawa): Ensure guards don't overflow. Move these validation - // check to the patch reader. - // Validate that the |equivalence| is within the |new_image|. - if (equivalence->dst_end() > new_image.size()) { - LOG(ERROR) << "Out of bounds equivalence"; - return false; - } MutableBufferView::iterator next_dst_it = new_image.begin() + equivalence->dst_offset; CHECK(next_dst_it >= dst_it); @@ -47,12 +40,6 @@ bool ApplyEquivalenceAndExtraData(ConstBufferView old_image, // copy should be valid. dst_it = std::copy(extra_data->begin(), extra_data->end(), dst_it); CHECK_EQ(dst_it, next_dst_it); - - // Validate that the |equivalence| is within the |old_image|. - if (equivalence->src_end() > old_image.size()) { - LOG(ERROR) << "Out of bounds equivalence"; - return false; - } dst_it = std::copy_n(old_image.begin() + equivalence->src_offset, equivalence->length, dst_it); CHECK_EQ(dst_it, next_dst_it + equivalence->length); |