aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--element_detection_unittest.cc91
-rw-r--r--heuristic_ensemble_matcher.cc5
-rw-r--r--image_utils.h52
-rw-r--r--image_utils_unittest.cc7
-rw-r--r--patch_read_write_unittest.cc100
-rw-r--r--patch_reader.cc3
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;