diff options
-rw-r--r-- | element_detection_unittest.cc | 91 | ||||
-rw-r--r-- | heuristic_ensemble_matcher.cc | 5 | ||||
-rw-r--r-- | image_utils.h | 52 | ||||
-rw-r--r-- | image_utils_unittest.cc | 7 | ||||
-rw-r--r-- | patch_read_write_unittest.cc | 100 | ||||
-rw-r--r-- | patch_reader.cc | 3 |
6 files changed, 161 insertions, 97 deletions
diff --git a/element_detection_unittest.cc b/element_detection_unittest.cc index 2200c0b..6dbfa3f 100644 --- a/element_detection_unittest.cc +++ b/element_detection_unittest.cc @@ -11,14 +11,65 @@ #include "testing/gtest/include/gtest/gtest.h" namespace zucchini { - namespace { +// This test uses a mock archive format where regions are determined by their +// consecutive byte values rather than parsing real executables. +// +// 0 - Padding or raw data (not mapped to an executable). +// 1 - A Win32x86 executable. +// 2 - A Win32x64 executable. +// +// So an example archive file of; +// 0 1 1 1 0 1 1 0 0 2 2 2 2 +// contains (in order left to right): +// - One padding byte +// - Three byte Win32x86 executable +// - One padding byte +// - Two byte Win32x86 executable +// - Two padding bytes +// - Four byte Win32x64 executable -using ElementVector = std::vector<Element>; +class ElementDetectionTest : public ::testing::Test { + protected: + using ElementVector = std::vector<Element>; + using ExeTypeMap = std::map<uint8_t, ExecutableType>; -} // namespace + ElementDetectionTest() + : exe_map_({{1, kExeTypeWin32X86}, {2, kExeTypeWin32X64}}) {} + + ElementVector TestElementFinder(std::vector<uint8_t> buffer) { + ConstBufferView image(buffer.data(), buffer.size()); + + ElementFinder finder( + image, + base::BindRepeating( + [](ExeTypeMap exe_map, ConstBufferView image, + ConstBufferView region) -> base::Optional<Element> { + EXPECT_GE(region.begin(), image.begin()); + EXPECT_LE(region.end(), image.end()); + EXPECT_GE(region.size(), 0U); + + if (region[0] != 0) { + offset_t length = 1; + while (length < region.size() && region[length] == region[0]) + ++length; + return Element{{0, length}, exe_map[region[0]]}; + } + return base::nullopt; + }, + exe_map_, image)); + std::vector<Element> elements; + for (auto element = finder.GetNext(); element; element = finder.GetNext()) { + elements.push_back(*element); + } + return elements; + } + + // Translation map from mock archive bytes to actual types used in Zucchini. + ExeTypeMap exe_map_; +}; -TEST(ElementDetectionTest, ElementFinderEmpty) { +TEST_F(ElementDetectionTest, ElementFinderEmpty) { std::vector<uint8_t> buffer(10, 0); ElementFinder finder( ConstBufferView(buffer.data(), buffer.size()), @@ -28,36 +79,7 @@ TEST(ElementDetectionTest, ElementFinderEmpty) { EXPECT_EQ(base::nullopt, finder.GetNext()); } -ElementVector TestElementFinder(std::vector<uint8_t> buffer) { - ConstBufferView image(buffer.data(), buffer.size()); - - ElementFinder finder( - image, - base::BindRepeating( - [](ConstBufferView image, - ConstBufferView region) -> base::Optional<Element> { - EXPECT_GE(region.begin(), image.begin()); - EXPECT_LE(region.end(), image.end()); - EXPECT_GE(region.size(), 0U); - - if (region[0] != 0) { - offset_t length = 1; - while (length < region.size() && region[length] == region[0]) - ++length; - return Element{{0, length}, - static_cast<ExecutableType>(region[0])}; - } - return base::nullopt; - }, - image)); - std::vector<Element> elements; - for (auto element = finder.GetNext(); element; element = finder.GetNext()) { - elements.push_back(*element); - } - return elements; -} - -TEST(ElementDetectionTest, ElementFinder) { +TEST_F(ElementDetectionTest, ElementFinder) { EXPECT_EQ(ElementVector(), TestElementFinder({})); EXPECT_EQ(ElementVector(), TestElementFinder({0, 0})); EXPECT_EQ(ElementVector({{{0, 2}, kExeTypeWin32X86}}), @@ -75,4 +97,5 @@ TEST(ElementDetectionTest, ElementFinder) { TestElementFinder({0, 1, 1, 0, 2, 2, 2})); } +} // namespace } // namespace zucchini diff --git a/heuristic_ensemble_matcher.cc b/heuristic_ensemble_matcher.cc index aead5dc..1e99d5b 100644 --- a/heuristic_ensemble_matcher.cc +++ b/heuristic_ensemble_matcher.cc @@ -68,8 +68,9 @@ bool UnsafeDifference(const Element& old_element, const Element& new_element) { } std::ostream& operator<<(std::ostream& stream, const Element& elt) { - stream << "(" << elt.exe_type << ", " << AsHex<8, size_t>(elt.offset) << " +" - << AsHex<8, size_t>(elt.size) << ")"; + stream << "(" << CastExecutableTypeToString(elt.exe_type) << ", " + << AsHex<8, size_t>(elt.offset) << " +" << AsHex<8, size_t>(elt.size) + << ")"; return stream; } diff --git a/image_utils.h b/image_utils.h index 3765763..868e358 100644 --- a/image_utils.h +++ b/image_utils.h @@ -8,6 +8,7 @@ #include <stddef.h> #include <stdint.h> +#include "base/macros.h" #include "base/numerics/safe_conversions.h" #include "base/optional.h" #include "components/zucchini/buffer_view.h" @@ -137,20 +138,51 @@ struct EquivalenceCandidate { double similarity; }; -// Enumerations for supported executables. +template <size_t N> +inline constexpr uint32_t ExeTypeToUint32(const char (&exe_type)[N]) { + static_assert(N == 5, "Expected ExeType of length 4 + 1 null byte."); + return (exe_type[3] << 24) | (exe_type[2] << 16) | (exe_type[1] << 8) | + exe_type[0]; +} + +// Enumerations for supported executables. Values in this enum must be distinct. +// Once present, values should never be altered or removed to ensure backwards +// compatibility and patch type collision avoidance. enum ExecutableType : uint32_t { kExeTypeUnknown = UINT32_MAX, - kExeTypeNoOp = 0, - kExeTypeWin32X86 = 1, - kExeTypeWin32X64 = 2, - kExeTypeElfX86 = 3, - kExeTypeElfX64 = 4, - kExeTypeElfArm32 = 5, - kExeTypeElfAArch64 = 6, - kExeTypeDex = 7, - kNumExeType + kExeTypeNoOp = ExeTypeToUint32("NoOp"), + kExeTypeWin32X86 = ExeTypeToUint32("Px86"), + kExeTypeWin32X64 = ExeTypeToUint32("Px64"), + kExeTypeElfX86 = ExeTypeToUint32("Ex86"), + kExeTypeElfX64 = ExeTypeToUint32("Ex64"), + kExeTypeElfArm32 = ExeTypeToUint32("EA32"), + kExeTypeElfAArch64 = ExeTypeToUint32("EA64"), + kExeTypeDex = ExeTypeToUint32("DEX "), }; +constexpr ExecutableType CastToExecutableType(uint32_t possible_exe_type) { + switch (static_cast<ExecutableType>(possible_exe_type)) { + case kExeTypeNoOp: // Falls through. + case kExeTypeWin32X86: // Falls through. + case kExeTypeWin32X64: // Falls through. + case kExeTypeElfX86: // Falls through. + case kExeTypeElfX64: // Falls through. + case kExeTypeElfArm32: // Falls through. + case kExeTypeElfAArch64: // Falls through. + case kExeTypeDex: // Falls through. + case kExeTypeUnknown: + return static_cast<ExecutableType>(possible_exe_type); + default: + NOTREACHED() << "Unknown type."; + return kExeTypeUnknown; + } +} + +inline std::string CastExecutableTypeToString(ExecutableType exe_type) { + uint32_t v = static_cast<uint32_t>(exe_type); + return {v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF, (v >> 24) & 0xFF}; +} + // A region in an image with associated executable type |exe_type|. If // |exe_type == kExeTypeNoOp|, then the Element represents a region of raw data. struct Element : public BufferRegion { diff --git a/image_utils_unittest.cc b/image_utils_unittest.cc index cd71a2f..f483625 100644 --- a/image_utils_unittest.cc +++ b/image_utils_unittest.cc @@ -14,4 +14,11 @@ TEST(ImageUtilsTest, Bitness) { EXPECT_EQ(8U, WidthOf(kBit64)); } +TEST(ImageUtilsTest, CastExecutableTypeToString) { + EXPECT_EQ("NoOp", CastExecutableTypeToString(kExeTypeNoOp)); + EXPECT_EQ("Px86", CastExecutableTypeToString(kExeTypeWin32X86)); + EXPECT_EQ("EA64", CastExecutableTypeToString(kExeTypeElfAArch64)); + EXPECT_EQ("DEX ", CastExecutableTypeToString(kExeTypeDex)); +} + } // namespace zucchini diff --git a/patch_read_write_unittest.cc b/patch_read_write_unittest.cc index b701df8..f6396d6 100644 --- a/patch_read_write_unittest.cc +++ b/patch_read_write_unittest.cc @@ -63,11 +63,11 @@ 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 - 7, 0, 0, 0, // kExeTypeDex + 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 }; BufferSource buffer_source(data.data(), data.size()); ElementMatch element_match = {}; @@ -392,37 +392,37 @@ 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 - 1, 0, 0, 0, // 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 + 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 }; PatchElementReader patch_element_reader = @@ -490,7 +490,7 @@ TEST(EnsemblePatchTest, RawPatch) { 0x00, 0, 0, 0, // new_offset 0x02, 0, 0, 0, // old_length 0x98, 0xBA, 0xDC, 0xFE, // new_length - 1, 0, 0, 0, // EXE_TYPE_WIN32_X86 + '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 @@ -536,19 +536,19 @@ TEST(EnsemblePatchTest, CheckFile) { 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 }; EnsemblePatchReader ensemble_patch_reader = diff --git a/patch_reader.cc b/patch_reader.cc index 970b90c..8215405 100644 --- a/patch_reader.cc +++ b/patch_reader.cc @@ -4,6 +4,7 @@ #include "components/zucchini/patch_reader.h" +#include <algorithm> #include <type_traits> #include <utility> @@ -23,7 +24,7 @@ bool ParseElementMatch(BufferSource* source, ElementMatch* element_match) { } ExecutableType exe_type = static_cast<ExecutableType>(element_header.exe_type); - if (exe_type >= kNumExeType) { + if (CastToExecutableType(exe_type) == kExeTypeUnknown) { LOG(ERROR) << "Invalid ExecutableType encountered."; LOG(ERROR) << base::debug::StackTrace().ToString(); return false; |