blob: 99a5c629b4a35b1a7ae4c24bb1c37779aaf49eda [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
Andrew Lassalle165843c2019-11-05 13:30:34 -080027from __future__ import absolute_import
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080028from __future__ import print_function
29
Gilad Arnold553b0ec2013-01-26 01:00:39 -080030import array
31import base64
Tudor Brindus8d05a7e2018-06-14 11:18:18 -070032import collections
Gilad Arnold553b0ec2013-01-26 01:00:39 -080033import hashlib
Gilad Arnoldcb638912013-06-24 04:57:11 -070034import itertools
Gilad Arnold9b90c932013-05-22 17:12:56 -070035import os
Gilad Arnold553b0ec2013-01-26 01:00:39 -080036import subprocess
37
Andrew Lassalle165843c2019-11-05 13:30:34 -080038from six.moves import range
39
Amin Hassanib05a65a2017-12-18 15:15:32 -080040from update_payload import common
41from update_payload import error
42from update_payload import format_utils
43from update_payload import histogram
44from update_payload import update_metadata_pb2
Gilad Arnold553b0ec2013-01-26 01:00:39 -080045
Gilad Arnold553b0ec2013-01-26 01:00:39 -080046#
Gilad Arnold9b90c932013-05-22 17:12:56 -070047# Constants.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080048#
Gilad Arnoldcb638912013-06-24 04:57:11 -070049
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070050_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
51_CHECK_PAYLOAD_SIG = 'payload-sig'
52CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070053 _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,),
Amin Hassani582d8fe2020-09-28 22:15:18 -070074 7: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070075}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080076
Andrew Lassalle165843c2019-11-05 13:30:34 -080077
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.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700325 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 Hassani55c75412019-10-07 11:20:39 -0700628 for part in manifest.partitions:
629 name = part.partition_name
630 self.old_part_info[name] = self._CheckOptionalSubMsg(
631 part, 'old_partition_info', report)
632 self.new_part_info[name] = self._CheckMandatorySubMsg(
633 part, 'new_partition_info', report, 'manifest.partitions')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700634
Amin Hassani55c75412019-10-07 11:20:39 -0700635 # Check: Old-style partition infos should not be specified.
636 for _, part in common.CROS_PARTITIONS:
637 self._CheckElemNotPresent(manifest, 'old_%s_info' % part, 'manifest')
638 self._CheckElemNotPresent(manifest, 'new_%s_info' % part, 'manifest')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700639
Amin Hassani55c75412019-10-07 11:20:39 -0700640 # Check: If old_partition_info is specified anywhere, it must be
641 # specified everywhere.
642 old_part_msgs = [part.msg for part in self.old_part_info.values() if part]
643 self._CheckPresentIffMany(old_part_msgs, 'old_partition_info',
644 'manifest.partitions')
Tudor Brindus40506cd2018-06-18 20:18:17 -0700645
646 is_delta = any(part and part.msg for part in self.old_part_info.values())
647 if is_delta:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800648 # Assert/mark delta payload.
649 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700650 raise error.PayloadError(
651 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800652 self.payload_type = _TYPE_DELTA
653
Andrew Lassalle165843c2019-11-05 13:30:34 -0800654 for part, (msg, part_report) in self.old_part_info.items():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700655 # Check: {size, hash} present in old_{kernel,rootfs}_info.
656 field = 'old_%s_info' % part
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700657 self.old_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
Tudor Brindus40506cd2018-06-18 20:18:17 -0700658 part_report, field)
659 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700660 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700661
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700662 # Check: old_{kernel,rootfs} size must fit in respective partition.
663 if self.old_fs_sizes[part] > part_sizes[part] > 0:
664 raise error.PayloadError(
665 'Old %s content (%d) exceed partition size (%d).' %
666 (part, self.old_fs_sizes[part], part_sizes[part]))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800667 else:
668 # Assert/mark full payload.
669 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700670 raise error.PayloadError(
671 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800672 self.payload_type = _TYPE_FULL
673
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700674 # Check: new_{kernel,rootfs}_info present; contains {size, hash}.
Andrew Lassalle165843c2019-11-05 13:30:34 -0800675 for part, (msg, part_report) in self.new_part_info.items():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700676 field = 'new_%s_info' % part
Tudor Brindus40506cd2018-06-18 20:18:17 -0700677 self.new_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
678 part_report, field)
679 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700680 convert=common.FormatSha256)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800681
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700682 # Check: new_{kernel,rootfs} size must fit in respective partition.
683 if self.new_fs_sizes[part] > part_sizes[part] > 0:
684 raise error.PayloadError(
685 'New %s content (%d) exceed partition size (%d).' %
686 (part, self.new_fs_sizes[part], part_sizes[part]))
Gilad Arnold382df5c2013-05-03 12:49:28 -0700687
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800688 # Check: minor_version makes sense for the payload type. This check should
689 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700690 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800691
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800692 def _CheckLength(self, length, total_blocks, op_name, length_name):
693 """Checks whether a length matches the space designated in extents.
694
695 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700696 length: The total length of the data.
697 total_blocks: The total number of blocks in extents.
698 op_name: Operation name (for error reporting).
699 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800700
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800701 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700702 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800703 """
704 # Check: length is non-zero.
705 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700706 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800707
708 # Check that length matches number of blocks.
709 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
710 '%s: %s' % (op_name, length_name))
711
Amin Hassani55c75412019-10-07 11:20:39 -0700712 def _CheckExtents(self, extents, usable_size, block_counters, name):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800713 """Checks a sequence of extents.
714
715 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700716 extents: The sequence of extents to check.
717 usable_size: The usable size of the partition to which the extents apply.
718 block_counters: Array of counters corresponding to the number of blocks.
719 name: The name of the extent block.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800720
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800721 Returns:
722 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800723
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800724 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700725 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800726 """
727 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800728 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700729 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800730 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
731 None, ex_name)
732 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
733 ex_name)
734 end_block = start_block + num_blocks
735
736 # Check: num_blocks > 0.
737 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700738 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800739
Amin Hassani55c75412019-10-07 11:20:39 -0700740 # Check: Make sure we're within the partition limit.
741 if usable_size and end_block * self.block_size > usable_size:
742 raise error.PayloadError(
743 '%s: extent (%s) exceeds usable partition size (%d).' %
744 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800745
Amin Hassani55c75412019-10-07 11:20:39 -0700746 # Record block usage.
Andrew Lassalle165843c2019-11-05 13:30:34 -0800747 for i in range(start_block, end_block):
Amin Hassani55c75412019-10-07 11:20:39 -0700748 block_counters[i] += 1
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800749
750 total_num_blocks += num_blocks
751
752 return total_num_blocks
753
754 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0de7f782017-12-07 12:13:03 -0800755 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800756
757 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700758 op: The operation object from the manifest.
759 data_length: The length of the data blob associated with the operation.
760 total_dst_blocks: Total number of blocks in dst_extents.
761 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800762
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800763 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700764 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800765 """
Andrew Lassalle165843c2019-11-05 13:30:34 -0800766 # Check: total_dst_blocks is not a floating point.
767 if isinstance(total_dst_blocks, float):
768 raise error.PayloadError('%s: contains invalid data type of '
769 'total_dst_blocks.' % op_name)
770
Gilad Arnoldcb638912013-06-24 04:57:11 -0700771 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800772 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700773 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800774
Gilad Arnoldcb638912013-06-24 04:57:11 -0700775 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800776 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700777 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800778
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800779 if op.type == common.OpType.REPLACE:
780 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
781 self.block_size,
782 op_name + '.data_length', 'dst')
783 else:
Sen Jiang771f6482018-04-04 17:59:10 -0700784 # Check: data_length must be smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800785 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700786 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800787 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700788 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800789 (op_name, data_length, total_dst_blocks, self.block_size))
790
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700791 def _CheckZeroOperation(self, op, op_name):
792 """Specific checks for ZERO operations.
793
794 Args:
795 op: The operation object from the manifest.
796 op_name: Operation name for error reporting.
797
798 Raises:
799 error.PayloadError if any check fails.
800 """
801 # Check: Does not contain src extents, data_length and data_offset.
802 if op.src_extents:
803 raise error.PayloadError('%s: contains src_extents.' % op_name)
804 if op.data_length:
805 raise error.PayloadError('%s: contains data_length.' % op_name)
806 if op.data_offset:
807 raise error.PayloadError('%s: contains data_offset.' % op_name)
808
Amin Hassaniefa62d92017-11-09 13:46:56 -0800809 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0f59a9a2019-09-27 10:24:31 -0700810 """Specific checks for SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
Amin Hassaniefa62d92017-11-09 13:46:56 -0800811 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800812
813 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800814 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700815 data_length: The length of the data blob associated with the operation.
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 Arnold5502b562013-03-08 13:22:31 -0800822 # Check: data_{offset,length} present.
823 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700824 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800825
Sen Jiang771f6482018-04-04 17:59:10 -0700826 # Check: data_length is strictly smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800827 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700828 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800829 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700830 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800831 (op_name, data_length, total_dst_blocks, self.block_size,
832 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800833
Amin Hassaniefa62d92017-11-09 13:46:56 -0800834 # Check the existence of src_length and dst_length for legacy bsdiffs.
Amin Hassani0f59a9a2019-09-27 10:24:31 -0700835 if op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800836 if not op.HasField('src_length') or not op.HasField('dst_length'):
837 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
838 else:
839 if op.HasField('src_length') or op.HasField('dst_length'):
840 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
841
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800842 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
843 total_dst_blocks, op_name):
844 """Specific checks for SOURCE_COPY.
845
846 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800847 data_offset: The offset of a data blob for the operation.
848 total_src_blocks: Total number of blocks in src_extents.
849 total_dst_blocks: Total number of blocks in dst_extents.
850 op_name: Operation name for error reporting.
851
852 Raises:
853 error.PayloadError if any check fails.
854 """
855 # Check: No data_{offset,length}.
856 if data_offset is not None:
857 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
858
859 # Check: total_src_blocks == total_dst_blocks.
860 if total_src_blocks != total_dst_blocks:
861 raise error.PayloadError(
862 '%s: total src blocks (%d) != total dst blocks (%d).' %
863 (op_name, total_src_blocks, total_dst_blocks))
864
Sen Jiangd6122bb2015-12-11 10:27:04 -0800865 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800866 """Specific checks for SOURCE_* operations.
867
868 Args:
869 op: The operation object from the manifest.
870 total_src_blocks: Total number of blocks in src_extents.
871 op_name: Operation name for error reporting.
872
873 Raises:
874 error.PayloadError if any check fails.
875 """
876 # Check: total_src_blocks != 0.
877 if total_src_blocks == 0:
878 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
879
Sen Jiang92161a72016-06-28 16:09:38 -0700880 # Check: src_sha256_hash present in minor version >= 3.
881 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800882 raise error.PayloadError('%s: source hash missing.' % op_name)
883
Amin Hassani55c75412019-10-07 11:20:39 -0700884 def _CheckOperation(self, op, op_name, old_block_counters, new_block_counters,
885 old_usable_size, new_usable_size, prev_data_offset,
886 blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800887 """Checks a single update operation.
888
889 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700890 op: The operation object.
891 op_name: Operation name string for error reporting.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700892 old_block_counters: Arrays of block read counters.
893 new_block_counters: Arrays of block write counters.
894 old_usable_size: The overall usable size for src data in bytes.
895 new_usable_size: The overall usable size for dst data in bytes.
896 prev_data_offset: Offset of last used data bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700897 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800898
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800899 Returns:
900 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800901
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800902 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700903 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800904 """
905 # Check extents.
906 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700907 op.src_extents, old_usable_size, old_block_counters,
Amin Hassani55c75412019-10-07 11:20:39 -0700908 op_name + '.src_extents')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800909 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700910 op.dst_extents, new_usable_size, new_block_counters,
Amin Hassani55c75412019-10-07 11:20:39 -0700911 op_name + '.dst_extents')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800912
913 # Check: data_offset present <==> data_length present.
914 data_offset = self._CheckOptionalField(op, 'data_offset', None)
915 data_length = self._CheckOptionalField(op, 'data_length', None)
916 self._CheckPresentIff(data_offset, data_length, 'data_offset',
917 'data_length', op_name)
918
Gilad Arnoldcb638912013-06-24 04:57:11 -0700919 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800920 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700921 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800922
923 # Check {src,dst}_length, if present.
924 if op.HasField('src_length'):
925 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
926 if op.HasField('dst_length'):
927 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
928
929 if op.HasField('data_sha256_hash'):
930 blob_hash_counts['hashed'] += 1
931
Gilad Arnoldcb638912013-06-24 04:57:11 -0700932 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800933 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700934 raise error.PayloadError(
935 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800936 op_name)
937
Gilad Arnoldcb638912013-06-24 04:57:11 -0700938 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800939 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
940 data_length))
941 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700942 raise error.PayloadError(
943 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700944 (op_name, common.FormatSha256(op.data_sha256_hash),
945 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800946 elif data_offset is not None:
Amin Hassani55c75412019-10-07 11:20:39 -0700947 if self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800948 blob_hash_counts['unhashed'] += 1
949 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700950 raise error.PayloadError('%s: unhashed operation not allowed.' %
951 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800952
953 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700954 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800955 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700956 raise error.PayloadError(
957 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800958 (op_name, data_offset, prev_data_offset))
959
960 # Type-specific checks.
Amin Hassani55c75412019-10-07 11:20:39 -0700961 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ,
962 common.OpType.REPLACE_XZ):
Amin Hassani0de7f782017-12-07 12:13:03 -0800963 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700964 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
965 self._CheckZeroOperation(op, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700966 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800967 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
968 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800969 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700970 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800971 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700972 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani77d7cbc2018-02-07 16:21:33 -0800973 elif op.type == common.OpType.BROTLI_BSDIFF and self.minor_version >= 4:
974 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
975 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
976 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 5:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800977 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800978 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800979 else:
Allie Wood7cf9f132015-02-26 14:28:19 -0800980 raise error.PayloadError(
981 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700982 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800983 return data_length if data_length is not None else 0
984
Gilad Arnold382df5c2013-05-03 12:49:28 -0700985 def _SizeToNumBlocks(self, size):
986 """Returns the number of blocks needed to contain a given byte size."""
Andrew Lassalle165843c2019-11-05 13:30:34 -0800987 return (size + self.block_size - 1) // self.block_size
Gilad Arnold382df5c2013-05-03 12:49:28 -0700988
989 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800990 """Returns a freshly initialized array of block counters.
991
Gilad Arnoldcb638912013-06-24 04:57:11 -0700992 Note that the generated array is not portable as is due to byte-ordering
993 issues, hence it should not be serialized.
994
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800995 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700996 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800997
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800998 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -0700999 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001000 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001001 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001002 return array.array('H',
1003 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001004
Gilad Arnold382df5c2013-05-03 12:49:28 -07001005 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001006 new_fs_size, old_usable_size, new_usable_size,
Amin Hassani55c75412019-10-07 11:20:39 -07001007 prev_data_offset):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001008 """Checks a sequence of update operations.
1009
1010 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001011 operations: The sequence of operations to check.
1012 report: The report object to add to.
1013 base_name: The name of the operation block.
1014 old_fs_size: The old filesystem size in bytes.
1015 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001016 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001017 new_usable_size: The overall usable size of the new partition in bytes.
1018 prev_data_offset: Offset of last used data bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001019
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001020 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001021 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001022
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001023 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001024 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001025 """
1026 # The total size of data blobs used by operations scanned thus far.
1027 total_data_used = 0
1028 # Counts of specific operation types.
1029 op_counts = {
1030 common.OpType.REPLACE: 0,
1031 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001032 common.OpType.REPLACE_XZ: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001033 common.OpType.ZERO: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001034 common.OpType.SOURCE_COPY: 0,
1035 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001036 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001037 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001038 }
1039 # Total blob sizes for each operation type.
1040 op_blob_totals = {
1041 common.OpType.REPLACE: 0,
1042 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001043 common.OpType.REPLACE_XZ: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001044 # SOURCE_COPY operations don't have blobs.
1045 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001046 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001047 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001048 }
1049 # Counts of hashed vs unhashed operations.
1050 blob_hash_counts = {
1051 'hashed': 0,
1052 'unhashed': 0,
1053 }
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001054
1055 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001056 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001057 if old_fs_size else None)
1058 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001059
1060 # Process and verify each operation.
1061 op_num = 0
1062 for op, op_name in common.OperationIter(operations, base_name):
1063 op_num += 1
1064
Gilad Arnoldcb638912013-06-24 04:57:11 -07001065 # Check: Type is valid.
Andrew Lassalle165843c2019-11-05 13:30:34 -08001066 if op.type not in op_counts:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001067 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001068 op_counts[op.type] += 1
1069
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001070 curr_data_used = self._CheckOperation(
Amin Hassani55c75412019-10-07 11:20:39 -07001071 op, op_name, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001072 old_usable_size, new_usable_size,
Amin Hassani55c75412019-10-07 11:20:39 -07001073 prev_data_offset + total_data_used, blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001074 if curr_data_used:
1075 op_blob_totals[op.type] += curr_data_used
1076 total_data_used += curr_data_used
1077
1078 # Report totals and breakdown statistics.
1079 report.AddField('total operations', op_num)
1080 report.AddField(
1081 None,
1082 histogram.Histogram.FromCountDict(op_counts,
1083 key_names=common.OpType.NAMES),
1084 indent=1)
1085 report.AddField('total blobs', sum(blob_hash_counts.values()))
1086 report.AddField(None,
1087 histogram.Histogram.FromCountDict(blob_hash_counts),
1088 indent=1)
1089 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1090 report.AddField(
1091 None,
1092 histogram.Histogram.FromCountDict(op_blob_totals,
1093 formatter=_AddHumanReadableSize,
1094 key_names=common.OpType.NAMES),
1095 indent=1)
1096
1097 # Report read/write histograms.
1098 if old_block_counters:
1099 report.AddField('block read hist',
1100 histogram.Histogram.FromKeyList(old_block_counters),
1101 linebreak=True, indent=1)
1102
Gilad Arnold382df5c2013-05-03 12:49:28 -07001103 new_write_hist = histogram.Histogram.FromKeyList(
1104 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1105 report.AddField('block write hist', new_write_hist, linebreak=True,
1106 indent=1)
1107
Gilad Arnoldcb638912013-06-24 04:57:11 -07001108 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001109 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001110 raise error.PayloadError(
1111 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001112 base_name)
1113
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001114 return total_data_used
1115
1116 def _CheckSignatures(self, report, pubkey_file_name):
1117 """Checks a payload's signature block."""
1118 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1119 sigs = update_metadata_pb2.Signatures()
1120 sigs.ParseFromString(sigs_raw)
1121 report.AddSection('signatures')
1122
Gilad Arnoldcb638912013-06-24 04:57:11 -07001123 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001124 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001125 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001126
Amin Hassani55c75412019-10-07 11:20:39 -07001127 # Check that we don't have the signature operation blob at the end (used to
1128 # be for major version 1).
1129 last_partition = self.payload.manifest.partitions[-1]
1130 if last_partition.operations:
1131 last_op = last_partition.operations[-1]
Amin Hassani8ea19572018-12-05 12:06:21 -08001132 # Check: signatures_{offset,size} must match the last (fake) operation.
Amin Hassani55c75412019-10-07 11:20:39 -07001133 if (last_op.type == common.OpType.REPLACE and
1134 last_op.data_offset == self.sigs_offset and
1135 last_op.data_length == self.sigs_size):
1136 raise error.PayloadError('It seems like the last operation is the '
1137 'signature blob. This is an invalid payload.')
1138
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001139 # Compute the checksum of all data up to signature blob.
1140 # TODO(garnold) we're re-reading the whole data section into a string
1141 # just to compute the checksum; instead, we could do it incrementally as
1142 # we read the blobs one-by-one, under the assumption that we're reading
1143 # them in order (which currently holds). This should be reconsidered.
1144 payload_hasher = self.payload.manifest_hasher.copy()
1145 common.Read(self.payload.payload_file, self.sigs_offset,
1146 offset=self.payload.data_offset, hasher=payload_hasher)
1147
1148 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1149 sig_report = report.AddSubReport(sig_name)
1150
Gilad Arnoldcb638912013-06-24 04:57:11 -07001151 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001152 self._CheckMandatoryField(sig, 'data', None, sig_name)
1153 sig_report.AddField('data len', len(sig.data))
1154
Gilad Arnoldcb638912013-06-24 04:57:11 -07001155 # Check: Signatures pertains to actual payload hash.
Amin Hassanic0840c42020-09-27 18:17:33 -07001156 if sig.data:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001157 self._CheckSha256Signature(sig.data, pubkey_file_name,
1158 payload_hasher.digest(), sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001159
Amin Hassania86b1082018-03-08 15:48:59 -08001160 def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0,
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001161 part_sizes=None, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001162 """Checker entry point, invoking all checks.
1163
1164 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001165 pubkey_file_name: Public key used for signature verification.
1166 metadata_sig_file: Metadata signature, if verification is desired.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001167 metadata_size: Metadata size, if verification is desired.
1168 part_sizes: Mapping of partition label to size in bytes (default: infer
1169 based on payload type and version or filesystem).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001170 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001171
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001172 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001173 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001174 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001175 if not pubkey_file_name:
1176 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1177
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001178 report = _PayloadReport()
1179
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001180 # Get payload file size.
1181 self.payload.payload_file.seek(0, 2)
1182 payload_file_size = self.payload.payload_file.tell()
1183 self.payload.ResetFile()
1184
1185 try:
Amin Hassania86b1082018-03-08 15:48:59 -08001186 # Check metadata_size (if provided).
Amin Hassani8ea19572018-12-05 12:06:21 -08001187 if metadata_size and self.payload.metadata_size != metadata_size:
Amin Hassania86b1082018-03-08 15:48:59 -08001188 raise error.PayloadError('Invalid payload metadata size in payload(%d) '
Amin Hassani8ea19572018-12-05 12:06:21 -08001189 'vs given(%d)' % (self.payload.metadata_size,
Amin Hassania86b1082018-03-08 15:48:59 -08001190 metadata_size))
1191
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001192 # Check metadata signature (if provided).
1193 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001194 metadata_sig = base64.b64decode(metadata_sig_file.read())
1195 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1196 self.payload.manifest_hasher.digest(),
1197 'metadata signature')
1198
Gilad Arnoldcb638912013-06-24 04:57:11 -07001199 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001200 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001201 # Check: Payload version is valid.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001202 if self.payload.header.version not in (1, 2):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001203 raise error.PayloadError('Unknown payload version (%d).' %
1204 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001205 report.AddField('version', self.payload.header.version)
1206 report.AddField('manifest len', self.payload.header.manifest_len)
1207
Gilad Arnoldcb638912013-06-24 04:57:11 -07001208 # Part 2: Check the manifest.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001209 self._CheckManifest(report, part_sizes)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001210 assert self.payload_type, 'payload type should be known by now'
1211
Amin Hassani55c75412019-10-07 11:20:39 -07001212 # Make sure deprecated values are not present in the payload.
1213 for field in ('install_operations', 'kernel_install_operations'):
1214 self._CheckRepeatedElemNotPresent(self.payload.manifest, field,
Tudor Brindus40506cd2018-06-18 20:18:17 -07001215 'manifest')
Amin Hassani55c75412019-10-07 11:20:39 -07001216 for field in ('old_kernel_info', 'old_rootfs_info',
1217 'new_kernel_info', 'new_rootfs_info'):
1218 self._CheckElemNotPresent(self.payload.manifest, field, 'manifest')
Tudor Brindus40506cd2018-06-18 20:18:17 -07001219
1220 total_blob_size = 0
Amin Hassani55c75412019-10-07 11:20:39 -07001221 for part, operations in ((p.partition_name, p.operations)
1222 for p in self.payload.manifest.partitions):
Tudor Brindus40506cd2018-06-18 20:18:17 -07001223 report.AddSection('%s operations' % part)
1224
1225 new_fs_usable_size = self.new_fs_sizes[part]
1226 old_fs_usable_size = self.old_fs_sizes[part]
1227
Xiaochu Liu6cf8e672019-03-14 16:15:42 -07001228 if part_sizes is not None and part_sizes.get(part, None):
Tudor Brindus40506cd2018-06-18 20:18:17 -07001229 new_fs_usable_size = old_fs_usable_size = part_sizes[part]
Tudor Brindus40506cd2018-06-18 20:18:17 -07001230
Amin Hassani0f59a9a2019-09-27 10:24:31 -07001231 # TODO(chromium:243559) only default to the filesystem size if no
1232 # explicit size provided *and* the partition size is not embedded in the
1233 # payload; see issue for more details.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001234 total_blob_size += self._CheckOperations(
1235 operations, report, '%s_install_operations' % part,
1236 self.old_fs_sizes[part], self.new_fs_sizes[part],
Amin Hassani55c75412019-10-07 11:20:39 -07001237 old_fs_usable_size, new_fs_usable_size, total_blob_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001238
Gilad Arnoldcb638912013-06-24 04:57:11 -07001239 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001240 used_payload_size = self.payload.data_offset + total_blob_size
Amin Hassani8ea19572018-12-05 12:06:21 -08001241 # Major versions 2 and higher have a signature at the end, so it should be
1242 # considered in the total size of the image.
Amin Hassani55c75412019-10-07 11:20:39 -07001243 if self.sigs_size:
Amin Hassani8ea19572018-12-05 12:06:21 -08001244 used_payload_size += self.sigs_size
1245
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001246 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001247 raise error.PayloadError(
1248 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001249 (used_payload_size, payload_file_size))
1250
Tudor Brindus40506cd2018-06-18 20:18:17 -07001251 # Part 4: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001252 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001253 self._CheckSignatures(report, pubkey_file_name)
1254
Tudor Brindus40506cd2018-06-18 20:18:17 -07001255 # Part 5: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001256 report.AddSection('summary')
1257 report.AddField('update type', self.payload_type)
1258
1259 report.Finalize()
1260 finally:
1261 if report_out_file:
1262 report.Dump(report_out_file)