blob: 15f11ae65e84801589e1517632fb876639a83d90 [file] [log] [blame]
Amin Hassanif94b6432018-01-26 17:39:47 -08001#
2# Copyright (C) 2013 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
Gilad Arnold553b0ec2013-01-26 01:00:39 -080016
17"""Verifying the integrity of a Chrome OS update payload.
18
19This module is used internally by the main Payload class for verifying the
20integrity of an update payload. The interface for invoking the checks is as
21follows:
22
23 checker = PayloadChecker(payload)
24 checker.Run(...)
Gilad Arnold553b0ec2013-01-26 01:00:39 -080025"""
26
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080027from __future__ import print_function
28
Gilad Arnold553b0ec2013-01-26 01:00:39 -080029import array
30import base64
Tudor Brindus8d05a7e2018-06-14 11:18:18 -070031import collections
Gilad Arnold553b0ec2013-01-26 01:00:39 -080032import hashlib
Gilad Arnoldcb638912013-06-24 04:57:11 -070033import itertools
Gilad Arnold9b90c932013-05-22 17:12:56 -070034import os
Gilad Arnold553b0ec2013-01-26 01:00:39 -080035import subprocess
36
Amin Hassanib05a65a2017-12-18 15:15:32 -080037from update_payload import common
38from update_payload import error
39from update_payload import format_utils
40from update_payload import histogram
41from update_payload import update_metadata_pb2
Gilad Arnold553b0ec2013-01-26 01:00:39 -080042
43
44#
Gilad Arnold9b90c932013-05-22 17:12:56 -070045# Constants.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080046#
Gilad Arnoldcb638912013-06-24 04:57:11 -070047
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070048_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents'
49_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
50_CHECK_PAYLOAD_SIG = 'payload-sig'
51CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070052 _CHECK_DST_PSEUDO_EXTENTS,
53 _CHECK_MOVE_SAME_SRC_DST_BLOCK,
54 _CHECK_PAYLOAD_SIG,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070055)
56
Gilad Arnold553b0ec2013-01-26 01:00:39 -080057_TYPE_FULL = 'full'
58_TYPE_DELTA = 'delta'
59
60_DEFAULT_BLOCK_SIZE = 4096
61
Gilad Arnold9b90c932013-05-22 17:12:56 -070062_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem'
63_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
64 _DEFAULT_PUBKEY_BASE_NAME)
65
Gilad Arnold0d575cd2015-07-13 17:29:21 -070066# Supported minor version map to payload types allowed to be using them.
67_SUPPORTED_MINOR_VERSIONS = {
68 0: (_TYPE_FULL,),
69 1: (_TYPE_DELTA,),
70 2: (_TYPE_DELTA,),
Sen Jiang912c4df2015-12-10 12:17:13 -080071 3: (_TYPE_DELTA,),
Sen Jiang92161a72016-06-28 16:09:38 -070072 4: (_TYPE_DELTA,),
Amin Hassani77d7cbc2018-02-07 16:21:33 -080073 5: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070074}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080075
Gilad Arnold06eea332015-07-13 18:06:33 -070076_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
77
Gilad Arnold553b0ec2013-01-26 01:00:39 -080078#
79# Helper functions.
80#
Gilad Arnoldcb638912013-06-24 04:57:11 -070081
Gilad Arnold553b0ec2013-01-26 01:00:39 -080082def _IsPowerOfTwo(val):
83 """Returns True iff val is a power of two."""
84 return val > 0 and (val & (val - 1)) == 0
85
86
87def _AddFormat(format_func, value):
88 """Adds a custom formatted representation to ordinary string representation.
89
90 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070091 format_func: A value formatter.
92 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080093
Gilad Arnold553b0ec2013-01-26 01:00:39 -080094 Returns:
95 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080096 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070097 ret = str(value)
98 formatted_str = format_func(value)
99 if formatted_str:
100 ret += ' (%s)' % formatted_str
101 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800102
103
104def _AddHumanReadableSize(size):
105 """Adds a human readable representation to a byte size value."""
106 return _AddFormat(format_utils.BytesToHumanReadable, size)
107
108
109#
110# Payload report generator.
111#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700112
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800113class _PayloadReport(object):
114 """A payload report generator.
115
116 A report is essentially a sequence of nodes, which represent data points. It
117 is initialized to have a "global", untitled section. A node may be a
118 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800119 """
120
Gilad Arnoldcb638912013-06-24 04:57:11 -0700121 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800122 class Node(object):
123 """A report node interface."""
124
125 @staticmethod
126 def _Indent(indent, line):
127 """Indents a line by a given indentation amount.
128
129 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700130 indent: The indentation amount.
131 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800132
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800133 Returns:
134 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800135 """
136 return '%*s%s' % (indent, '', line)
137
138 def GenerateLines(self, base_indent, sub_indent, curr_section):
139 """Generates the report lines for this node.
140
141 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700142 base_indent: Base indentation for each line.
143 sub_indent: Additional indentation for sub-nodes.
144 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800145
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800146 Returns:
147 A pair consisting of a list of properly indented report lines and a new
148 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800149 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700150 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800151
152 class FieldNode(Node):
153 """A field report node, representing a (name, value) pair."""
154
155 def __init__(self, name, value, linebreak, indent):
156 super(_PayloadReport.FieldNode, self).__init__()
157 self.name = name
158 self.value = value
159 self.linebreak = linebreak
160 self.indent = indent
161
162 def GenerateLines(self, base_indent, sub_indent, curr_section):
163 """Generates a properly formatted 'name : value' entry."""
164 report_output = ''
165 if self.name:
166 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
167 value_lines = str(self.value).splitlines()
168 if self.linebreak and self.name:
169 report_output += '\n' + '\n'.join(
170 ['%*s%s' % (self.indent, '', line) for line in value_lines])
171 else:
172 if self.name:
173 report_output += ' '
174 report_output += '%*s' % (self.indent, '')
175 cont_line_indent = len(report_output)
176 indented_value_lines = [value_lines[0]]
177 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
178 for line in value_lines[1:]])
179 report_output += '\n'.join(indented_value_lines)
180
181 report_lines = [self._Indent(base_indent, line + '\n')
182 for line in report_output.split('\n')]
183 return report_lines, curr_section
184
185 class SubReportNode(Node):
186 """A sub-report node, representing a nested report."""
187
188 def __init__(self, title, report):
189 super(_PayloadReport.SubReportNode, self).__init__()
190 self.title = title
191 self.report = report
192
193 def GenerateLines(self, base_indent, sub_indent, curr_section):
194 """Recurse with indentation."""
195 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
196 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
197 sub_indent))
198 return report_lines, curr_section
199
200 class SectionNode(Node):
201 """A section header node."""
202
203 def __init__(self, title=None):
204 super(_PayloadReport.SectionNode, self).__init__()
205 self.title = title
206 self.max_field_name_len = 0
207
208 def GenerateLines(self, base_indent, sub_indent, curr_section):
209 """Dump a title line, return self as the (new) current section."""
210 report_lines = []
211 if self.title:
212 report_lines.append(self._Indent(base_indent,
213 '=== %s ===\n' % self.title))
214 return report_lines, self
215
216 def __init__(self):
217 self.report = []
218 self.last_section = self.global_section = self.SectionNode()
219 self.is_finalized = False
220
221 def GenerateLines(self, base_indent, sub_indent):
222 """Generates the lines in the report, properly indented.
223
224 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700225 base_indent: The indentation used for root-level report lines.
226 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800227
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800228 Returns:
229 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800230 """
231 report_lines = []
232 curr_section = self.global_section
233 for node in self.report:
234 node_report_lines, curr_section = node.GenerateLines(
235 base_indent, sub_indent, curr_section)
236 report_lines.extend(node_report_lines)
237
238 return report_lines
239
240 def Dump(self, out_file, base_indent=0, sub_indent=2):
241 """Dumps the report to a file.
242
243 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700244 out_file: File object to output the content to.
245 base_indent: Base indentation for report lines.
246 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800247 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800248 report_lines = self.GenerateLines(base_indent, sub_indent)
249 if report_lines and not self.is_finalized:
250 report_lines.append('(incomplete report)\n')
251
252 for line in report_lines:
253 out_file.write(line)
254
255 def AddField(self, name, value, linebreak=False, indent=0):
256 """Adds a field/value pair to the payload report.
257
258 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700259 name: The field's name.
260 value: The field's value.
261 linebreak: Whether the value should be printed on a new line.
262 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800263 """
264 assert not self.is_finalized
265 if name and self.last_section.max_field_name_len < len(name):
266 self.last_section.max_field_name_len = len(name)
267 self.report.append(self.FieldNode(name, value, linebreak, indent))
268
269 def AddSubReport(self, title):
270 """Adds and returns a sub-report with a title."""
271 assert not self.is_finalized
272 sub_report = self.SubReportNode(title, type(self)())
273 self.report.append(sub_report)
274 return sub_report.report
275
276 def AddSection(self, title):
277 """Adds a new section title."""
278 assert not self.is_finalized
279 self.last_section = self.SectionNode(title)
280 self.report.append(self.last_section)
281
282 def Finalize(self):
283 """Seals the report, marking it as complete."""
284 self.is_finalized = True
285
286
287#
288# Payload verification.
289#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700290
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800291class PayloadChecker(object):
292 """Checking the integrity of an update payload.
293
294 This is a short-lived object whose purpose is to isolate the logic used for
295 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800296 """
297
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700298 def __init__(self, payload, assert_type=None, block_size=0,
299 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700300 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700301
302 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700303 payload: The payload object to check.
304 assert_type: Assert that payload is either 'full' or 'delta' (optional).
305 block_size: Expected filesystem / payload block size (optional).
306 allow_unhashed: Allow operations with unhashed data blobs.
307 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700308 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700309 if not payload.is_init:
310 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700311
312 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800313 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700314 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
315 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700316 raise error.PayloadError(
317 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700318 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700319 raise error.PayloadError('Invalid assert_type value (%r).' %
320 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700321 self.payload_type = assert_type
322 self.allow_unhashed = allow_unhashed
323
324 # Disable specific tests.
325 self.check_dst_pseudo_extents = (
326 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
327 self.check_move_same_src_dst_block = (
328 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
329 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800330
331 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800332 self.sigs_offset = 0
333 self.sigs_size = 0
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700334 self.old_part_info = {}
335 self.new_part_info = {}
336 self.new_fs_sizes = collections.defaultdict(int)
337 self.old_fs_sizes = collections.defaultdict(int)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700338 self.minor_version = None
Tudor Brindus40506cd2018-06-18 20:18:17 -0700339 self.major_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800340
341 @staticmethod
342 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
343 msg_name=None, linebreak=False, indent=0):
344 """Adds an element from a protobuf message to the payload report.
345
346 Checks to see whether a message contains a given element, and if so adds
347 the element value to the provided report. A missing mandatory element
348 causes an exception to be raised.
349
350 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700351 msg: The message containing the element.
352 name: The name of the element.
353 report: A report object to add the element name/value to.
354 is_mandatory: Whether or not this element must be present.
355 is_submsg: Whether this element is itself a message.
356 convert: A function for converting the element value for reporting.
357 msg_name: The name of the message object (for error reporting).
358 linebreak: Whether the value report should induce a line break.
359 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800360
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800361 Returns:
362 A pair consisting of the element value and the generated sub-report for
363 it (if the element is a sub-message, None otherwise). If the element is
364 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800365
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800366 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700367 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800368 """
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700369 element_result = collections.namedtuple('element_result', ['msg', 'report'])
370
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800371 if not msg.HasField(name):
372 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700373 raise error.PayloadError('%smissing mandatory %s %r.' %
374 (msg_name + ' ' if msg_name else '',
375 'sub-message' if is_submsg else 'field',
376 name))
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700377 return element_result(None, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800378
379 value = getattr(msg, name)
380 if is_submsg:
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700381 return element_result(value, report and report.AddSubReport(name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800382 else:
383 if report:
384 report.AddField(name, convert(value), linebreak=linebreak,
385 indent=indent)
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700386 return element_result(value, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800387
388 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700389 def _CheckRepeatedElemNotPresent(msg, field_name, msg_name):
390 """Checks that a repeated element is not specified in the message.
391
392 Args:
393 msg: The message containing the element.
394 field_name: The name of the element.
395 msg_name: The name of the message object (for error reporting).
396
397 Raises:
398 error.PayloadError if the repeated element is present or non-empty.
399 """
400 if getattr(msg, field_name, None):
401 raise error.PayloadError('%sfield %r not empty.' %
402 (msg_name + ' ' if msg_name else '', field_name))
403
404 @staticmethod
405 def _CheckElemNotPresent(msg, field_name, msg_name):
406 """Checks that an element is not specified in the message.
407
408 Args:
409 msg: The message containing the element.
410 field_name: The name of the element.
411 msg_name: The name of the message object (for error reporting).
412
413 Raises:
414 error.PayloadError if the repeated element is present.
415 """
416 if msg.HasField(field_name):
417 raise error.PayloadError('%sfield %r exists.' %
418 (msg_name + ' ' if msg_name else '', field_name))
419
420 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800421 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
422 linebreak=False, indent=0):
423 """Adds a mandatory field; returning first component from _CheckElem."""
424 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
425 convert=convert, msg_name=msg_name,
426 linebreak=linebreak, indent=indent)[0]
427
428 @staticmethod
429 def _CheckOptionalField(msg, field_name, report, convert=str,
430 linebreak=False, indent=0):
431 """Adds an optional field; returning first component from _CheckElem."""
432 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
433 convert=convert, linebreak=linebreak,
434 indent=indent)[0]
435
436 @staticmethod
437 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
438 """Adds a mandatory sub-message; wrapper for _CheckElem."""
439 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
440 msg_name)
441
442 @staticmethod
443 def _CheckOptionalSubMsg(msg, submsg_name, report):
444 """Adds an optional sub-message; wrapper for _CheckElem."""
445 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
446
447 @staticmethod
448 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
449 """Checks that val1 is None iff val2 is None.
450
451 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700452 val1: first value to be compared.
453 val2: second value to be compared.
454 name1: name of object holding the first value.
455 name2: name of object holding the second value.
456 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800457
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800458 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700459 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800460 """
461 if None in (val1, val2) and val1 is not val2:
462 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700463 raise error.PayloadError('%r present without %r%s.' %
464 (present, missing,
465 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800466
467 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700468 def _CheckPresentIffMany(vals, name, obj_name):
469 """Checks that a set of vals and names imply every other element.
470
471 Args:
472 vals: The set of values to be compared.
473 name: The name of the objects holding the corresponding value.
474 obj_name: Name of the object containing these values.
475
476 Raises:
477 error.PayloadError if assertion does not hold.
478 """
479 if any(vals) and not all(vals):
480 raise error.PayloadError('%r is not present in all values%s.' %
481 (name, ' in ' + obj_name if obj_name else ''))
482
483 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800484 def _Run(cmd, send_data=None):
485 """Runs a subprocess, returns its output.
486
487 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700488 cmd: Sequence of command-line argument for invoking the subprocess.
489 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800490
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800491 Returns:
492 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800493 """
494 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
495 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700496 try:
497 result = run_process.communicate(input=send_data)
498 finally:
499 exit_code = run_process.wait()
500
501 if exit_code:
502 raise RuntimeError('Subprocess %r failed with code %r.' %
503 (cmd, exit_code))
504
505 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800506
507 @staticmethod
508 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
509 """Verifies an actual hash against a signed one.
510
511 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700512 sig_data: The raw signature data.
513 pubkey_file_name: Public key used for verifying signature.
514 actual_hash: The actual hash digest.
515 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800516
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800517 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700518 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800519 """
520 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700521 raise error.PayloadError(
522 '%s: signature size (%d) not as expected (256).' %
523 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800524 signed_data, _ = PayloadChecker._Run(
525 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
526 send_data=sig_data)
527
Gilad Arnold5502b562013-03-08 13:22:31 -0800528 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700529 raise error.PayloadError('%s: unexpected signed data length (%d).' %
530 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800531
Gilad Arnold5502b562013-03-08 13:22:31 -0800532 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700533 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
534 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800535
Gilad Arnold5502b562013-03-08 13:22:31 -0800536 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800537 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700538 raise error.PayloadError(
539 '%s: signed hash (%s) different from actual (%s).' %
540 (sig_name, common.FormatSha256(signed_hash),
541 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800542
543 @staticmethod
544 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
545 block_name=None):
546 """Checks that a given length fits given block space.
547
548 This ensures that the number of blocks allocated is appropriate for the
549 length of the data residing in these blocks.
550
551 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700552 length: The actual length of the data.
553 num_blocks: The number of blocks allocated for it.
554 block_size: The size of each block in bytes.
555 length_name: Name of length (used for error reporting).
556 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800557
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800558 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700559 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800560 """
561 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700562 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700563 raise error.PayloadError(
564 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800565 (length_name, length, block_name or '', num_blocks, block_size))
566
567 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700568 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700569 raise error.PayloadError(
570 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800571 (length_name, length, block_name or '', num_blocks - 1, block_size))
572
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700573 def _CheckManifestMinorVersion(self, report):
574 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800575
576 Args:
577 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800578
579 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700580 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800581 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700582 self.minor_version = self._CheckOptionalField(self.payload.manifest,
583 'minor_version', report)
584 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
585 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800586 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700587 'Minor version %d not compatible with payload type %s.' %
588 (self.minor_version, self.payload_type))
589 elif self.minor_version is None:
590 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800591 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700592 raise error.PayloadError('Unsupported minor version: %d' %
593 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800594
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700595 def _CheckManifest(self, report, part_sizes=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800596 """Checks the payload manifest.
597
598 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700599 report: A report object to add to.
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700600 part_sizes: Map of partition label to partition size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800601
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800602 Returns:
603 A tuple consisting of the partition block size used during the update
604 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800605
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800606 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700607 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800608 """
Tudor Brindus40506cd2018-06-18 20:18:17 -0700609 self.major_version = self.payload.header.version
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700610
Tudor Brindus40506cd2018-06-18 20:18:17 -0700611 part_sizes = collections.defaultdict(int, part_sizes)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800612 manifest = self.payload.manifest
613 report.AddSection('manifest')
614
615 # Check: block_size must exist and match the expected value.
616 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
617 report, 'manifest')
618 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700619 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
620 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800621
622 # Check: signatures_offset <==> signatures_size.
623 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
624 report)
625 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
626 report)
627 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
628 'signatures_offset', 'signatures_size', 'manifest')
629
Amin Hassani8ea19572018-12-05 12:06:21 -0800630 if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
Tudor Brindusb220d662018-07-10 23:55:51 -0700631 for real_name, proto_name in common.CROS_PARTITIONS:
632 self.old_part_info[real_name] = self._CheckOptionalSubMsg(
633 manifest, 'old_%s_info' % proto_name, report)
634 self.new_part_info[real_name] = self._CheckMandatorySubMsg(
635 manifest, 'new_%s_info' % proto_name, report, 'manifest')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700636
Tudor Brindus40506cd2018-06-18 20:18:17 -0700637 # Check: old_kernel_info <==> old_rootfs_info.
638 self._CheckPresentIff(self.old_part_info[common.KERNEL].msg,
639 self.old_part_info[common.ROOTFS].msg,
640 'old_kernel_info', 'old_rootfs_info', 'manifest')
641 else:
642 for part in manifest.partitions:
643 name = part.partition_name
644 self.old_part_info[name] = self._CheckOptionalSubMsg(
645 part, 'old_partition_info', report)
646 self.new_part_info[name] = self._CheckMandatorySubMsg(
647 part, 'new_partition_info', report, 'manifest.partitions')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700648
Tudor Brindus40506cd2018-06-18 20:18:17 -0700649 # Check: Old-style partition infos should not be specified.
Tudor Brindusb220d662018-07-10 23:55:51 -0700650 for _, part in common.CROS_PARTITIONS:
Tudor Brindus40506cd2018-06-18 20:18:17 -0700651 self._CheckElemNotPresent(manifest, 'old_%s_info' % part, 'manifest')
652 self._CheckElemNotPresent(manifest, 'new_%s_info' % part, 'manifest')
653
654 # Check: If old_partition_info is specified anywhere, it must be
655 # specified everywhere.
656 old_part_msgs = [part.msg for part in self.old_part_info.values() if part]
657 self._CheckPresentIffMany(old_part_msgs, 'old_partition_info',
658 'manifest.partitions')
659
660 is_delta = any(part and part.msg for part in self.old_part_info.values())
661 if is_delta:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800662 # Assert/mark delta payload.
663 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700664 raise error.PayloadError(
665 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800666 self.payload_type = _TYPE_DELTA
667
Tudor Brindus40506cd2018-06-18 20:18:17 -0700668 for part, (msg, part_report) in self.old_part_info.iteritems():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700669 # Check: {size, hash} present in old_{kernel,rootfs}_info.
670 field = 'old_%s_info' % part
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700671 self.old_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
Tudor Brindus40506cd2018-06-18 20:18:17 -0700672 part_report, field)
673 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700674 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700675
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700676 # Check: old_{kernel,rootfs} size must fit in respective partition.
677 if self.old_fs_sizes[part] > part_sizes[part] > 0:
678 raise error.PayloadError(
679 'Old %s content (%d) exceed partition size (%d).' %
680 (part, self.old_fs_sizes[part], part_sizes[part]))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800681 else:
682 # Assert/mark full payload.
683 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700684 raise error.PayloadError(
685 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800686 self.payload_type = _TYPE_FULL
687
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700688 # Check: new_{kernel,rootfs}_info present; contains {size, hash}.
Tudor Brindus40506cd2018-06-18 20:18:17 -0700689 for part, (msg, part_report) in self.new_part_info.iteritems():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700690 field = 'new_%s_info' % part
Tudor Brindus40506cd2018-06-18 20:18:17 -0700691 self.new_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
692 part_report, field)
693 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700694 convert=common.FormatSha256)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800695
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700696 # Check: new_{kernel,rootfs} size must fit in respective partition.
697 if self.new_fs_sizes[part] > part_sizes[part] > 0:
698 raise error.PayloadError(
699 'New %s content (%d) exceed partition size (%d).' %
700 (part, self.new_fs_sizes[part], part_sizes[part]))
Gilad Arnold382df5c2013-05-03 12:49:28 -0700701
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800702 # Check: minor_version makes sense for the payload type. This check should
703 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700704 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800705
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800706 def _CheckLength(self, length, total_blocks, op_name, length_name):
707 """Checks whether a length matches the space designated in extents.
708
709 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700710 length: The total length of the data.
711 total_blocks: The total number of blocks in extents.
712 op_name: Operation name (for error reporting).
713 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800714
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800715 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700716 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800717 """
718 # Check: length is non-zero.
719 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700720 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800721
722 # Check that length matches number of blocks.
723 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
724 '%s: %s' % (op_name, length_name))
725
Gilad Arnold382df5c2013-05-03 12:49:28 -0700726 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800727 allow_pseudo=False, allow_signature=False):
728 """Checks a sequence of extents.
729
730 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700731 extents: The sequence of extents to check.
732 usable_size: The usable size of the partition to which the extents apply.
733 block_counters: Array of counters corresponding to the number of blocks.
734 name: The name of the extent block.
735 allow_pseudo: Whether or not pseudo block numbers are allowed.
736 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800737
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800738 Returns:
739 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800740
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800741 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700742 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800743 """
744 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800745 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700746 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800747 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
748 None, ex_name)
749 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
750 ex_name)
751 end_block = start_block + num_blocks
752
753 # Check: num_blocks > 0.
754 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700755 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800756
757 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700758 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700759 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700760 raise error.PayloadError(
761 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700762 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800763
764 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700765 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800766 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800767 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
768 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
769 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700770 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800771
772 total_num_blocks += num_blocks
773
774 return total_num_blocks
775
776 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0de7f782017-12-07 12:13:03 -0800777 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800778
779 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700780 op: The operation object from the manifest.
781 data_length: The length of the data blob associated with the operation.
782 total_dst_blocks: Total number of blocks in dst_extents.
783 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800784
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800785 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700786 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800787 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700788 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800789 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700790 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800791
Gilad Arnoldcb638912013-06-24 04:57:11 -0700792 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800793 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700794 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800795
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800796 if op.type == common.OpType.REPLACE:
797 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
798 self.block_size,
799 op_name + '.data_length', 'dst')
800 else:
Sen Jiang771f6482018-04-04 17:59:10 -0700801 # Check: data_length must be smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800802 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700803 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800804 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700805 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800806 (op_name, data_length, total_dst_blocks, self.block_size))
807
808 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
809 total_dst_blocks, op_name):
810 """Specific checks for MOVE operations.
811
812 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700813 op: The operation object from the manifest.
814 data_offset: The offset of a data blob for the operation.
815 total_src_blocks: Total number of blocks in src_extents.
816 total_dst_blocks: Total number of blocks in dst_extents.
817 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800818
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800819 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700820 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800821 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700822 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800823 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700824 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800825
Gilad Arnoldcb638912013-06-24 04:57:11 -0700826 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800827 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700828 raise error.PayloadError(
829 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800830 (op_name, total_src_blocks, total_dst_blocks))
831
Gilad Arnoldcb638912013-06-24 04:57:11 -0700832 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800833 i = 0
834 src_extent_iter = iter(op.src_extents)
835 dst_extent_iter = iter(op.dst_extents)
836 src_extent = dst_extent = None
837 src_idx = src_num = dst_idx = dst_num = 0
838 while i < total_src_blocks:
839 # Get the next source extent, if needed.
840 if not src_extent:
841 try:
842 src_extent = src_extent_iter.next()
843 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700844 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
845 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800846 src_idx = src_extent.start_block
847 src_num = src_extent.num_blocks
848
849 # Get the next dest extent, if needed.
850 if not dst_extent:
851 try:
852 dst_extent = dst_extent_iter.next()
853 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700854 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
855 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800856 dst_idx = dst_extent.start_block
857 dst_num = dst_extent.num_blocks
858
Allie Woodb065e132015-04-24 10:20:27 -0700859 # Check: start block is not 0. See crbug/480751; there are still versions
860 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
861 # so we need to fail payloads that try to MOVE to/from block 0.
862 if src_idx == 0 or dst_idx == 0:
863 raise error.PayloadError(
864 '%s: MOVE operation cannot have extent with start block 0' %
865 op_name)
866
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700867 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700868 raise error.PayloadError(
869 '%s: src/dst block number %d is the same (%d).' %
870 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800871
872 advance = min(src_num, dst_num)
873 i += advance
874
875 src_idx += advance
876 src_num -= advance
877 if src_num == 0:
878 src_extent = None
879
880 dst_idx += advance
881 dst_num -= advance
882 if dst_num == 0:
883 dst_extent = None
884
Gilad Arnold5502b562013-03-08 13:22:31 -0800885 # Make sure we've exhausted all src/dst extents.
886 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700887 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800888 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700889 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800890
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700891 def _CheckZeroOperation(self, op, op_name):
892 """Specific checks for ZERO operations.
893
894 Args:
895 op: The operation object from the manifest.
896 op_name: Operation name for error reporting.
897
898 Raises:
899 error.PayloadError if any check fails.
900 """
901 # Check: Does not contain src extents, data_length and data_offset.
902 if op.src_extents:
903 raise error.PayloadError('%s: contains src_extents.' % op_name)
904 if op.data_length:
905 raise error.PayloadError('%s: contains data_length.' % op_name)
906 if op.data_offset:
907 raise error.PayloadError('%s: contains data_offset.' % op_name)
908
Amin Hassaniefa62d92017-11-09 13:46:56 -0800909 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
910 """Specific checks for BSDIFF, SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
911 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800912
913 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800914 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700915 data_length: The length of the data blob associated with the operation.
916 total_dst_blocks: Total number of blocks in dst_extents.
917 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800918
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800919 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700920 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800921 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800922 # Check: data_{offset,length} present.
923 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700924 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800925
Sen Jiang771f6482018-04-04 17:59:10 -0700926 # Check: data_length is strictly smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800927 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700928 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800929 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700930 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800931 (op_name, data_length, total_dst_blocks, self.block_size,
932 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800933
Amin Hassaniefa62d92017-11-09 13:46:56 -0800934 # Check the existence of src_length and dst_length for legacy bsdiffs.
935 if (op.type == common.OpType.BSDIFF or
936 (op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3)):
937 if not op.HasField('src_length') or not op.HasField('dst_length'):
938 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
939 else:
940 if op.HasField('src_length') or op.HasField('dst_length'):
941 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
942
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800943 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
944 total_dst_blocks, op_name):
945 """Specific checks for SOURCE_COPY.
946
947 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800948 data_offset: The offset of a data blob for the operation.
949 total_src_blocks: Total number of blocks in src_extents.
950 total_dst_blocks: Total number of blocks in dst_extents.
951 op_name: Operation name for error reporting.
952
953 Raises:
954 error.PayloadError if any check fails.
955 """
956 # Check: No data_{offset,length}.
957 if data_offset is not None:
958 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
959
960 # Check: total_src_blocks == total_dst_blocks.
961 if total_src_blocks != total_dst_blocks:
962 raise error.PayloadError(
963 '%s: total src blocks (%d) != total dst blocks (%d).' %
964 (op_name, total_src_blocks, total_dst_blocks))
965
Sen Jiangd6122bb2015-12-11 10:27:04 -0800966 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800967 """Specific checks for SOURCE_* operations.
968
969 Args:
970 op: The operation object from the manifest.
971 total_src_blocks: Total number of blocks in src_extents.
972 op_name: Operation name for error reporting.
973
974 Raises:
975 error.PayloadError if any check fails.
976 """
977 # Check: total_src_blocks != 0.
978 if total_src_blocks == 0:
979 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
980
Sen Jiang92161a72016-06-28 16:09:38 -0700981 # Check: src_sha256_hash present in minor version >= 3.
982 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800983 raise error.PayloadError('%s: source hash missing.' % op_name)
984
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800985 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700986 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700987 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800988 """Checks a single update operation.
989
990 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700991 op: The operation object.
992 op_name: Operation name string for error reporting.
993 is_last: Whether this is the last operation in the sequence.
994 old_block_counters: Arrays of block read counters.
995 new_block_counters: Arrays of block write counters.
996 old_usable_size: The overall usable size for src data in bytes.
997 new_usable_size: The overall usable size for dst data in bytes.
998 prev_data_offset: Offset of last used data bytes.
999 allow_signature: Whether this may be a signature operation.
1000 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001001
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001002 Returns:
1003 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001004
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001005 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001006 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001007 """
1008 # Check extents.
1009 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -07001010 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001011 op_name + '.src_extents', allow_pseudo=True)
1012 allow_signature_in_extents = (allow_signature and is_last and
1013 op.type == common.OpType.REPLACE)
1014 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -07001015 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001016 op_name + '.dst_extents',
1017 allow_pseudo=(not self.check_dst_pseudo_extents),
1018 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001019
1020 # Check: data_offset present <==> data_length present.
1021 data_offset = self._CheckOptionalField(op, 'data_offset', None)
1022 data_length = self._CheckOptionalField(op, 'data_length', None)
1023 self._CheckPresentIff(data_offset, data_length, 'data_offset',
1024 'data_length', op_name)
1025
Gilad Arnoldcb638912013-06-24 04:57:11 -07001026 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001027 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001028 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001029
1030 # Check {src,dst}_length, if present.
1031 if op.HasField('src_length'):
1032 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
1033 if op.HasField('dst_length'):
1034 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
1035
1036 if op.HasField('data_sha256_hash'):
1037 blob_hash_counts['hashed'] += 1
1038
Gilad Arnoldcb638912013-06-24 04:57:11 -07001039 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001040 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001041 raise error.PayloadError(
1042 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001043 op_name)
1044
Gilad Arnoldcb638912013-06-24 04:57:11 -07001045 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001046 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
1047 data_length))
1048 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001049 raise error.PayloadError(
1050 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -07001051 (op_name, common.FormatSha256(op.data_sha256_hash),
1052 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001053 elif data_offset is not None:
1054 if allow_signature_in_extents:
1055 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001056 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001057 blob_hash_counts['unhashed'] += 1
1058 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001059 raise error.PayloadError('%s: unhashed operation not allowed.' %
1060 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001061
1062 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001063 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001064 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001065 raise error.PayloadError(
1066 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001067 (op_name, data_offset, prev_data_offset))
1068
1069 # Type-specific checks.
1070 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
1071 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani8ea19572018-12-05 12:06:21 -08001072 elif (op.type == common.OpType.REPLACE_XZ and
1073 (self.minor_version >= 3 or
1074 self.major_version >= common.BRILLO_MAJOR_PAYLOAD_VERSION)):
Amin Hassani0de7f782017-12-07 12:13:03 -08001075 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001076 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001077 self._CheckMoveOperation(op, data_offset, total_src_blocks,
1078 total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001079 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
1080 self._CheckZeroOperation(op, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001081 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001082 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001083 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001084 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
1085 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001086 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001087 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001088 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001089 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani77d7cbc2018-02-07 16:21:33 -08001090 elif op.type == common.OpType.BROTLI_BSDIFF and self.minor_version >= 4:
1091 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
1092 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
1093 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 5:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001094 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001095 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001096 else:
Allie Wood7cf9f132015-02-26 14:28:19 -08001097 raise error.PayloadError(
1098 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001099 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001100 return data_length if data_length is not None else 0
1101
Gilad Arnold382df5c2013-05-03 12:49:28 -07001102 def _SizeToNumBlocks(self, size):
1103 """Returns the number of blocks needed to contain a given byte size."""
1104 return (size + self.block_size - 1) / self.block_size
1105
1106 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001107 """Returns a freshly initialized array of block counters.
1108
Gilad Arnoldcb638912013-06-24 04:57:11 -07001109 Note that the generated array is not portable as is due to byte-ordering
1110 issues, hence it should not be serialized.
1111
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001112 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001113 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001114
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001115 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001116 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001117 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001118 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001119 return array.array('H',
1120 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001121
Gilad Arnold382df5c2013-05-03 12:49:28 -07001122 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001123 new_fs_size, old_usable_size, new_usable_size,
1124 prev_data_offset, allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001125 """Checks a sequence of update operations.
1126
1127 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001128 operations: The sequence of operations to check.
1129 report: The report object to add to.
1130 base_name: The name of the operation block.
1131 old_fs_size: The old filesystem size in bytes.
1132 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001133 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001134 new_usable_size: The overall usable size of the new partition in bytes.
1135 prev_data_offset: Offset of last used data bytes.
1136 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001137
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001138 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001139 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001140
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001141 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001142 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001143 """
1144 # The total size of data blobs used by operations scanned thus far.
1145 total_data_used = 0
1146 # Counts of specific operation types.
1147 op_counts = {
1148 common.OpType.REPLACE: 0,
1149 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001150 common.OpType.REPLACE_XZ: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001151 common.OpType.MOVE: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001152 common.OpType.ZERO: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001153 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001154 common.OpType.SOURCE_COPY: 0,
1155 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001156 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001157 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001158 }
1159 # Total blob sizes for each operation type.
1160 op_blob_totals = {
1161 common.OpType.REPLACE: 0,
1162 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001163 common.OpType.REPLACE_XZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001164 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001165 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001166 # SOURCE_COPY operations don't have blobs.
1167 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001168 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001169 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001170 }
1171 # Counts of hashed vs unhashed operations.
1172 blob_hash_counts = {
1173 'hashed': 0,
1174 'unhashed': 0,
1175 }
1176 if allow_signature:
1177 blob_hash_counts['signature'] = 0
1178
1179 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001180 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001181 if old_fs_size else None)
1182 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001183
1184 # Process and verify each operation.
1185 op_num = 0
1186 for op, op_name in common.OperationIter(operations, base_name):
1187 op_num += 1
1188
Gilad Arnoldcb638912013-06-24 04:57:11 -07001189 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001190 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001191 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001192 op_counts[op.type] += 1
1193
1194 is_last = op_num == len(operations)
1195 curr_data_used = self._CheckOperation(
1196 op, op_name, is_last, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001197 old_usable_size, new_usable_size,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001198 prev_data_offset + total_data_used, allow_signature,
1199 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001200 if curr_data_used:
1201 op_blob_totals[op.type] += curr_data_used
1202 total_data_used += curr_data_used
1203
1204 # Report totals and breakdown statistics.
1205 report.AddField('total operations', op_num)
1206 report.AddField(
1207 None,
1208 histogram.Histogram.FromCountDict(op_counts,
1209 key_names=common.OpType.NAMES),
1210 indent=1)
1211 report.AddField('total blobs', sum(blob_hash_counts.values()))
1212 report.AddField(None,
1213 histogram.Histogram.FromCountDict(blob_hash_counts),
1214 indent=1)
1215 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1216 report.AddField(
1217 None,
1218 histogram.Histogram.FromCountDict(op_blob_totals,
1219 formatter=_AddHumanReadableSize,
1220 key_names=common.OpType.NAMES),
1221 indent=1)
1222
1223 # Report read/write histograms.
1224 if old_block_counters:
1225 report.AddField('block read hist',
1226 histogram.Histogram.FromKeyList(old_block_counters),
1227 linebreak=True, indent=1)
1228
Gilad Arnold382df5c2013-05-03 12:49:28 -07001229 new_write_hist = histogram.Histogram.FromKeyList(
1230 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1231 report.AddField('block write hist', new_write_hist, linebreak=True,
1232 indent=1)
1233
Gilad Arnoldcb638912013-06-24 04:57:11 -07001234 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001235 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001236 raise error.PayloadError(
1237 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001238 base_name)
1239
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001240 return total_data_used
1241
1242 def _CheckSignatures(self, report, pubkey_file_name):
1243 """Checks a payload's signature block."""
1244 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1245 sigs = update_metadata_pb2.Signatures()
1246 sigs.ParseFromString(sigs_raw)
1247 report.AddSection('signatures')
1248
Gilad Arnoldcb638912013-06-24 04:57:11 -07001249 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001250 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001251 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001252
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001253 last_ops_section = (self.payload.manifest.kernel_install_operations or
1254 self.payload.manifest.install_operations)
Amin Hassani8ea19572018-12-05 12:06:21 -08001255
1256 # Only major version 1 has the fake signature OP at the end.
1257 if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
1258 fake_sig_op = last_ops_section[-1]
1259 # Check: signatures_{offset,size} must match the last (fake) operation.
1260 if not (fake_sig_op.type == common.OpType.REPLACE and
1261 self.sigs_offset == fake_sig_op.data_offset and
1262 self.sigs_size == fake_sig_op.data_length):
1263 raise error.PayloadError('Signatures_{offset,size} (%d+%d) does not'
1264 ' match last operation (%d+%d).' %
1265 (self.sigs_offset, self.sigs_size,
1266 fake_sig_op.data_offset,
1267 fake_sig_op.data_length))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001268
1269 # Compute the checksum of all data up to signature blob.
1270 # TODO(garnold) we're re-reading the whole data section into a string
1271 # just to compute the checksum; instead, we could do it incrementally as
1272 # we read the blobs one-by-one, under the assumption that we're reading
1273 # them in order (which currently holds). This should be reconsidered.
1274 payload_hasher = self.payload.manifest_hasher.copy()
1275 common.Read(self.payload.payload_file, self.sigs_offset,
1276 offset=self.payload.data_offset, hasher=payload_hasher)
1277
1278 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1279 sig_report = report.AddSubReport(sig_name)
1280
Gilad Arnoldcb638912013-06-24 04:57:11 -07001281 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001282 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1283 self._CheckMandatoryField(sig, 'data', None, sig_name)
1284 sig_report.AddField('data len', len(sig.data))
1285
Gilad Arnoldcb638912013-06-24 04:57:11 -07001286 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001287 if sig.version == 1:
1288 self._CheckSha256Signature(sig.data, pubkey_file_name,
1289 payload_hasher.digest(), sig_name)
1290 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001291 raise error.PayloadError('Unknown signature version (%d).' %
1292 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001293
Amin Hassania86b1082018-03-08 15:48:59 -08001294 def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0,
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001295 part_sizes=None, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001296 """Checker entry point, invoking all checks.
1297
1298 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001299 pubkey_file_name: Public key used for signature verification.
1300 metadata_sig_file: Metadata signature, if verification is desired.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001301 metadata_size: Metadata size, if verification is desired.
1302 part_sizes: Mapping of partition label to size in bytes (default: infer
1303 based on payload type and version or filesystem).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001304 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001305
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001306 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001307 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001308 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001309 if not pubkey_file_name:
1310 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1311
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001312 report = _PayloadReport()
1313
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001314 # Get payload file size.
1315 self.payload.payload_file.seek(0, 2)
1316 payload_file_size = self.payload.payload_file.tell()
1317 self.payload.ResetFile()
1318
1319 try:
Amin Hassania86b1082018-03-08 15:48:59 -08001320 # Check metadata_size (if provided).
Amin Hassani8ea19572018-12-05 12:06:21 -08001321 if metadata_size and self.payload.metadata_size != metadata_size:
Amin Hassania86b1082018-03-08 15:48:59 -08001322 raise error.PayloadError('Invalid payload metadata size in payload(%d) '
Amin Hassani8ea19572018-12-05 12:06:21 -08001323 'vs given(%d)' % (self.payload.metadata_size,
Amin Hassania86b1082018-03-08 15:48:59 -08001324 metadata_size))
1325
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001326 # Check metadata signature (if provided).
1327 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001328 metadata_sig = base64.b64decode(metadata_sig_file.read())
1329 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1330 self.payload.manifest_hasher.digest(),
1331 'metadata signature')
1332
Gilad Arnoldcb638912013-06-24 04:57:11 -07001333 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001334 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001335 # Check: Payload version is valid.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001336 if self.payload.header.version not in (1, 2):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001337 raise error.PayloadError('Unknown payload version (%d).' %
1338 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001339 report.AddField('version', self.payload.header.version)
1340 report.AddField('manifest len', self.payload.header.manifest_len)
1341
Gilad Arnoldcb638912013-06-24 04:57:11 -07001342 # Part 2: Check the manifest.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001343 self._CheckManifest(report, part_sizes)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001344 assert self.payload_type, 'payload type should be known by now'
1345
Tudor Brindus40506cd2018-06-18 20:18:17 -07001346 manifest = self.payload.manifest
Gilad Arnold06eea332015-07-13 18:06:33 -07001347
Tudor Brindus40506cd2018-06-18 20:18:17 -07001348 # Part 3: Examine partition operations.
1349 install_operations = []
Amin Hassani8ea19572018-12-05 12:06:21 -08001350 if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
Tudor Brindus40506cd2018-06-18 20:18:17 -07001351 # partitions field should not ever exist in major version 1 payloads
1352 self._CheckRepeatedElemNotPresent(manifest, 'partitions', 'manifest')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001353
Tudor Brindus40506cd2018-06-18 20:18:17 -07001354 install_operations.append((common.ROOTFS, manifest.install_operations))
1355 install_operations.append((common.KERNEL,
1356 manifest.kernel_install_operations))
1357
1358 else:
1359 self._CheckRepeatedElemNotPresent(manifest, 'install_operations',
1360 'manifest')
1361 self._CheckRepeatedElemNotPresent(manifest, 'kernel_install_operations',
1362 'manifest')
1363
1364 for update in manifest.partitions:
1365 install_operations.append((update.partition_name, update.operations))
1366
1367 total_blob_size = 0
1368 for part, operations in install_operations:
1369 report.AddSection('%s operations' % part)
1370
1371 new_fs_usable_size = self.new_fs_sizes[part]
1372 old_fs_usable_size = self.old_fs_sizes[part]
1373
1374 if part_sizes.get(part, None):
1375 new_fs_usable_size = old_fs_usable_size = part_sizes[part]
1376 # Infer the usable partition size when validating rootfs operations:
1377 # - If rootfs partition size was provided, use that.
1378 # - Otherwise, if this is an older delta (minor version < 2), stick with
1379 # a known constant size. This is necessary because older deltas may
1380 # exceed the filesystem size when moving data blocks around.
1381 # - Otherwise, use the encoded filesystem size.
1382 elif self.payload_type == _TYPE_DELTA and part == common.ROOTFS and \
1383 self.minor_version in (None, 1):
1384 new_fs_usable_size = old_fs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
1385
1386 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1387 # no explicit size provided *and* the partition size is not embedded in
1388 # the payload; see issue for more details.
1389 total_blob_size += self._CheckOperations(
1390 operations, report, '%s_install_operations' % part,
1391 self.old_fs_sizes[part], self.new_fs_sizes[part],
1392 old_fs_usable_size, new_fs_usable_size, total_blob_size,
Amin Hassani8ea19572018-12-05 12:06:21 -08001393 (self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION
1394 and part == common.KERNEL))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001395
Gilad Arnoldcb638912013-06-24 04:57:11 -07001396 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001397 used_payload_size = self.payload.data_offset + total_blob_size
Amin Hassani8ea19572018-12-05 12:06:21 -08001398 # Major versions 2 and higher have a signature at the end, so it should be
1399 # considered in the total size of the image.
Amin Hassani72b80ed2018-12-12 23:15:30 -08001400 if (self.major_version >= common.BRILLO_MAJOR_PAYLOAD_VERSION and
1401 self.sigs_size):
Amin Hassani8ea19572018-12-05 12:06:21 -08001402 used_payload_size += self.sigs_size
1403
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001404 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001405 raise error.PayloadError(
1406 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001407 (used_payload_size, payload_file_size))
1408
Tudor Brindus40506cd2018-06-18 20:18:17 -07001409 # Part 4: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001410 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001411 self._CheckSignatures(report, pubkey_file_name)
1412
Tudor Brindus40506cd2018-06-18 20:18:17 -07001413 # Part 5: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001414 report.AddSection('summary')
1415 report.AddField('update type', self.payload_type)
1416
1417 report.Finalize()
1418 finally:
1419 if report_out_file:
1420 report.Dump(report_out_file)