blob: 674d9f4e506b73297e3fa63cebcb41c8f059ab0f [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,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070069 2: (_TYPE_DELTA,),
Sen Jiang912c4df2015-12-10 12:17:13 -080070 3: (_TYPE_DELTA,),
Sen Jiang92161a72016-06-28 16:09:38 -070071 4: (_TYPE_DELTA,),
Amin Hassani77d7cbc2018-02-07 16:21:33 -080072 5: (_TYPE_DELTA,),
Amin Hassanief1af272019-01-10 14:04:27 -080073 6: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070074}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080075
76#
77# Helper functions.
78#
Gilad Arnoldcb638912013-06-24 04:57:11 -070079
Gilad Arnold553b0ec2013-01-26 01:00:39 -080080def _IsPowerOfTwo(val):
81 """Returns True iff val is a power of two."""
82 return val > 0 and (val & (val - 1)) == 0
83
84
85def _AddFormat(format_func, value):
86 """Adds a custom formatted representation to ordinary string representation.
87
88 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070089 format_func: A value formatter.
90 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080091
Gilad Arnold553b0ec2013-01-26 01:00:39 -080092 Returns:
93 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080094 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070095 ret = str(value)
96 formatted_str = format_func(value)
97 if formatted_str:
98 ret += ' (%s)' % formatted_str
99 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800100
101
102def _AddHumanReadableSize(size):
103 """Adds a human readable representation to a byte size value."""
104 return _AddFormat(format_utils.BytesToHumanReadable, size)
105
106
107#
108# Payload report generator.
109#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700110
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800111class _PayloadReport(object):
112 """A payload report generator.
113
114 A report is essentially a sequence of nodes, which represent data points. It
115 is initialized to have a "global", untitled section. A node may be a
116 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800117 """
118
Gilad Arnoldcb638912013-06-24 04:57:11 -0700119 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800120 class Node(object):
121 """A report node interface."""
122
123 @staticmethod
124 def _Indent(indent, line):
125 """Indents a line by a given indentation amount.
126
127 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700128 indent: The indentation amount.
129 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800130
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800131 Returns:
132 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800133 """
134 return '%*s%s' % (indent, '', line)
135
136 def GenerateLines(self, base_indent, sub_indent, curr_section):
137 """Generates the report lines for this node.
138
139 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700140 base_indent: Base indentation for each line.
141 sub_indent: Additional indentation for sub-nodes.
142 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800143
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800144 Returns:
145 A pair consisting of a list of properly indented report lines and a new
146 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800147 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700148 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800149
150 class FieldNode(Node):
151 """A field report node, representing a (name, value) pair."""
152
153 def __init__(self, name, value, linebreak, indent):
154 super(_PayloadReport.FieldNode, self).__init__()
155 self.name = name
156 self.value = value
157 self.linebreak = linebreak
158 self.indent = indent
159
160 def GenerateLines(self, base_indent, sub_indent, curr_section):
161 """Generates a properly formatted 'name : value' entry."""
162 report_output = ''
163 if self.name:
164 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
165 value_lines = str(self.value).splitlines()
166 if self.linebreak and self.name:
167 report_output += '\n' + '\n'.join(
168 ['%*s%s' % (self.indent, '', line) for line in value_lines])
169 else:
170 if self.name:
171 report_output += ' '
172 report_output += '%*s' % (self.indent, '')
173 cont_line_indent = len(report_output)
174 indented_value_lines = [value_lines[0]]
175 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
176 for line in value_lines[1:]])
177 report_output += '\n'.join(indented_value_lines)
178
179 report_lines = [self._Indent(base_indent, line + '\n')
180 for line in report_output.split('\n')]
181 return report_lines, curr_section
182
183 class SubReportNode(Node):
184 """A sub-report node, representing a nested report."""
185
186 def __init__(self, title, report):
187 super(_PayloadReport.SubReportNode, self).__init__()
188 self.title = title
189 self.report = report
190
191 def GenerateLines(self, base_indent, sub_indent, curr_section):
192 """Recurse with indentation."""
193 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
194 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
195 sub_indent))
196 return report_lines, curr_section
197
198 class SectionNode(Node):
199 """A section header node."""
200
201 def __init__(self, title=None):
202 super(_PayloadReport.SectionNode, self).__init__()
203 self.title = title
204 self.max_field_name_len = 0
205
206 def GenerateLines(self, base_indent, sub_indent, curr_section):
207 """Dump a title line, return self as the (new) current section."""
208 report_lines = []
209 if self.title:
210 report_lines.append(self._Indent(base_indent,
211 '=== %s ===\n' % self.title))
212 return report_lines, self
213
214 def __init__(self):
215 self.report = []
216 self.last_section = self.global_section = self.SectionNode()
217 self.is_finalized = False
218
219 def GenerateLines(self, base_indent, sub_indent):
220 """Generates the lines in the report, properly indented.
221
222 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700223 base_indent: The indentation used for root-level report lines.
224 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800225
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800226 Returns:
227 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800228 """
229 report_lines = []
230 curr_section = self.global_section
231 for node in self.report:
232 node_report_lines, curr_section = node.GenerateLines(
233 base_indent, sub_indent, curr_section)
234 report_lines.extend(node_report_lines)
235
236 return report_lines
237
238 def Dump(self, out_file, base_indent=0, sub_indent=2):
239 """Dumps the report to a file.
240
241 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700242 out_file: File object to output the content to.
243 base_indent: Base indentation for report lines.
244 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800245 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800246 report_lines = self.GenerateLines(base_indent, sub_indent)
247 if report_lines and not self.is_finalized:
248 report_lines.append('(incomplete report)\n')
249
250 for line in report_lines:
251 out_file.write(line)
252
253 def AddField(self, name, value, linebreak=False, indent=0):
254 """Adds a field/value pair to the payload report.
255
256 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700257 name: The field's name.
258 value: The field's value.
259 linebreak: Whether the value should be printed on a new line.
260 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800261 """
262 assert not self.is_finalized
263 if name and self.last_section.max_field_name_len < len(name):
264 self.last_section.max_field_name_len = len(name)
265 self.report.append(self.FieldNode(name, value, linebreak, indent))
266
267 def AddSubReport(self, title):
268 """Adds and returns a sub-report with a title."""
269 assert not self.is_finalized
270 sub_report = self.SubReportNode(title, type(self)())
271 self.report.append(sub_report)
272 return sub_report.report
273
274 def AddSection(self, title):
275 """Adds a new section title."""
276 assert not self.is_finalized
277 self.last_section = self.SectionNode(title)
278 self.report.append(self.last_section)
279
280 def Finalize(self):
281 """Seals the report, marking it as complete."""
282 self.is_finalized = True
283
284
285#
286# Payload verification.
287#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700288
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800289class PayloadChecker(object):
290 """Checking the integrity of an update payload.
291
292 This is a short-lived object whose purpose is to isolate the logic used for
293 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800294 """
295
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700296 def __init__(self, payload, assert_type=None, block_size=0,
297 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700298 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700299
300 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700301 payload: The payload object to check.
302 assert_type: Assert that payload is either 'full' or 'delta' (optional).
303 block_size: Expected filesystem / payload block size (optional).
304 allow_unhashed: Allow operations with unhashed data blobs.
305 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700306 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700307 if not payload.is_init:
308 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700309
310 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800311 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700312 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
313 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700314 raise error.PayloadError(
315 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700316 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700317 raise error.PayloadError('Invalid assert_type value (%r).' %
318 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700319 self.payload_type = assert_type
320 self.allow_unhashed = allow_unhashed
321
322 # Disable specific tests.
323 self.check_dst_pseudo_extents = (
324 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
325 self.check_move_same_src_dst_block = (
326 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
327 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800328
329 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800330 self.sigs_offset = 0
331 self.sigs_size = 0
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700332 self.old_part_info = {}
333 self.new_part_info = {}
334 self.new_fs_sizes = collections.defaultdict(int)
335 self.old_fs_sizes = collections.defaultdict(int)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700336 self.minor_version = None
Tudor Brindus40506cd2018-06-18 20:18:17 -0700337 self.major_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800338
339 @staticmethod
340 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
341 msg_name=None, linebreak=False, indent=0):
342 """Adds an element from a protobuf message to the payload report.
343
344 Checks to see whether a message contains a given element, and if so adds
345 the element value to the provided report. A missing mandatory element
346 causes an exception to be raised.
347
348 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700349 msg: The message containing the element.
350 name: The name of the element.
351 report: A report object to add the element name/value to.
352 is_mandatory: Whether or not this element must be present.
353 is_submsg: Whether this element is itself a message.
354 convert: A function for converting the element value for reporting.
355 msg_name: The name of the message object (for error reporting).
356 linebreak: Whether the value report should induce a line break.
357 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800358
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800359 Returns:
360 A pair consisting of the element value and the generated sub-report for
361 it (if the element is a sub-message, None otherwise). If the element is
362 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800363
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800364 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700365 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800366 """
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700367 element_result = collections.namedtuple('element_result', ['msg', 'report'])
368
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800369 if not msg.HasField(name):
370 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700371 raise error.PayloadError('%smissing mandatory %s %r.' %
372 (msg_name + ' ' if msg_name else '',
373 'sub-message' if is_submsg else 'field',
374 name))
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700375 return element_result(None, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800376
377 value = getattr(msg, name)
378 if is_submsg:
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700379 return element_result(value, report and report.AddSubReport(name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800380 else:
381 if report:
382 report.AddField(name, convert(value), linebreak=linebreak,
383 indent=indent)
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700384 return element_result(value, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800385
386 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700387 def _CheckRepeatedElemNotPresent(msg, field_name, msg_name):
388 """Checks that a repeated element is not specified in the message.
389
390 Args:
391 msg: The message containing the element.
392 field_name: The name of the element.
393 msg_name: The name of the message object (for error reporting).
394
395 Raises:
396 error.PayloadError if the repeated element is present or non-empty.
397 """
398 if getattr(msg, field_name, None):
399 raise error.PayloadError('%sfield %r not empty.' %
400 (msg_name + ' ' if msg_name else '', field_name))
401
402 @staticmethod
403 def _CheckElemNotPresent(msg, field_name, msg_name):
404 """Checks that an element is not specified in the message.
405
406 Args:
407 msg: The message containing the element.
408 field_name: The name of the element.
409 msg_name: The name of the message object (for error reporting).
410
411 Raises:
412 error.PayloadError if the repeated element is present.
413 """
414 if msg.HasField(field_name):
415 raise error.PayloadError('%sfield %r exists.' %
416 (msg_name + ' ' if msg_name else '', field_name))
417
418 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800419 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
420 linebreak=False, indent=0):
421 """Adds a mandatory field; returning first component from _CheckElem."""
422 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
423 convert=convert, msg_name=msg_name,
424 linebreak=linebreak, indent=indent)[0]
425
426 @staticmethod
427 def _CheckOptionalField(msg, field_name, report, convert=str,
428 linebreak=False, indent=0):
429 """Adds an optional field; returning first component from _CheckElem."""
430 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
431 convert=convert, linebreak=linebreak,
432 indent=indent)[0]
433
434 @staticmethod
435 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
436 """Adds a mandatory sub-message; wrapper for _CheckElem."""
437 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
438 msg_name)
439
440 @staticmethod
441 def _CheckOptionalSubMsg(msg, submsg_name, report):
442 """Adds an optional sub-message; wrapper for _CheckElem."""
443 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
444
445 @staticmethod
446 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
447 """Checks that val1 is None iff val2 is None.
448
449 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700450 val1: first value to be compared.
451 val2: second value to be compared.
452 name1: name of object holding the first value.
453 name2: name of object holding the second value.
454 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800455
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800456 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700457 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800458 """
459 if None in (val1, val2) and val1 is not val2:
460 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700461 raise error.PayloadError('%r present without %r%s.' %
462 (present, missing,
463 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800464
465 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700466 def _CheckPresentIffMany(vals, name, obj_name):
467 """Checks that a set of vals and names imply every other element.
468
469 Args:
470 vals: The set of values to be compared.
471 name: The name of the objects holding the corresponding value.
472 obj_name: Name of the object containing these values.
473
474 Raises:
475 error.PayloadError if assertion does not hold.
476 """
477 if any(vals) and not all(vals):
478 raise error.PayloadError('%r is not present in all values%s.' %
479 (name, ' in ' + obj_name if obj_name else ''))
480
481 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800482 def _Run(cmd, send_data=None):
483 """Runs a subprocess, returns its output.
484
485 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700486 cmd: Sequence of command-line argument for invoking the subprocess.
487 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800488
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800489 Returns:
490 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800491 """
492 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
493 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700494 try:
495 result = run_process.communicate(input=send_data)
496 finally:
497 exit_code = run_process.wait()
498
499 if exit_code:
500 raise RuntimeError('Subprocess %r failed with code %r.' %
501 (cmd, exit_code))
502
503 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800504
505 @staticmethod
506 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
507 """Verifies an actual hash against a signed one.
508
509 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700510 sig_data: The raw signature data.
511 pubkey_file_name: Public key used for verifying signature.
512 actual_hash: The actual hash digest.
513 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800514
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800515 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700516 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800517 """
518 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700519 raise error.PayloadError(
520 '%s: signature size (%d) not as expected (256).' %
521 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800522 signed_data, _ = PayloadChecker._Run(
523 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
524 send_data=sig_data)
525
Gilad Arnold5502b562013-03-08 13:22:31 -0800526 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700527 raise error.PayloadError('%s: unexpected signed data length (%d).' %
528 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800529
Gilad Arnold5502b562013-03-08 13:22:31 -0800530 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700531 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
532 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800533
Gilad Arnold5502b562013-03-08 13:22:31 -0800534 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800535 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700536 raise error.PayloadError(
537 '%s: signed hash (%s) different from actual (%s).' %
538 (sig_name, common.FormatSha256(signed_hash),
539 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800540
541 @staticmethod
542 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
543 block_name=None):
544 """Checks that a given length fits given block space.
545
546 This ensures that the number of blocks allocated is appropriate for the
547 length of the data residing in these blocks.
548
549 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700550 length: The actual length of the data.
551 num_blocks: The number of blocks allocated for it.
552 block_size: The size of each block in bytes.
553 length_name: Name of length (used for error reporting).
554 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800555
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800556 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700557 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800558 """
559 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700560 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700561 raise error.PayloadError(
562 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800563 (length_name, length, block_name or '', num_blocks, block_size))
564
565 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700566 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700567 raise error.PayloadError(
568 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800569 (length_name, length, block_name or '', num_blocks - 1, block_size))
570
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700571 def _CheckManifestMinorVersion(self, report):
572 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800573
574 Args:
575 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800576
577 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700578 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800579 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700580 self.minor_version = self._CheckOptionalField(self.payload.manifest,
581 'minor_version', report)
582 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
583 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800584 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700585 'Minor version %d not compatible with payload type %s.' %
586 (self.minor_version, self.payload_type))
587 elif self.minor_version is None:
588 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800589 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700590 raise error.PayloadError('Unsupported minor version: %d' %
591 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800592
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700593 def _CheckManifest(self, report, part_sizes=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800594 """Checks the payload manifest.
595
596 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700597 report: A report object to add to.
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700598 part_sizes: Map of partition label to partition size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800599
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800600 Returns:
601 A tuple consisting of the partition block size used during the update
602 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800603
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800604 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700605 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800606 """
Tudor Brindus40506cd2018-06-18 20:18:17 -0700607 self.major_version = self.payload.header.version
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700608
Xiaochu Liu6cf8e672019-03-14 16:15:42 -0700609 part_sizes = part_sizes or collections.defaultdict(int)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800610 manifest = self.payload.manifest
611 report.AddSection('manifest')
612
613 # Check: block_size must exist and match the expected value.
614 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
615 report, 'manifest')
616 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700617 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
618 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800619
620 # Check: signatures_offset <==> signatures_size.
621 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
622 report)
623 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
624 report)
625 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
626 'signatures_offset', 'signatures_size', 'manifest')
627
Amin Hassani8ea19572018-12-05 12:06:21 -0800628 if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
Tudor Brindusb220d662018-07-10 23:55:51 -0700629 for real_name, proto_name in common.CROS_PARTITIONS:
630 self.old_part_info[real_name] = self._CheckOptionalSubMsg(
631 manifest, 'old_%s_info' % proto_name, report)
632 self.new_part_info[real_name] = self._CheckMandatorySubMsg(
633 manifest, 'new_%s_info' % proto_name, report, 'manifest')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700634
Tudor Brindus40506cd2018-06-18 20:18:17 -0700635 # Check: old_kernel_info <==> old_rootfs_info.
636 self._CheckPresentIff(self.old_part_info[common.KERNEL].msg,
637 self.old_part_info[common.ROOTFS].msg,
638 'old_kernel_info', 'old_rootfs_info', 'manifest')
639 else:
640 for part in manifest.partitions:
641 name = part.partition_name
642 self.old_part_info[name] = self._CheckOptionalSubMsg(
643 part, 'old_partition_info', report)
644 self.new_part_info[name] = self._CheckMandatorySubMsg(
645 part, 'new_partition_info', report, 'manifest.partitions')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700646
Tudor Brindus40506cd2018-06-18 20:18:17 -0700647 # Check: Old-style partition infos should not be specified.
Tudor Brindusb220d662018-07-10 23:55:51 -0700648 for _, part in common.CROS_PARTITIONS:
Tudor Brindus40506cd2018-06-18 20:18:17 -0700649 self._CheckElemNotPresent(manifest, 'old_%s_info' % part, 'manifest')
650 self._CheckElemNotPresent(manifest, 'new_%s_info' % part, 'manifest')
651
652 # Check: If old_partition_info is specified anywhere, it must be
653 # specified everywhere.
654 old_part_msgs = [part.msg for part in self.old_part_info.values() if part]
655 self._CheckPresentIffMany(old_part_msgs, 'old_partition_info',
656 'manifest.partitions')
657
658 is_delta = any(part and part.msg for part in self.old_part_info.values())
659 if is_delta:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800660 # Assert/mark delta payload.
661 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700662 raise error.PayloadError(
663 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800664 self.payload_type = _TYPE_DELTA
665
Tudor Brindus40506cd2018-06-18 20:18:17 -0700666 for part, (msg, part_report) in self.old_part_info.iteritems():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700667 # Check: {size, hash} present in old_{kernel,rootfs}_info.
668 field = 'old_%s_info' % part
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700669 self.old_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
Tudor Brindus40506cd2018-06-18 20:18:17 -0700670 part_report, field)
671 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700672 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700673
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700674 # Check: old_{kernel,rootfs} size must fit in respective partition.
675 if self.old_fs_sizes[part] > part_sizes[part] > 0:
676 raise error.PayloadError(
677 'Old %s content (%d) exceed partition size (%d).' %
678 (part, self.old_fs_sizes[part], part_sizes[part]))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800679 else:
680 # Assert/mark full payload.
681 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700682 raise error.PayloadError(
683 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800684 self.payload_type = _TYPE_FULL
685
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700686 # Check: new_{kernel,rootfs}_info present; contains {size, hash}.
Tudor Brindus40506cd2018-06-18 20:18:17 -0700687 for part, (msg, part_report) in self.new_part_info.iteritems():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700688 field = 'new_%s_info' % part
Tudor Brindus40506cd2018-06-18 20:18:17 -0700689 self.new_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
690 part_report, field)
691 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700692 convert=common.FormatSha256)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800693
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700694 # Check: new_{kernel,rootfs} size must fit in respective partition.
695 if self.new_fs_sizes[part] > part_sizes[part] > 0:
696 raise error.PayloadError(
697 'New %s content (%d) exceed partition size (%d).' %
698 (part, self.new_fs_sizes[part], part_sizes[part]))
Gilad Arnold382df5c2013-05-03 12:49:28 -0700699
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800700 # Check: minor_version makes sense for the payload type. This check should
701 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700702 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800703
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800704 def _CheckLength(self, length, total_blocks, op_name, length_name):
705 """Checks whether a length matches the space designated in extents.
706
707 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700708 length: The total length of the data.
709 total_blocks: The total number of blocks in extents.
710 op_name: Operation name (for error reporting).
711 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800712
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800713 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700714 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800715 """
716 # Check: length is non-zero.
717 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700718 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800719
720 # Check that length matches number of blocks.
721 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
722 '%s: %s' % (op_name, length_name))
723
Gilad Arnold382df5c2013-05-03 12:49:28 -0700724 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800725 allow_pseudo=False, allow_signature=False):
726 """Checks a sequence of extents.
727
728 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700729 extents: The sequence of extents to check.
730 usable_size: The usable size of the partition to which the extents apply.
731 block_counters: Array of counters corresponding to the number of blocks.
732 name: The name of the extent block.
733 allow_pseudo: Whether or not pseudo block numbers are allowed.
734 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800735
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800736 Returns:
737 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800738
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800739 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700740 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800741 """
742 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800743 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700744 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800745 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
746 None, ex_name)
747 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
748 ex_name)
749 end_block = start_block + num_blocks
750
751 # Check: num_blocks > 0.
752 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700753 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800754
755 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700756 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700757 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700758 raise error.PayloadError(
759 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700760 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800761
762 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700763 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800764 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800765 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
766 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
767 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700768 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800769
770 total_num_blocks += num_blocks
771
772 return total_num_blocks
773
774 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0de7f782017-12-07 12:13:03 -0800775 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800776
777 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700778 op: The operation object from the manifest.
779 data_length: The length of the data blob associated with the operation.
780 total_dst_blocks: Total number of blocks in dst_extents.
781 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800782
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800783 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700784 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800785 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700786 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800787 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700788 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800789
Gilad Arnoldcb638912013-06-24 04:57:11 -0700790 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800791 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700792 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800793
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800794 if op.type == common.OpType.REPLACE:
795 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
796 self.block_size,
797 op_name + '.data_length', 'dst')
798 else:
Sen Jiang771f6482018-04-04 17:59:10 -0700799 # Check: data_length must be smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800800 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700801 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800802 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700803 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800804 (op_name, data_length, total_dst_blocks, self.block_size))
805
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700806 def _CheckZeroOperation(self, op, op_name):
807 """Specific checks for ZERO operations.
808
809 Args:
810 op: The operation object from the manifest.
811 op_name: Operation name for error reporting.
812
813 Raises:
814 error.PayloadError if any check fails.
815 """
816 # Check: Does not contain src extents, data_length and data_offset.
817 if op.src_extents:
818 raise error.PayloadError('%s: contains src_extents.' % op_name)
819 if op.data_length:
820 raise error.PayloadError('%s: contains data_length.' % op_name)
821 if op.data_offset:
822 raise error.PayloadError('%s: contains data_offset.' % op_name)
823
Amin Hassaniefa62d92017-11-09 13:46:56 -0800824 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0f59a9a2019-09-27 10:24:31 -0700825 """Specific checks for SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
Amin Hassaniefa62d92017-11-09 13:46:56 -0800826 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800827
828 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800829 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700830 data_length: The length of the data blob associated with the operation.
831 total_dst_blocks: Total number of blocks in dst_extents.
832 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800833
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800834 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700835 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800836 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800837 # Check: data_{offset,length} present.
838 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700839 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800840
Sen Jiang771f6482018-04-04 17:59:10 -0700841 # Check: data_length is strictly smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800842 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700843 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800844 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700845 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800846 (op_name, data_length, total_dst_blocks, self.block_size,
847 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800848
Amin Hassaniefa62d92017-11-09 13:46:56 -0800849 # Check the existence of src_length and dst_length for legacy bsdiffs.
Amin Hassani0f59a9a2019-09-27 10:24:31 -0700850 if op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800851 if not op.HasField('src_length') or not op.HasField('dst_length'):
852 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
853 else:
854 if op.HasField('src_length') or op.HasField('dst_length'):
855 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
856
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800857 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
858 total_dst_blocks, op_name):
859 """Specific checks for SOURCE_COPY.
860
861 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800862 data_offset: The offset of a data blob for the operation.
863 total_src_blocks: Total number of blocks in src_extents.
864 total_dst_blocks: Total number of blocks in dst_extents.
865 op_name: Operation name for error reporting.
866
867 Raises:
868 error.PayloadError if any check fails.
869 """
870 # Check: No data_{offset,length}.
871 if data_offset is not None:
872 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
873
874 # Check: total_src_blocks == total_dst_blocks.
875 if total_src_blocks != total_dst_blocks:
876 raise error.PayloadError(
877 '%s: total src blocks (%d) != total dst blocks (%d).' %
878 (op_name, total_src_blocks, total_dst_blocks))
879
Sen Jiangd6122bb2015-12-11 10:27:04 -0800880 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800881 """Specific checks for SOURCE_* operations.
882
883 Args:
884 op: The operation object from the manifest.
885 total_src_blocks: Total number of blocks in src_extents.
886 op_name: Operation name for error reporting.
887
888 Raises:
889 error.PayloadError if any check fails.
890 """
891 # Check: total_src_blocks != 0.
892 if total_src_blocks == 0:
893 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
894
Sen Jiang92161a72016-06-28 16:09:38 -0700895 # Check: src_sha256_hash present in minor version >= 3.
896 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800897 raise error.PayloadError('%s: source hash missing.' % op_name)
898
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800899 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700900 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700901 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800902 """Checks a single update operation.
903
904 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700905 op: The operation object.
906 op_name: Operation name string for error reporting.
907 is_last: Whether this is the last operation in the sequence.
908 old_block_counters: Arrays of block read counters.
909 new_block_counters: Arrays of block write counters.
910 old_usable_size: The overall usable size for src data in bytes.
911 new_usable_size: The overall usable size for dst data in bytes.
912 prev_data_offset: Offset of last used data bytes.
913 allow_signature: Whether this may be a signature operation.
914 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800915
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800916 Returns:
917 The amount of data blob associated with the operation.
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 has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800921 """
922 # Check extents.
923 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700924 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800925 op_name + '.src_extents', allow_pseudo=True)
926 allow_signature_in_extents = (allow_signature and is_last and
927 op.type == common.OpType.REPLACE)
928 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700929 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700930 op_name + '.dst_extents',
931 allow_pseudo=(not self.check_dst_pseudo_extents),
932 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800933
934 # Check: data_offset present <==> data_length present.
935 data_offset = self._CheckOptionalField(op, 'data_offset', None)
936 data_length = self._CheckOptionalField(op, 'data_length', None)
937 self._CheckPresentIff(data_offset, data_length, 'data_offset',
938 'data_length', op_name)
939
Gilad Arnoldcb638912013-06-24 04:57:11 -0700940 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800941 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700942 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800943
944 # Check {src,dst}_length, if present.
945 if op.HasField('src_length'):
946 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
947 if op.HasField('dst_length'):
948 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
949
950 if op.HasField('data_sha256_hash'):
951 blob_hash_counts['hashed'] += 1
952
Gilad Arnoldcb638912013-06-24 04:57:11 -0700953 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800954 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700955 raise error.PayloadError(
956 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800957 op_name)
958
Gilad Arnoldcb638912013-06-24 04:57:11 -0700959 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800960 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
961 data_length))
962 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700963 raise error.PayloadError(
964 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700965 (op_name, common.FormatSha256(op.data_sha256_hash),
966 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800967 elif data_offset is not None:
968 if allow_signature_in_extents:
969 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700970 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800971 blob_hash_counts['unhashed'] += 1
972 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700973 raise error.PayloadError('%s: unhashed operation not allowed.' %
974 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800975
976 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700977 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800978 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700979 raise error.PayloadError(
980 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800981 (op_name, data_offset, prev_data_offset))
982
983 # Type-specific checks.
984 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
985 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani8ea19572018-12-05 12:06:21 -0800986 elif (op.type == common.OpType.REPLACE_XZ and
987 (self.minor_version >= 3 or
988 self.major_version >= common.BRILLO_MAJOR_PAYLOAD_VERSION)):
Amin Hassani0de7f782017-12-07 12:13:03 -0800989 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700990 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
991 self._CheckZeroOperation(op, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700992 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800993 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
994 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800995 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700996 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800997 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700998 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani77d7cbc2018-02-07 16:21:33 -0800999 elif op.type == common.OpType.BROTLI_BSDIFF and self.minor_version >= 4:
1000 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
1001 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
1002 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 5:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001003 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001004 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001005 else:
Allie Wood7cf9f132015-02-26 14:28:19 -08001006 raise error.PayloadError(
1007 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001008 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001009 return data_length if data_length is not None else 0
1010
Gilad Arnold382df5c2013-05-03 12:49:28 -07001011 def _SizeToNumBlocks(self, size):
1012 """Returns the number of blocks needed to contain a given byte size."""
1013 return (size + self.block_size - 1) / self.block_size
1014
1015 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001016 """Returns a freshly initialized array of block counters.
1017
Gilad Arnoldcb638912013-06-24 04:57:11 -07001018 Note that the generated array is not portable as is due to byte-ordering
1019 issues, hence it should not be serialized.
1020
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001021 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001022 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001023
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001024 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001025 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001026 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001027 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001028 return array.array('H',
1029 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001030
Gilad Arnold382df5c2013-05-03 12:49:28 -07001031 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001032 new_fs_size, old_usable_size, new_usable_size,
1033 prev_data_offset, allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001034 """Checks a sequence of update operations.
1035
1036 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001037 operations: The sequence of operations to check.
1038 report: The report object to add to.
1039 base_name: The name of the operation block.
1040 old_fs_size: The old filesystem size in bytes.
1041 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001042 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001043 new_usable_size: The overall usable size of the new partition in bytes.
1044 prev_data_offset: Offset of last used data bytes.
1045 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001046
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001047 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001048 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001049
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001050 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001051 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001052 """
1053 # The total size of data blobs used by operations scanned thus far.
1054 total_data_used = 0
1055 # Counts of specific operation types.
1056 op_counts = {
1057 common.OpType.REPLACE: 0,
1058 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001059 common.OpType.REPLACE_XZ: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001060 common.OpType.ZERO: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001061 common.OpType.SOURCE_COPY: 0,
1062 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001063 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001064 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001065 }
1066 # Total blob sizes for each operation type.
1067 op_blob_totals = {
1068 common.OpType.REPLACE: 0,
1069 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001070 common.OpType.REPLACE_XZ: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001071 # SOURCE_COPY operations don't have blobs.
1072 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001073 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001074 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001075 }
1076 # Counts of hashed vs unhashed operations.
1077 blob_hash_counts = {
1078 'hashed': 0,
1079 'unhashed': 0,
1080 }
1081 if allow_signature:
1082 blob_hash_counts['signature'] = 0
1083
1084 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001085 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001086 if old_fs_size else None)
1087 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001088
1089 # Process and verify each operation.
1090 op_num = 0
1091 for op, op_name in common.OperationIter(operations, base_name):
1092 op_num += 1
1093
Gilad Arnoldcb638912013-06-24 04:57:11 -07001094 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001095 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001096 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001097 op_counts[op.type] += 1
1098
1099 is_last = op_num == len(operations)
1100 curr_data_used = self._CheckOperation(
1101 op, op_name, is_last, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001102 old_usable_size, new_usable_size,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001103 prev_data_offset + total_data_used, allow_signature,
1104 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001105 if curr_data_used:
1106 op_blob_totals[op.type] += curr_data_used
1107 total_data_used += curr_data_used
1108
1109 # Report totals and breakdown statistics.
1110 report.AddField('total operations', op_num)
1111 report.AddField(
1112 None,
1113 histogram.Histogram.FromCountDict(op_counts,
1114 key_names=common.OpType.NAMES),
1115 indent=1)
1116 report.AddField('total blobs', sum(blob_hash_counts.values()))
1117 report.AddField(None,
1118 histogram.Histogram.FromCountDict(blob_hash_counts),
1119 indent=1)
1120 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1121 report.AddField(
1122 None,
1123 histogram.Histogram.FromCountDict(op_blob_totals,
1124 formatter=_AddHumanReadableSize,
1125 key_names=common.OpType.NAMES),
1126 indent=1)
1127
1128 # Report read/write histograms.
1129 if old_block_counters:
1130 report.AddField('block read hist',
1131 histogram.Histogram.FromKeyList(old_block_counters),
1132 linebreak=True, indent=1)
1133
Gilad Arnold382df5c2013-05-03 12:49:28 -07001134 new_write_hist = histogram.Histogram.FromKeyList(
1135 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1136 report.AddField('block write hist', new_write_hist, linebreak=True,
1137 indent=1)
1138
Gilad Arnoldcb638912013-06-24 04:57:11 -07001139 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001140 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001141 raise error.PayloadError(
1142 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001143 base_name)
1144
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001145 return total_data_used
1146
1147 def _CheckSignatures(self, report, pubkey_file_name):
1148 """Checks a payload's signature block."""
1149 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1150 sigs = update_metadata_pb2.Signatures()
1151 sigs.ParseFromString(sigs_raw)
1152 report.AddSection('signatures')
1153
Gilad Arnoldcb638912013-06-24 04:57:11 -07001154 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001155 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001156 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001157
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001158 last_ops_section = (self.payload.manifest.kernel_install_operations or
1159 self.payload.manifest.install_operations)
Amin Hassani8ea19572018-12-05 12:06:21 -08001160
1161 # Only major version 1 has the fake signature OP at the end.
1162 if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
1163 fake_sig_op = last_ops_section[-1]
1164 # Check: signatures_{offset,size} must match the last (fake) operation.
1165 if not (fake_sig_op.type == common.OpType.REPLACE and
1166 self.sigs_offset == fake_sig_op.data_offset and
1167 self.sigs_size == fake_sig_op.data_length):
1168 raise error.PayloadError('Signatures_{offset,size} (%d+%d) does not'
1169 ' match last operation (%d+%d).' %
1170 (self.sigs_offset, self.sigs_size,
1171 fake_sig_op.data_offset,
1172 fake_sig_op.data_length))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001173
1174 # Compute the checksum of all data up to signature blob.
1175 # TODO(garnold) we're re-reading the whole data section into a string
1176 # just to compute the checksum; instead, we could do it incrementally as
1177 # we read the blobs one-by-one, under the assumption that we're reading
1178 # them in order (which currently holds). This should be reconsidered.
1179 payload_hasher = self.payload.manifest_hasher.copy()
1180 common.Read(self.payload.payload_file, self.sigs_offset,
1181 offset=self.payload.data_offset, hasher=payload_hasher)
1182
1183 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1184 sig_report = report.AddSubReport(sig_name)
1185
Gilad Arnoldcb638912013-06-24 04:57:11 -07001186 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001187 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1188 self._CheckMandatoryField(sig, 'data', None, sig_name)
1189 sig_report.AddField('data len', len(sig.data))
1190
Gilad Arnoldcb638912013-06-24 04:57:11 -07001191 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001192 if sig.version == 1:
1193 self._CheckSha256Signature(sig.data, pubkey_file_name,
1194 payload_hasher.digest(), sig_name)
1195 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001196 raise error.PayloadError('Unknown signature version (%d).' %
1197 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001198
Amin Hassania86b1082018-03-08 15:48:59 -08001199 def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0,
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001200 part_sizes=None, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001201 """Checker entry point, invoking all checks.
1202
1203 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001204 pubkey_file_name: Public key used for signature verification.
1205 metadata_sig_file: Metadata signature, if verification is desired.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001206 metadata_size: Metadata size, if verification is desired.
1207 part_sizes: Mapping of partition label to size in bytes (default: infer
1208 based on payload type and version or filesystem).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001209 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001210
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001211 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001212 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001213 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001214 if not pubkey_file_name:
1215 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1216
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001217 report = _PayloadReport()
1218
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001219 # Get payload file size.
1220 self.payload.payload_file.seek(0, 2)
1221 payload_file_size = self.payload.payload_file.tell()
1222 self.payload.ResetFile()
1223
1224 try:
Amin Hassania86b1082018-03-08 15:48:59 -08001225 # Check metadata_size (if provided).
Amin Hassani8ea19572018-12-05 12:06:21 -08001226 if metadata_size and self.payload.metadata_size != metadata_size:
Amin Hassania86b1082018-03-08 15:48:59 -08001227 raise error.PayloadError('Invalid payload metadata size in payload(%d) '
Amin Hassani8ea19572018-12-05 12:06:21 -08001228 'vs given(%d)' % (self.payload.metadata_size,
Amin Hassania86b1082018-03-08 15:48:59 -08001229 metadata_size))
1230
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001231 # Check metadata signature (if provided).
1232 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001233 metadata_sig = base64.b64decode(metadata_sig_file.read())
1234 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1235 self.payload.manifest_hasher.digest(),
1236 'metadata signature')
1237
Gilad Arnoldcb638912013-06-24 04:57:11 -07001238 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001239 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001240 # Check: Payload version is valid.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001241 if self.payload.header.version not in (1, 2):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001242 raise error.PayloadError('Unknown payload version (%d).' %
1243 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001244 report.AddField('version', self.payload.header.version)
1245 report.AddField('manifest len', self.payload.header.manifest_len)
1246
Gilad Arnoldcb638912013-06-24 04:57:11 -07001247 # Part 2: Check the manifest.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001248 self._CheckManifest(report, part_sizes)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001249 assert self.payload_type, 'payload type should be known by now'
1250
Tudor Brindus40506cd2018-06-18 20:18:17 -07001251 manifest = self.payload.manifest
Gilad Arnold06eea332015-07-13 18:06:33 -07001252
Tudor Brindus40506cd2018-06-18 20:18:17 -07001253 # Part 3: Examine partition operations.
1254 install_operations = []
Amin Hassani8ea19572018-12-05 12:06:21 -08001255 if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
Tudor Brindus40506cd2018-06-18 20:18:17 -07001256 # partitions field should not ever exist in major version 1 payloads
1257 self._CheckRepeatedElemNotPresent(manifest, 'partitions', 'manifest')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001258
Tudor Brindus40506cd2018-06-18 20:18:17 -07001259 install_operations.append((common.ROOTFS, manifest.install_operations))
1260 install_operations.append((common.KERNEL,
1261 manifest.kernel_install_operations))
1262
1263 else:
1264 self._CheckRepeatedElemNotPresent(manifest, 'install_operations',
1265 'manifest')
1266 self._CheckRepeatedElemNotPresent(manifest, 'kernel_install_operations',
1267 'manifest')
1268
1269 for update in manifest.partitions:
1270 install_operations.append((update.partition_name, update.operations))
1271
1272 total_blob_size = 0
1273 for part, operations in install_operations:
1274 report.AddSection('%s operations' % part)
1275
1276 new_fs_usable_size = self.new_fs_sizes[part]
1277 old_fs_usable_size = self.old_fs_sizes[part]
1278
Xiaochu Liu6cf8e672019-03-14 16:15:42 -07001279 if part_sizes is not None and part_sizes.get(part, None):
Tudor Brindus40506cd2018-06-18 20:18:17 -07001280 new_fs_usable_size = old_fs_usable_size = part_sizes[part]
Tudor Brindus40506cd2018-06-18 20:18:17 -07001281
Amin Hassani0f59a9a2019-09-27 10:24:31 -07001282 # TODO(chromium:243559) only default to the filesystem size if no
1283 # explicit size provided *and* the partition size is not embedded in the
1284 # payload; see issue for more details.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001285 total_blob_size += self._CheckOperations(
1286 operations, report, '%s_install_operations' % part,
1287 self.old_fs_sizes[part], self.new_fs_sizes[part],
1288 old_fs_usable_size, new_fs_usable_size, total_blob_size,
Amin Hassani8ea19572018-12-05 12:06:21 -08001289 (self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION
1290 and part == common.KERNEL))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001291
Gilad Arnoldcb638912013-06-24 04:57:11 -07001292 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001293 used_payload_size = self.payload.data_offset + total_blob_size
Amin Hassani8ea19572018-12-05 12:06:21 -08001294 # Major versions 2 and higher have a signature at the end, so it should be
1295 # considered in the total size of the image.
Amin Hassani72b80ed2018-12-12 23:15:30 -08001296 if (self.major_version >= common.BRILLO_MAJOR_PAYLOAD_VERSION and
1297 self.sigs_size):
Amin Hassani8ea19572018-12-05 12:06:21 -08001298 used_payload_size += self.sigs_size
1299
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001300 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001301 raise error.PayloadError(
1302 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001303 (used_payload_size, payload_file_size))
1304
Tudor Brindus40506cd2018-06-18 20:18:17 -07001305 # Part 4: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001306 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001307 self._CheckSignatures(report, pubkey_file_name)
1308
Tudor Brindus40506cd2018-06-18 20:18:17 -07001309 # Part 5: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001310 report.AddSection('summary')
1311 report.AddField('update type', self.payload_type)
1312
1313 report.Finalize()
1314 finally:
1315 if report_out_file:
1316 report.Dump(report_out_file)