xref: /aosp_15_r20/external/zucchini/imposed_ensemble_matcher.cc (revision a03ca8b91e029cd15055c20c78c2e087c84792e4)
1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/zucchini/imposed_ensemble_matcher.h"
6 
7 #include <algorithm>
8 #include <sstream>
9 #include <utility>
10 
11 #include "base/bind.h"
12 #include "base/logging.h"
13 #include "components/zucchini/io_utils.h"
14 
15 namespace zucchini {
16 
17 /******** ImposedMatchParser ********/
18 
19 ImposedMatchParser::ImposedMatchParser() = default;
20 
21 ImposedMatchParser::~ImposedMatchParser() = default;
22 
Parse(std::string imposed_matches,ConstBufferView old_image,ConstBufferView new_image,ElementDetector && detector)23 ImposedMatchParser::Status ImposedMatchParser::Parse(
24     std::string imposed_matches,
25     ConstBufferView old_image,
26     ConstBufferView new_image,
27     ElementDetector&& detector) {
28   CHECK(matches_.empty());
29   CHECK(bad_matches_.empty());
30 
31   // Parse |imposed_matches| and check bounds.
32   std::istringstream iss(std::move(imposed_matches));
33   bool first = true;
34   iss.peek();  // Makes empty |iss| realize EOF is reached.
35   while (iss && !iss.eof()) {
36     // Eat delimiter.
37     if (first) {
38       first = false;
39     } else if (!(iss >> EatChar(','))) {
40       return kInvalidDelimiter;
41     }
42     // Extract parameters for one imposed match.
43     offset_t old_offset = 0U;
44     size_t old_size = 0U;
45     offset_t new_offset = 0U;
46     size_t new_size = 0U;
47     if (!(iss >> StrictUInt<offset_t>(old_offset) >> EatChar('+') >>
48           StrictUInt<size_t>(old_size) >> EatChar('=') >>
49           StrictUInt<offset_t>(new_offset) >> EatChar('+') >>
50           StrictUInt<size_t>(new_size))) {
51       return kParseError;
52     }
53     // Check bounds.
54     if (old_size == 0 || new_size == 0 ||
55         !old_image.covers({old_offset, old_size}) ||
56         !new_image.covers({new_offset, new_size})) {
57       return kOutOfBound;
58     }
59     matches_.push_back(
60         {{{old_offset, old_size}, kExeTypeUnknown},    // Assign type later.
61          {{new_offset, new_size}, kExeTypeUnknown}});  // Assign type later.
62   }
63   // Sort matches by "new" file offsets. This helps with overlap checks.
64   std::sort(matches_.begin(), matches_.end(),
65             [](const ElementMatch& match_a, const ElementMatch& match_b) {
66               return match_a.new_element.offset < match_b.new_element.offset;
67             });
68 
69   // Check for overlaps in "new" file.
70   if (std::adjacent_find(
71           matches_.begin(), matches_.end(),
72           [](const ElementMatch& match1, const ElementMatch& match2) {
73             return match1.new_element.hi() > match2.new_element.lo();
74           }) != matches_.end()) {
75     return kOverlapInNew;
76   }
77 
78   // Compute types and verify consistency. Remove identical matches and matches
79   // where any sub-image has an unknown type.
80   size_t write_idx = 0;
81   for (size_t read_idx = 0; read_idx < matches_.size(); ++read_idx) {
82     ConstBufferView old_sub_image(
83         old_image[matches_[read_idx].old_element.region()]);
84     ConstBufferView new_sub_image(
85         new_image[matches_[read_idx].new_element.region()]);
86     // Remove identical match.
87     if (old_sub_image.equals(new_sub_image)) {
88       ++num_identical_;
89       continue;
90     }
91     // Check executable types of sub-images.
92     std::optional<Element> old_element = detector.Run(old_sub_image);
93     std::optional<Element> new_element = detector.Run(new_sub_image);
94     if (!old_element || !new_element) {
95       // Skip unknown types, including those mixed with known types.
96       bad_matches_.push_back(matches_[read_idx]);
97       continue;
98     } else if (old_element->exe_type != new_element->exe_type) {
99       // Error if types are known, but inconsistent.
100       return kTypeMismatch;
101     }
102 
103     // Keep match and remove gaps.
104     matches_[read_idx].old_element.exe_type = old_element->exe_type;
105     matches_[read_idx].new_element.exe_type = new_element->exe_type;
106     if (write_idx < read_idx)
107       matches_[write_idx] = matches_[read_idx];
108     ++write_idx;
109   }
110   matches_.resize(write_idx);
111   return kSuccess;
112 }
113 
114 /******** ImposedEnsembleMatcher ********/
115 
ImposedEnsembleMatcher(const std::string & imposed_matches)116 ImposedEnsembleMatcher::ImposedEnsembleMatcher(
117     const std::string& imposed_matches)
118     : imposed_matches_(imposed_matches) {}
119 
120 ImposedEnsembleMatcher::~ImposedEnsembleMatcher() = default;
121 
RunMatch(ConstBufferView old_image,ConstBufferView new_image)122 bool ImposedEnsembleMatcher::RunMatch(ConstBufferView old_image,
123                                       ConstBufferView new_image) {
124   DCHECK(matches_.empty());
125   LOG(INFO) << "Start matching.";
126   ImposedMatchParser parser;
127   ImposedMatchParser::Status status =
128       parser.Parse(std::move(imposed_matches_), old_image, new_image,
129                    base::BindRepeating(DetectElementFromDisassembler));
130   // Print all warnings first.
131   for (const ElementMatch& bad_match : *parser.mutable_bad_matches())
132     LOG(WARNING) << "Skipped match with unknown type: " << bad_match.ToString();
133   if (status != ImposedMatchParser::kSuccess) {
134     LOG(ERROR) << "Imposed match failed with error code " << status << ".";
135     return false;
136   }
137   num_identical_ = parser.num_identical();
138   matches_ = std::move(*parser.mutable_matches());
139   Trim();
140   return true;
141 }
142 
143 }  // namespace zucchini
144