blob: e735bc4e79e1ee71f00a9d7ed2130015c1dc5c6b [file] [log] [blame]
// 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 "components/zucchini/imposed_ensemble_matcher.h"
#include <algorithm>
#include <sstream>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "components/zucchini/io_utils.h"
namespace zucchini {
/******** ImposedMatchParser ********/
ImposedMatchParser::ImposedMatchParser() = default;
ImposedMatchParser::~ImposedMatchParser() = default;
ImposedMatchParser::Status ImposedMatchParser::Parse(
std::string imposed_matches,
ConstBufferView old_image,
ConstBufferView new_image,
ElementDetector&& detector) {
CHECK(matches_.empty());
CHECK(bad_matches_.empty());
// Parse |imposed_matches| and check bounds.
std::istringstream iss(std::move(imposed_matches));
bool first = true;
iss.peek(); // Makes empty |iss| realize EOF is reached.
while (iss && !iss.eof()) {
// Eat delimiter.
if (first) {
first = false;
} else if (!(iss >> EatChar(','))) {
return kInvalidDelimiter;
}
// Extract parameters for one imposed match.
offset_t old_offset = 0U;
size_t old_size = 0U;
offset_t new_offset = 0U;
size_t new_size = 0U;
if (!(iss >> StrictUInt<offset_t>(old_offset) >> EatChar('+') >>
StrictUInt<size_t>(old_size) >> EatChar('=') >>
StrictUInt<offset_t>(new_offset) >> EatChar('+') >>
StrictUInt<size_t>(new_size))) {
return kParseError;
}
// Check bounds.
if (old_size == 0 || new_size == 0 ||
!old_image.covers({old_offset, old_size}) ||
!new_image.covers({new_offset, new_size})) {
return kOutOfBound;
}
matches_.push_back(
{{{old_offset, old_size}, kExeTypeUnknown}, // Assign type later.
{{new_offset, new_size}, kExeTypeUnknown}}); // Assign type later.
}
// Sort matches by "new" file offsets. This helps with overlap checks.
std::sort(matches_.begin(), matches_.end(),
[](const ElementMatch& match_a, const ElementMatch& match_b) {
return match_a.new_element.offset < match_b.new_element.offset;
});
// Check for overlaps in "new" file.
if (std::adjacent_find(
matches_.begin(), matches_.end(),
[](const ElementMatch& match1, const ElementMatch& match2) {
return match1.new_element.hi() > match2.new_element.lo();
}) != matches_.end()) {
return kOverlapInNew;
}
// Compute types and verify consistency. Remove identical matches and matches
// where any sub-image has an unknown type.
size_t write_idx = 0;
for (size_t read_idx = 0; read_idx < matches_.size(); ++read_idx) {
ConstBufferView old_sub_image(
old_image[matches_[read_idx].old_element.region()]);
ConstBufferView new_sub_image(
new_image[matches_[read_idx].new_element.region()]);
// Remove identical match.
if (old_sub_image.equals(new_sub_image)) {
++num_identical_;
continue;
}
// Check executable types of sub-images.
base::Optional<Element> old_element = detector.Run(old_sub_image);
base::Optional<Element> new_element = detector.Run(new_sub_image);
if (!old_element || !new_element) {
// Skip unknown types, including those mixed with known types.
bad_matches_.push_back(matches_[read_idx]);
continue;
} else if (old_element->exe_type != new_element->exe_type) {
// Error if types are known, but inconsistent.
return kTypeMismatch;
}
// Keep match and remove gaps.
matches_[read_idx].old_element.exe_type = old_element->exe_type;
matches_[read_idx].new_element.exe_type = new_element->exe_type;
if (write_idx < read_idx)
matches_[write_idx] = matches_[read_idx];
++write_idx;
}
matches_.resize(write_idx);
return kSuccess;
}
/******** ImposedEnsembleMatcher ********/
ImposedEnsembleMatcher::ImposedEnsembleMatcher(
const std::string& imposed_matches)
: imposed_matches_(imposed_matches) {}
ImposedEnsembleMatcher::~ImposedEnsembleMatcher() = default;
bool ImposedEnsembleMatcher::RunMatch(ConstBufferView old_image,
ConstBufferView new_image) {
DCHECK(matches_.empty());
LOG(INFO) << "Start matching.";
ImposedMatchParser parser;
ImposedMatchParser::Status status =
parser.Parse(std::move(imposed_matches_), old_image, new_image,
base::BindRepeating(DetectElementFromDisassembler));
// Print all warnings first.
for (const ElementMatch& bad_match : *parser.mutable_bad_matches())
LOG(WARNING) << "Skipped match with unknown type: " << bad_match.ToString();
if (status != ImposedMatchParser::kSuccess) {
LOG(ERROR) << "Imposed match failed with error code " << status << ".";
return false;
}
num_identical_ = parser.num_identical();
matches_ = std::move(*parser.mutable_matches());
Trim();
return true;
}
} // namespace zucchini