215 lines
7.8 KiB
C++
215 lines
7.8 KiB
C++
// Copyright 2018 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 <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "components/zucchini/imposed_ensemble_matcher.h"
|
|
|
|
#include "base/bind.h"
|
|
#include "base/callback_helpers.h"
|
|
#include "base/check_op.h"
|
|
#include "components/zucchini/buffer_view.h"
|
|
#include "components/zucchini/disassembler.h"
|
|
#include "components/zucchini/element_detection.h"
|
|
#include "components/zucchini/image_utils.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "third_party/abseil-cpp/absl/types/optional.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. In fact, since
|
|
// elements are imposed, only the first byte of the element is used to specify
|
|
// executable type of the mock data:
|
|
// - 'W' and 'w' specify kExeTypeWin32X86.
|
|
// - 'E' and 'e' specify kExeTypeElfX86.
|
|
// - Everything else specify kExeTypeUnknown.
|
|
class TestElementDetector {
|
|
public:
|
|
TestElementDetector() {}
|
|
|
|
absl::optional<Element> Run(ConstBufferView image) const {
|
|
DCHECK_GT(image.size(), 0U);
|
|
char first_char = *image.begin();
|
|
if (first_char == 'W' || first_char == 'w')
|
|
return Element(image.local_region(), kExeTypeWin32X86);
|
|
if (first_char == 'E' || first_char == 'e')
|
|
return Element(image.local_region(), kExeTypeElfX86);
|
|
return absl::nullopt;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(ImposedMatchParserTest, ImposedMatchParser) {
|
|
std::vector<uint8_t> old_data;
|
|
std::vector<uint8_t> new_data;
|
|
auto populate = [](const std::string& s, std::vector<uint8_t>* data) {
|
|
for (char ch : s)
|
|
data->push_back(static_cast<uint8_t>(ch));
|
|
};
|
|
// Pos: 11111111
|
|
// 012345678901234567
|
|
populate("1WW222EEEE", &old_data);
|
|
populate("33eee2222222wwww44", &new_data);
|
|
|
|
ConstBufferView old_image(&old_data[0], old_data.size());
|
|
ConstBufferView new_image(&new_data[0], new_data.size());
|
|
|
|
TestElementDetector detector;
|
|
|
|
// Reusable output values.
|
|
std::string prev_imposed_matches;
|
|
ImposedMatchParser::Status status;
|
|
size_t num_identical;
|
|
std::vector<ElementMatch> matches;
|
|
std::vector<ElementMatch> bad_matches;
|
|
|
|
auto run_test = [&](const std::string& imposed_matches) -> bool {
|
|
prev_imposed_matches = imposed_matches;
|
|
status = ImposedMatchParser::kSuccess;
|
|
num_identical = 0;
|
|
matches.clear();
|
|
bad_matches.clear();
|
|
ImposedMatchParser parser;
|
|
status = parser.Parse(imposed_matches, old_image, new_image,
|
|
base::BindRepeating(&TestElementDetector::Run,
|
|
base::Unretained(&detector)));
|
|
num_identical = parser.num_identical();
|
|
matches = std::move(*parser.mutable_matches());
|
|
bad_matches = std::move(*parser.mutable_bad_matches());
|
|
return status == ImposedMatchParser::kSuccess;
|
|
};
|
|
|
|
auto run_check = [&](const ElementMatch& match, ExecutableType exe_type,
|
|
offset_t old_offset, size_t old_size,
|
|
offset_t new_offset, size_t new_size) {
|
|
EXPECT_EQ(exe_type, match.exe_type()) << prev_imposed_matches;
|
|
EXPECT_EQ(exe_type, match.old_element.exe_type) << prev_imposed_matches;
|
|
EXPECT_EQ(old_offset, match.old_element.offset) << prev_imposed_matches;
|
|
EXPECT_EQ(old_size, match.old_element.size) << prev_imposed_matches;
|
|
EXPECT_EQ(exe_type, match.new_element.exe_type) << prev_imposed_matches;
|
|
EXPECT_EQ(new_offset, match.new_element.offset) << prev_imposed_matches;
|
|
EXPECT_EQ(new_size, match.new_element.size) << prev_imposed_matches;
|
|
};
|
|
|
|
// Empty string: Vacuous but valid.
|
|
EXPECT_TRUE(run_test(""));
|
|
EXPECT_EQ(0U, num_identical);
|
|
EXPECT_EQ(0U, matches.size());
|
|
EXPECT_EQ(0U, bad_matches.size());
|
|
|
|
// Full matches. Different permutations give same result.
|
|
for (const std::string& imposed_matches :
|
|
{"1+2=12+4,4+2=5+2,6+4=2+3", "1+2=12+4,6+4=2+3,4+2=5+2",
|
|
"4+2=5+2,1+2=12+4,6+4=2+3", "4+2=5+2,6+4=2+3,1+2=12+4",
|
|
"6+4=2+3,1+2=12+4,4+2=5+2", "6+4=2+3,1+2=12+4,4+2=5+2"}) {
|
|
EXPECT_TRUE(run_test(imposed_matches));
|
|
EXPECT_EQ(1U, num_identical); // "4+2=5+2"
|
|
EXPECT_EQ(2U, matches.size());
|
|
// Results are sorted by "new" offsets.
|
|
run_check(matches[0], kExeTypeElfX86, 6, 4, 2, 3);
|
|
run_check(matches[1], kExeTypeWin32X86, 1, 2, 12, 4);
|
|
EXPECT_EQ(0U, bad_matches.size());
|
|
}
|
|
|
|
// Single subregion match.
|
|
EXPECT_TRUE(run_test("1+2=12+4"));
|
|
EXPECT_EQ(0U, num_identical);
|
|
EXPECT_EQ(1U, matches.size());
|
|
run_check(matches[0], kExeTypeWin32X86, 1, 2, 12, 4);
|
|
EXPECT_EQ(0U, bad_matches.size());
|
|
|
|
// Single subregion match. We're lax with redundant 0.
|
|
EXPECT_TRUE(run_test("6+04=02+10"));
|
|
EXPECT_EQ(0U, num_identical);
|
|
EXPECT_EQ(1U, matches.size());
|
|
run_check(matches[0], kExeTypeElfX86, 6, 4, 2, 10);
|
|
EXPECT_EQ(0U, bad_matches.size());
|
|
|
|
// Successive elements, no overlap.
|
|
EXPECT_TRUE(run_test("1+1=12+1,2+1=13+1"));
|
|
EXPECT_EQ(0U, num_identical);
|
|
EXPECT_EQ(2U, matches.size());
|
|
run_check(matches[0], kExeTypeWin32X86, 1, 1, 12, 1);
|
|
run_check(matches[1], kExeTypeWin32X86, 2, 1, 13, 1);
|
|
EXPECT_EQ(0U, bad_matches.size());
|
|
|
|
// Overlap in "old" file is okay.
|
|
EXPECT_TRUE(run_test("1+2=12+2,1+2=14+2"));
|
|
EXPECT_EQ(0U, num_identical);
|
|
EXPECT_EQ(2U, matches.size());
|
|
run_check(matches[0], kExeTypeWin32X86, 1, 2, 12, 2);
|
|
run_check(matches[1], kExeTypeWin32X86, 1, 2, 14, 2);
|
|
EXPECT_EQ(0U, bad_matches.size());
|
|
|
|
// Entire files: Have unknown type, so are recognized as such, and ignored.
|
|
EXPECT_TRUE(run_test("0+10=0+18"));
|
|
EXPECT_EQ(0U, num_identical);
|
|
EXPECT_EQ(0U, matches.size());
|
|
EXPECT_EQ(1U, bad_matches.size());
|
|
run_check(bad_matches[0], kExeTypeUnknown, 0, 10, 0, 18);
|
|
|
|
// Forgive matches that mix known type with unknown type.
|
|
EXPECT_TRUE(run_test("1+2=0+18"));
|
|
EXPECT_EQ(0U, num_identical);
|
|
EXPECT_EQ(0U, matches.size());
|
|
EXPECT_EQ(1U, bad_matches.size());
|
|
run_check(bad_matches[0], kExeTypeUnknown, 1, 2, 0, 18);
|
|
|
|
EXPECT_TRUE(run_test("0+10=12+4"));
|
|
EXPECT_EQ(0U, num_identical);
|
|
EXPECT_EQ(0U, matches.size());
|
|
EXPECT_EQ(1U, bad_matches.size());
|
|
run_check(bad_matches[0], kExeTypeUnknown, 0, 10, 12, 4);
|
|
|
|
// Test invalid delimiter.
|
|
for (const std::string& imposed_matches :
|
|
{"1+2=12+4,4+2=5+2x", "1+2=12+4 4+2=5+2", "1+2=12+4,4+2=5+2 ",
|
|
"1+2=12+4 "}) {
|
|
EXPECT_FALSE(run_test(imposed_matches));
|
|
EXPECT_EQ(ImposedMatchParser::kInvalidDelimiter, status);
|
|
}
|
|
|
|
// Test parse errors, including uint32_t overflow.
|
|
for (const std::string& imposed_matches :
|
|
{"x1+2=12+4,4+2=5+2,6+4=2+3", "x1+2=12+4,4+2=5+2,6+4=2+3x", ",", " ",
|
|
"+2=12+4", "1+2+12+4", "1=2+12+4", " 1+2=12+4", "1+2= 12+4", "1", "1+2",
|
|
"1+2=", "1+2=12", "1+2=12+", "4294967296+2=12+4"}) {
|
|
EXPECT_FALSE(run_test(imposed_matches));
|
|
EXPECT_EQ(ImposedMatchParser::kParseError, status);
|
|
}
|
|
|
|
// Test bound errors, include 0-size.
|
|
for (const std::string& imposed_matches :
|
|
{"1+10=12+4", "1+2=12+7", "0+11=0+18", "0+12=0+17", "10+1=0+18",
|
|
"0+10=18+1", "0+0=0+18", "0+10=0+0", "1000000000+1=0+1000000000"}) {
|
|
EXPECT_FALSE(run_test(imposed_matches));
|
|
EXPECT_EQ(ImposedMatchParser::kOutOfBound, status);
|
|
}
|
|
|
|
// Test overlap errors. Matches that get ignored are still tested.
|
|
for (const std::string& imposed_matches :
|
|
{"1+2=12+4,4+2=5+2,6+4=2+4", "0+10=0+18,1+2=12+4", "6+4=2+10,3+2=5+2"}) {
|
|
EXPECT_FALSE(run_test(imposed_matches));
|
|
EXPECT_EQ(ImposedMatchParser::kOverlapInNew, status);
|
|
}
|
|
|
|
// Test type mismatch errors.
|
|
EXPECT_FALSE(run_test("1+2=2+3"));
|
|
EXPECT_EQ(ImposedMatchParser::kTypeMismatch, status);
|
|
|
|
EXPECT_FALSE(run_test("6+4=12+4"));
|
|
EXPECT_EQ(ImposedMatchParser::kTypeMismatch, status);
|
|
}
|
|
|
|
} // namespace zucchini
|