aboutsummaryrefslogtreecommitdiff
path: root/imposed_ensemble_matcher.cc
blob: 1c1301b0653c474e36b4aee01cf9a4f722c749ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// 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.
    absl::optional<Element> old_element = detector.Run(old_sub_image);
    absl::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