blob: 44b6811565bb976dda51f0cb55646b8134673b2c [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
Amin Hassani42c2f982020-10-29 12:10:05 -070038# pylint: disable=redefined-builtin
Andrew Lassalle165843c2019-11-05 13:30:34 -080039from six.moves import range
40
Amin Hassanib05a65a2017-12-18 15:15:32 -080041from update_payload import common
42from update_payload import error
43from update_payload import format_utils
44from update_payload import histogram
Kelvin Zhang73202a92022-06-02 10:19:54 -070045import update_metadata_pb2
Gilad Arnold553b0ec2013-01-26 01:00:39 -080046
Gilad Arnold553b0ec2013-01-26 01:00:39 -080047#
Gilad Arnold9b90c932013-05-22 17:12:56 -070048# Constants.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080049#
Gilad Arnoldcb638912013-06-24 04:57:11 -070050
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070051_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
52_CHECK_PAYLOAD_SIG = 'payload-sig'
53CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070054 _CHECK_MOVE_SAME_SRC_DST_BLOCK,
55 _CHECK_PAYLOAD_SIG,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070056)
57
Gilad Arnold553b0ec2013-01-26 01:00:39 -080058_TYPE_FULL = 'full'
59_TYPE_DELTA = 'delta'
60
61_DEFAULT_BLOCK_SIZE = 4096
62
Gilad Arnold9b90c932013-05-22 17:12:56 -070063_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem'
64_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
65 _DEFAULT_PUBKEY_BASE_NAME)
66
Gilad Arnold0d575cd2015-07-13 17:29:21 -070067# Supported minor version map to payload types allowed to be using them.
68_SUPPORTED_MINOR_VERSIONS = {
69 0: (_TYPE_FULL,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070070 2: (_TYPE_DELTA,),
Sen Jiang912c4df2015-12-10 12:17:13 -080071 3: (_TYPE_DELTA,),
Sen Jiang92161a72016-06-28 16:09:38 -070072 4: (_TYPE_DELTA,),
Amin Hassani77d7cbc2018-02-07 16:21:33 -080073 5: (_TYPE_DELTA,),
Amin Hassanief1af272019-01-10 14:04:27 -080074 6: (_TYPE_DELTA,),
Amin Hassani582d8fe2020-09-28 22:15:18 -070075 7: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070076}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080077
Andrew Lassalle165843c2019-11-05 13:30:34 -080078
Gilad Arnold553b0ec2013-01-26 01:00:39 -080079#
80# Helper functions.
81#
Gilad Arnoldcb638912013-06-24 04:57:11 -070082
Gilad Arnold553b0ec2013-01-26 01:00:39 -080083def _IsPowerOfTwo(val):
84 """Returns True iff val is a power of two."""
85 return val > 0 and (val & (val - 1)) == 0
86
87
88def _AddFormat(format_func, value):
89 """Adds a custom formatted representation to ordinary string representation.
90
91 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070092 format_func: A value formatter.
93 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080094
Gilad Arnold553b0ec2013-01-26 01:00:39 -080095 Returns:
96 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080097 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070098 ret = str(value)
99 formatted_str = format_func(value)
100 if formatted_str:
101 ret += ' (%s)' % formatted_str
102 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800103
104
105def _AddHumanReadableSize(size):
106 """Adds a human readable representation to a byte size value."""
107 return _AddFormat(format_utils.BytesToHumanReadable, size)
108
109
110#
111# Payload report generator.
112#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700113
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800114class _PayloadReport(object):
115 """A payload report generator.
116
117 A report is essentially a sequence of nodes, which represent data points. It
118 is initialized to have a "global", untitled section. A node may be a
119 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800120 """
121
Gilad Arnoldcb638912013-06-24 04:57:11 -0700122 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800123 class Node(object):
124 """A report node interface."""
125
126 @staticmethod
127 def _Indent(indent, line):
128 """Indents a line by a given indentation amount.
129
130 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700131 indent: The indentation amount.
132 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800133
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800134 Returns:
135 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800136 """
137 return '%*s%s' % (indent, '', line)
138
139 def GenerateLines(self, base_indent, sub_indent, curr_section):
140 """Generates the report lines for this node.
141
142 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700143 base_indent: Base indentation for each line.
144 sub_indent: Additional indentation for sub-nodes.
145 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800146
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800147 Returns:
148 A pair consisting of a list of properly indented report lines and a new
149 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800150 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700151 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800152
153 class FieldNode(Node):
154 """A field report node, representing a (name, value) pair."""
155
156 def __init__(self, name, value, linebreak, indent):
157 super(_PayloadReport.FieldNode, self).__init__()
158 self.name = name
159 self.value = value
160 self.linebreak = linebreak
161 self.indent = indent
162
163 def GenerateLines(self, base_indent, sub_indent, curr_section):
164 """Generates a properly formatted 'name : value' entry."""
165 report_output = ''
166 if self.name:
167 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
168 value_lines = str(self.value).splitlines()
169 if self.linebreak and self.name:
170 report_output += '\n' + '\n'.join(
171 ['%*s%s' % (self.indent, '', line) for line in value_lines])
172 else:
173 if self.name:
174 report_output += ' '
175 report_output += '%*s' % (self.indent, '')
176 cont_line_indent = len(report_output)
177 indented_value_lines = [value_lines[0]]
178 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
179 for line in value_lines[1:]])
180 report_output += '\n'.join(indented_value_lines)
181
182 report_lines = [self._Indent(base_indent, line + '\n')
183 for line in report_output.split('\n')]
184 return report_lines, curr_section
185
186 class SubReportNode(Node):
187 """A sub-report node, representing a nested report."""
188
189 def __init__(self, title, report):
190 super(_PayloadReport.SubReportNode, self).__init__()
191 self.title = title
192 self.report = report
193
194 def GenerateLines(self, base_indent, sub_indent, curr_section):
195 """Recurse with indentation."""
196 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
197 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
198 sub_indent))
199 return report_lines, curr_section
200
201 class SectionNode(Node):
202 """A section header node."""
203
204 def __init__(self, title=None):
205 super(_PayloadReport.SectionNode, self).__init__()
206 self.title = title
207 self.max_field_name_len = 0
208
209 def GenerateLines(self, base_indent, sub_indent, curr_section):
210 """Dump a title line, return self as the (new) current section."""
211 report_lines = []
212 if self.title:
213 report_lines.append(self._Indent(base_indent,
214 '=== %s ===\n' % self.title))
215 return report_lines, self
216
217 def __init__(self):
218 self.report = []
219 self.last_section = self.global_section = self.SectionNode()
220 self.is_finalized = False
221
222 def GenerateLines(self, base_indent, sub_indent):
223 """Generates the lines in the report, properly indented.
224
225 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700226 base_indent: The indentation used for root-level report lines.
227 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800228
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800229 Returns:
230 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800231 """
232 report_lines = []
233 curr_section = self.global_section
234 for node in self.report:
235 node_report_lines, curr_section = node.GenerateLines(
236 base_indent, sub_indent, curr_section)
237 report_lines.extend(node_report_lines)
238
239 return report_lines
240
241 def Dump(self, out_file, base_indent=0, sub_indent=2):
242 """Dumps the report to a file.
243
244 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700245 out_file: File object to output the content to.
246 base_indent: Base indentation for report lines.
247 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800248 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800249 report_lines = self.GenerateLines(base_indent, sub_indent)
250 if report_lines and not self.is_finalized:
251 report_lines.append('(incomplete report)\n')
252
253 for line in report_lines:
254 out_file.write(line)
255
256 def AddField(self, name, value, linebreak=False, indent=0):
257 """Adds a field/value pair to the payload report.
258
259 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700260 name: The field's name.
261 value: The field's value.
262 linebreak: Whether the value should be printed on a new line.
263 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800264 """
265 assert not self.is_finalized
266 if name and self.last_section.max_field_name_len < len(name):
267 self.last_section.max_field_name_len = len(name)
268 self.report.append(self.FieldNode(name, value, linebreak, indent))
269
270 def AddSubReport(self, title):
271 """Adds and returns a sub-report with a title."""
272 assert not self.is_finalized
273 sub_report = self.SubReportNode(title, type(self)())
274 self.report.append(sub_report)
275 return sub_report.report
276
277 def AddSection(self, title):
278 """Adds a new section title."""
279 assert not self.is_finalized
280 self.last_section = self.SectionNode(title)
281 self.report.append(self.last_section)
282
283 def Finalize(self):
284 """Seals the report, marking it as complete."""
285 self.is_finalized = True
286
287
288#
289# Payload verification.
290#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700291
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800292class PayloadChecker(object):
293 """Checking the integrity of an update payload.
294
295 This is a short-lived object whose purpose is to isolate the logic used for
296 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800297 """
298
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700299 def __init__(self, payload, assert_type=None, block_size=0,
300 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700301 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700302
303 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700304 payload: The payload object to check.
305 assert_type: Assert that payload is either 'full' or 'delta' (optional).
306 block_size: Expected filesystem / payload block size (optional).
307 allow_unhashed: Allow operations with unhashed data blobs.
308 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700309 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700310 if not payload.is_init:
311 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700312
313 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800314 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700315 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
316 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700317 raise error.PayloadError(
318 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700319 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700320 raise error.PayloadError('Invalid assert_type value (%r).' %
321 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700322 self.payload_type = assert_type
323 self.allow_unhashed = allow_unhashed
324
325 # Disable specific tests.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700326 self.check_move_same_src_dst_block = (
327 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
328 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800329
330 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800331 self.sigs_offset = 0
332 self.sigs_size = 0
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700333 self.old_part_info = {}
334 self.new_part_info = {}
335 self.new_fs_sizes = collections.defaultdict(int)
336 self.old_fs_sizes = collections.defaultdict(int)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700337 self.minor_version = None
Tudor Brindus40506cd2018-06-18 20:18:17 -0700338 self.major_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800339
340 @staticmethod
341 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
342 msg_name=None, linebreak=False, indent=0):
343 """Adds an element from a protobuf message to the payload report.
344
345 Checks to see whether a message contains a given element, and if so adds
346 the element value to the provided report. A missing mandatory element
347 causes an exception to be raised.
348
349 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700350 msg: The message containing the element.
351 name: The name of the element.
352 report: A report object to add the element name/value to.
353 is_mandatory: Whether or not this element must be present.
354 is_submsg: Whether this element is itself a message.
355 convert: A function for converting the element value for reporting.
356 msg_name: The name of the message object (for error reporting).
357 linebreak: Whether the value report should induce a line break.
358 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800359
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800360 Returns:
361 A pair consisting of the element value and the generated sub-report for
362 it (if the element is a sub-message, None otherwise). If the element is
363 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800364
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800365 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700366 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800367 """
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700368 element_result = collections.namedtuple('element_result', ['msg', 'report'])
369
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800370 if not msg.HasField(name):
371 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700372 raise error.PayloadError('%smissing mandatory %s %r.' %
373 (msg_name + ' ' if msg_name else '',
374 'sub-message' if is_submsg else 'field',
375 name))
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700376 return element_result(None, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800377
378 value = getattr(msg, name)
379 if is_submsg:
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700380 return element_result(value, report and report.AddSubReport(name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800381 else:
382 if report:
383 report.AddField(name, convert(value), linebreak=linebreak,
384 indent=indent)
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700385 return element_result(value, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800386
387 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700388 def _CheckRepeatedElemNotPresent(msg, field_name, msg_name):
389 """Checks that a repeated element is not specified in the message.
390
391 Args:
392 msg: The message containing the element.
393 field_name: The name of the element.
394 msg_name: The name of the message object (for error reporting).
395
396 Raises:
397 error.PayloadError if the repeated element is present or non-empty.
398 """
399 if getattr(msg, field_name, None):
400 raise error.PayloadError('%sfield %r not empty.' %
401 (msg_name + ' ' if msg_name else '', field_name))
402
403 @staticmethod
404 def _CheckElemNotPresent(msg, field_name, msg_name):
405 """Checks that an element is not specified in the message.
406
407 Args:
408 msg: The message containing the element.
409 field_name: The name of the element.
410 msg_name: The name of the message object (for error reporting).
411
412 Raises:
413 error.PayloadError if the repeated element is present.
414 """
415 if msg.HasField(field_name):
416 raise error.PayloadError('%sfield %r exists.' %
417 (msg_name + ' ' if msg_name else '', field_name))
418
419 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800420 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
421 linebreak=False, indent=0):
422 """Adds a mandatory field; returning first component from _CheckElem."""
423 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
424 convert=convert, msg_name=msg_name,
425 linebreak=linebreak, indent=indent)[0]
426
427 @staticmethod
428 def _CheckOptionalField(msg, field_name, report, convert=str,
429 linebreak=False, indent=0):
430 """Adds an optional field; returning first component from _CheckElem."""
431 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
432 convert=convert, linebreak=linebreak,
433 indent=indent)[0]
434
435 @staticmethod
436 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
437 """Adds a mandatory sub-message; wrapper for _CheckElem."""
438 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
439 msg_name)
440
441 @staticmethod
442 def _CheckOptionalSubMsg(msg, submsg_name, report):
443 """Adds an optional sub-message; wrapper for _CheckElem."""
444 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
445
446 @staticmethod
447 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
448 """Checks that val1 is None iff val2 is None.
449
450 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700451 val1: first value to be compared.
452 val2: second value to be compared.
453 name1: name of object holding the first value.
454 name2: name of object holding the second value.
455 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800456
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800457 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700458 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800459 """
460 if None in (val1, val2) and val1 is not val2:
461 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700462 raise error.PayloadError('%r present without %r%s.' %
463 (present, missing,
464 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800465
466 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700467 def _CheckPresentIffMany(vals, name, obj_name):
468 """Checks that a set of vals and names imply every other element.
469
470 Args:
471 vals: The set of values to be compared.
472 name: The name of the objects holding the corresponding value.
473 obj_name: Name of the object containing these values.
474
475 Raises:
476 error.PayloadError if assertion does not hold.
477 """
478 if any(vals) and not all(vals):
479 raise error.PayloadError('%r is not present in all values%s.' %
480 (name, ' in ' + obj_name if obj_name else ''))
481
482 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800483 def _Run(cmd, send_data=None):
484 """Runs a subprocess, returns its output.
485
486 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700487 cmd: Sequence of command-line argument for invoking the subprocess.
488 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800489
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800490 Returns:
491 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800492 """
493 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
494 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700495 try:
496 result = run_process.communicate(input=send_data)
497 finally:
498 exit_code = run_process.wait()
499
500 if exit_code:
501 raise RuntimeError('Subprocess %r failed with code %r.' %
502 (cmd, exit_code))
503
504 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800505
506 @staticmethod
507 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
508 """Verifies an actual hash against a signed one.
509
510 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700511 sig_data: The raw signature data.
512 pubkey_file_name: Public key used for verifying signature.
513 actual_hash: The actual hash digest.
514 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800515
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800516 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700517 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800518 """
519 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700520 raise error.PayloadError(
521 '%s: signature size (%d) not as expected (256).' %
522 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800523 signed_data, _ = PayloadChecker._Run(
524 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
525 send_data=sig_data)
526
Gilad Arnold5502b562013-03-08 13:22:31 -0800527 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700528 raise error.PayloadError('%s: unexpected signed data length (%d).' %
529 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800530
Gilad Arnold5502b562013-03-08 13:22:31 -0800531 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700532 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
533 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800534
Gilad Arnold5502b562013-03-08 13:22:31 -0800535 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800536 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700537 raise error.PayloadError(
538 '%s: signed hash (%s) different from actual (%s).' %
539 (sig_name, common.FormatSha256(signed_hash),
540 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800541
542 @staticmethod
543 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
544 block_name=None):
545 """Checks that a given length fits given block space.
546
547 This ensures that the number of blocks allocated is appropriate for the
548 length of the data residing in these blocks.
549
550 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700551 length: The actual length of the data.
552 num_blocks: The number of blocks allocated for it.
553 block_size: The size of each block in bytes.
554 length_name: Name of length (used for error reporting).
555 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800556
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800557 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700558 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800559 """
560 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700561 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700562 raise error.PayloadError(
563 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800564 (length_name, length, block_name or '', num_blocks, block_size))
565
566 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700567 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700568 raise error.PayloadError(
569 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800570 (length_name, length, block_name or '', num_blocks - 1, block_size))
571
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700572 def _CheckManifestMinorVersion(self, report):
573 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800574
575 Args:
576 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800577
578 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700579 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800580 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700581 self.minor_version = self._CheckOptionalField(self.payload.manifest,
582 'minor_version', report)
583 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
584 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800585 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700586 'Minor version %d not compatible with payload type %s.' %
587 (self.minor_version, self.payload_type))
588 elif self.minor_version is None:
589 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800590 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700591 raise error.PayloadError('Unsupported minor version: %d' %
592 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800593
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700594 def _CheckManifest(self, report, part_sizes=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800595 """Checks the payload manifest.
596
597 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700598 report: A report object to add to.
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700599 part_sizes: Map of partition label to partition size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800600
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800601 Returns:
602 A tuple consisting of the partition block size used during the update
603 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800604
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800605 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700606 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800607 """
Tudor Brindus40506cd2018-06-18 20:18:17 -0700608 self.major_version = self.payload.header.version
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700609
Xiaochu Liu6cf8e672019-03-14 16:15:42 -0700610 part_sizes = part_sizes or collections.defaultdict(int)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800611 manifest = self.payload.manifest
612 report.AddSection('manifest')
613
614 # Check: block_size must exist and match the expected value.
615 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
616 report, 'manifest')
617 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700618 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
619 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800620
621 # Check: signatures_offset <==> signatures_size.
622 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
623 report)
624 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
625 report)
626 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
627 'signatures_offset', 'signatures_size', 'manifest')
628
Amin Hassani55c75412019-10-07 11:20:39 -0700629 for part in manifest.partitions:
630 name = part.partition_name
631 self.old_part_info[name] = self._CheckOptionalSubMsg(
632 part, 'old_partition_info', report)
633 self.new_part_info[name] = self._CheckMandatorySubMsg(
634 part, 'new_partition_info', report, 'manifest.partitions')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700635
Amin Hassani55c75412019-10-07 11:20:39 -0700636 # Check: Old-style partition infos should not be specified.
637 for _, part in common.CROS_PARTITIONS:
638 self._CheckElemNotPresent(manifest, 'old_%s_info' % part, 'manifest')
639 self._CheckElemNotPresent(manifest, 'new_%s_info' % part, 'manifest')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700640
Amin Hassani55c75412019-10-07 11:20:39 -0700641 # Check: If old_partition_info is specified anywhere, it must be
642 # specified everywhere.
643 old_part_msgs = [part.msg for part in self.old_part_info.values() if part]
644 self._CheckPresentIffMany(old_part_msgs, 'old_partition_info',
645 'manifest.partitions')
Tudor Brindus40506cd2018-06-18 20:18:17 -0700646
647 is_delta = any(part and part.msg for part in self.old_part_info.values())
648 if is_delta:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800649 # Assert/mark delta payload.
650 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700651 raise error.PayloadError(
652 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800653 self.payload_type = _TYPE_DELTA
654
Andrew Lassalle165843c2019-11-05 13:30:34 -0800655 for part, (msg, part_report) in self.old_part_info.items():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700656 # Check: {size, hash} present in old_{kernel,rootfs}_info.
657 field = 'old_%s_info' % part
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700658 self.old_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
Tudor Brindus40506cd2018-06-18 20:18:17 -0700659 part_report, field)
660 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700661 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700662
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700663 # Check: old_{kernel,rootfs} size must fit in respective partition.
664 if self.old_fs_sizes[part] > part_sizes[part] > 0:
665 raise error.PayloadError(
666 'Old %s content (%d) exceed partition size (%d).' %
667 (part, self.old_fs_sizes[part], part_sizes[part]))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800668 else:
669 # Assert/mark full payload.
670 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700671 raise error.PayloadError(
672 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800673 self.payload_type = _TYPE_FULL
674
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700675 # Check: new_{kernel,rootfs}_info present; contains {size, hash}.
Andrew Lassalle165843c2019-11-05 13:30:34 -0800676 for part, (msg, part_report) in self.new_part_info.items():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700677 field = 'new_%s_info' % part
Tudor Brindus40506cd2018-06-18 20:18:17 -0700678 self.new_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
679 part_report, field)
680 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700681 convert=common.FormatSha256)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800682
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700683 # Check: new_{kernel,rootfs} size must fit in respective partition.
684 if self.new_fs_sizes[part] > part_sizes[part] > 0:
685 raise error.PayloadError(
686 'New %s content (%d) exceed partition size (%d).' %
687 (part, self.new_fs_sizes[part], part_sizes[part]))
Gilad Arnold382df5c2013-05-03 12:49:28 -0700688
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800689 # Check: minor_version makes sense for the payload type. This check should
690 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700691 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800692
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800693 def _CheckLength(self, length, total_blocks, op_name, length_name):
694 """Checks whether a length matches the space designated in extents.
695
696 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700697 length: The total length of the data.
698 total_blocks: The total number of blocks in extents.
699 op_name: Operation name (for error reporting).
700 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800701
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800702 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700703 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800704 """
705 # Check: length is non-zero.
706 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700707 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800708
709 # Check that length matches number of blocks.
710 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
711 '%s: %s' % (op_name, length_name))
712
Amin Hassani55c75412019-10-07 11:20:39 -0700713 def _CheckExtents(self, extents, usable_size, block_counters, name):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800714 """Checks a sequence of extents.
715
716 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700717 extents: The sequence of extents to check.
718 usable_size: The usable size of the partition to which the extents apply.
719 block_counters: Array of counters corresponding to the number of blocks.
720 name: The name of the extent block.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800721
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800722 Returns:
723 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800724
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800725 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700726 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800727 """
728 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800729 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700730 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800731 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
732 None, ex_name)
733 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
734 ex_name)
735 end_block = start_block + num_blocks
736
737 # Check: num_blocks > 0.
738 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700739 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800740
Amin Hassani55c75412019-10-07 11:20:39 -0700741 # Check: Make sure we're within the partition limit.
742 if usable_size and end_block * self.block_size > usable_size:
743 raise error.PayloadError(
744 '%s: extent (%s) exceeds usable partition size (%d).' %
745 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800746
Amin Hassani55c75412019-10-07 11:20:39 -0700747 # Record block usage.
Andrew Lassalle165843c2019-11-05 13:30:34 -0800748 for i in range(start_block, end_block):
Amin Hassani55c75412019-10-07 11:20:39 -0700749 block_counters[i] += 1
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800750
751 total_num_blocks += num_blocks
752
753 return total_num_blocks
754
755 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0de7f782017-12-07 12:13:03 -0800756 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800757
758 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700759 op: The operation object from the manifest.
760 data_length: The length of the data blob associated with the operation.
761 total_dst_blocks: Total number of blocks in dst_extents.
762 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800763
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800764 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700765 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800766 """
Andrew Lassalle165843c2019-11-05 13:30:34 -0800767 # Check: total_dst_blocks is not a floating point.
768 if isinstance(total_dst_blocks, float):
769 raise error.PayloadError('%s: contains invalid data type of '
770 'total_dst_blocks.' % op_name)
771
Gilad Arnoldcb638912013-06-24 04:57:11 -0700772 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800773 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700774 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800775
Gilad Arnoldcb638912013-06-24 04:57:11 -0700776 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800777 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700778 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800779
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800780 if op.type == common.OpType.REPLACE:
781 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
782 self.block_size,
783 op_name + '.data_length', 'dst')
784 else:
Sen Jiang771f6482018-04-04 17:59:10 -0700785 # Check: data_length must be smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800786 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700787 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800788 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700789 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800790 (op_name, data_length, total_dst_blocks, self.block_size))
791
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700792 def _CheckZeroOperation(self, op, op_name):
793 """Specific checks for ZERO operations.
794
795 Args:
796 op: The operation object from the manifest.
797 op_name: Operation name for error reporting.
798
799 Raises:
800 error.PayloadError if any check fails.
801 """
802 # Check: Does not contain src extents, data_length and data_offset.
803 if op.src_extents:
804 raise error.PayloadError('%s: contains src_extents.' % op_name)
805 if op.data_length:
806 raise error.PayloadError('%s: contains data_length.' % op_name)
807 if op.data_offset:
808 raise error.PayloadError('%s: contains data_offset.' % op_name)
809
Amin Hassaniefa62d92017-11-09 13:46:56 -0800810 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0f59a9a2019-09-27 10:24:31 -0700811 """Specific checks for SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
Amin Hassaniefa62d92017-11-09 13:46:56 -0800812 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800813
814 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800815 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700816 data_length: The length of the data blob associated with the operation.
817 total_dst_blocks: Total number of blocks in dst_extents.
818 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800819
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800820 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700821 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800822 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800823 # Check: data_{offset,length} present.
824 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700825 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800826
Sen Jiang771f6482018-04-04 17:59:10 -0700827 # Check: data_length is strictly smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800828 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700829 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800830 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700831 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800832 (op_name, data_length, total_dst_blocks, self.block_size,
833 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800834
Amin Hassaniefa62d92017-11-09 13:46:56 -0800835 # Check the existence of src_length and dst_length for legacy bsdiffs.
Amin Hassani0f59a9a2019-09-27 10:24:31 -0700836 if op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800837 if not op.HasField('src_length') or not op.HasField('dst_length'):
838 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
839 else:
840 if op.HasField('src_length') or op.HasField('dst_length'):
841 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
842
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800843 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
844 total_dst_blocks, op_name):
845 """Specific checks for SOURCE_COPY.
846
847 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800848 data_offset: The offset of a data blob for the operation.
849 total_src_blocks: Total number of blocks in src_extents.
850 total_dst_blocks: Total number of blocks in dst_extents.
851 op_name: Operation name for error reporting.
852
853 Raises:
854 error.PayloadError if any check fails.
855 """
856 # Check: No data_{offset,length}.
857 if data_offset is not None:
858 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
859
860 # Check: total_src_blocks == total_dst_blocks.
861 if total_src_blocks != total_dst_blocks:
862 raise error.PayloadError(
863 '%s: total src blocks (%d) != total dst blocks (%d).' %
864 (op_name, total_src_blocks, total_dst_blocks))
865
Sen Jiangd6122bb2015-12-11 10:27:04 -0800866 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800867 """Specific checks for SOURCE_* operations.
868
869 Args:
870 op: The operation object from the manifest.
871 total_src_blocks: Total number of blocks in src_extents.
872 op_name: Operation name for error reporting.
873
874 Raises:
875 error.PayloadError if any check fails.
876 """
877 # Check: total_src_blocks != 0.
878 if total_src_blocks == 0:
879 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
880
Sen Jiang92161a72016-06-28 16:09:38 -0700881 # Check: src_sha256_hash present in minor version >= 3.
882 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800883 raise error.PayloadError('%s: source hash missing.' % op_name)
884
Amin Hassani55c75412019-10-07 11:20:39 -0700885 def _CheckOperation(self, op, op_name, old_block_counters, new_block_counters,
886 old_usable_size, new_usable_size, prev_data_offset,
887 blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800888 """Checks a single update operation.
889
890 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700891 op: The operation object.
892 op_name: Operation name string for error reporting.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700893 old_block_counters: Arrays of block read counters.
894 new_block_counters: Arrays of block write counters.
895 old_usable_size: The overall usable size for src data in bytes.
896 new_usable_size: The overall usable size for dst data in bytes.
897 prev_data_offset: Offset of last used data bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700898 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800899
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800900 Returns:
901 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800902
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800903 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700904 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800905 """
906 # Check extents.
907 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700908 op.src_extents, old_usable_size, old_block_counters,
Amin Hassani55c75412019-10-07 11:20:39 -0700909 op_name + '.src_extents')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800910 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700911 op.dst_extents, new_usable_size, new_block_counters,
Amin Hassani55c75412019-10-07 11:20:39 -0700912 op_name + '.dst_extents')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800913
914 # Check: data_offset present <==> data_length present.
915 data_offset = self._CheckOptionalField(op, 'data_offset', None)
916 data_length = self._CheckOptionalField(op, 'data_length', None)
917 self._CheckPresentIff(data_offset, data_length, 'data_offset',
918 'data_length', op_name)
919
Gilad Arnoldcb638912013-06-24 04:57:11 -0700920 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800921 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700922 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800923
924 # Check {src,dst}_length, if present.
925 if op.HasField('src_length'):
926 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
927 if op.HasField('dst_length'):
928 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
929
930 if op.HasField('data_sha256_hash'):
931 blob_hash_counts['hashed'] += 1
932
Gilad Arnoldcb638912013-06-24 04:57:11 -0700933 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800934 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700935 raise error.PayloadError(
936 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800937 op_name)
938
Gilad Arnoldcb638912013-06-24 04:57:11 -0700939 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800940 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
941 data_length))
942 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700943 raise error.PayloadError(
944 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700945 (op_name, common.FormatSha256(op.data_sha256_hash),
946 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800947 elif data_offset is not None:
Amin Hassani55c75412019-10-07 11:20:39 -0700948 if self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800949 blob_hash_counts['unhashed'] += 1
950 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700951 raise error.PayloadError('%s: unhashed operation not allowed.' %
952 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800953
954 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700955 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800956 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700957 raise error.PayloadError(
958 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800959 (op_name, data_offset, prev_data_offset))
960
961 # Type-specific checks.
Amin Hassani55c75412019-10-07 11:20:39 -0700962 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ,
963 common.OpType.REPLACE_XZ):
Amin Hassani0de7f782017-12-07 12:13:03 -0800964 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700965 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
966 self._CheckZeroOperation(op, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700967 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800968 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
969 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800970 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700971 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800972 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700973 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani77d7cbc2018-02-07 16:21:33 -0800974 elif op.type == common.OpType.BROTLI_BSDIFF and self.minor_version >= 4:
975 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
976 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
977 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 5:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800978 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800979 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800980 else:
Allie Wood7cf9f132015-02-26 14:28:19 -0800981 raise error.PayloadError(
982 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700983 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800984 return data_length if data_length is not None else 0
985
Gilad Arnold382df5c2013-05-03 12:49:28 -0700986 def _SizeToNumBlocks(self, size):
987 """Returns the number of blocks needed to contain a given byte size."""
Andrew Lassalle165843c2019-11-05 13:30:34 -0800988 return (size + self.block_size - 1) // self.block_size
Gilad Arnold382df5c2013-05-03 12:49:28 -0700989
990 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800991 """Returns a freshly initialized array of block counters.
992
Gilad Arnoldcb638912013-06-24 04:57:11 -0700993 Note that the generated array is not portable as is due to byte-ordering
994 issues, hence it should not be serialized.
995
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800996 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700997 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800998
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800999 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001000 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001001 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001002 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001003 return array.array('H',
1004 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001005
Gilad Arnold382df5c2013-05-03 12:49:28 -07001006 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001007 new_fs_size, old_usable_size, new_usable_size,
Amin Hassani55c75412019-10-07 11:20:39 -07001008 prev_data_offset):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001009 """Checks a sequence of update operations.
1010
1011 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001012 operations: The sequence of operations to check.
1013 report: The report object to add to.
1014 base_name: The name of the operation block.
1015 old_fs_size: The old filesystem size in bytes.
1016 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001017 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001018 new_usable_size: The overall usable size of the new partition in bytes.
1019 prev_data_offset: Offset of last used data bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001020
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001021 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001022 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001023
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001024 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001025 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001026 """
1027 # The total size of data blobs used by operations scanned thus far.
1028 total_data_used = 0
1029 # Counts of specific operation types.
1030 op_counts = {
1031 common.OpType.REPLACE: 0,
1032 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001033 common.OpType.REPLACE_XZ: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001034 common.OpType.ZERO: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001035 common.OpType.SOURCE_COPY: 0,
1036 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001037 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001038 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001039 }
1040 # Total blob sizes for each operation type.
1041 op_blob_totals = {
1042 common.OpType.REPLACE: 0,
1043 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001044 common.OpType.REPLACE_XZ: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001045 # SOURCE_COPY operations don't have blobs.
1046 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001047 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001048 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001049 }
1050 # Counts of hashed vs unhashed operations.
1051 blob_hash_counts = {
1052 'hashed': 0,
1053 'unhashed': 0,
1054 }
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001055
1056 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001057 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001058 if old_fs_size else None)
1059 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001060
1061 # Process and verify each operation.
1062 op_num = 0
1063 for op, op_name in common.OperationIter(operations, base_name):
1064 op_num += 1
1065
Gilad Arnoldcb638912013-06-24 04:57:11 -07001066 # Check: Type is valid.
Andrew Lassalle165843c2019-11-05 13:30:34 -08001067 if op.type not in op_counts:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001068 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001069 op_counts[op.type] += 1
1070
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001071 curr_data_used = self._CheckOperation(
Amin Hassani55c75412019-10-07 11:20:39 -07001072 op, op_name, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001073 old_usable_size, new_usable_size,
Amin Hassani55c75412019-10-07 11:20:39 -07001074 prev_data_offset + total_data_used, blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001075 if curr_data_used:
1076 op_blob_totals[op.type] += curr_data_used
1077 total_data_used += curr_data_used
1078
1079 # Report totals and breakdown statistics.
1080 report.AddField('total operations', op_num)
1081 report.AddField(
1082 None,
1083 histogram.Histogram.FromCountDict(op_counts,
1084 key_names=common.OpType.NAMES),
1085 indent=1)
1086 report.AddField('total blobs', sum(blob_hash_counts.values()))
1087 report.AddField(None,
1088 histogram.Histogram.FromCountDict(blob_hash_counts),
1089 indent=1)
1090 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1091 report.AddField(
1092 None,
1093 histogram.Histogram.FromCountDict(op_blob_totals,
1094 formatter=_AddHumanReadableSize,
1095 key_names=common.OpType.NAMES),
1096 indent=1)
1097
1098 # Report read/write histograms.
1099 if old_block_counters:
1100 report.AddField('block read hist',
1101 histogram.Histogram.FromKeyList(old_block_counters),
1102 linebreak=True, indent=1)
1103
Gilad Arnold382df5c2013-05-03 12:49:28 -07001104 new_write_hist = histogram.Histogram.FromKeyList(
1105 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1106 report.AddField('block write hist', new_write_hist, linebreak=True,
1107 indent=1)
1108
Gilad Arnoldcb638912013-06-24 04:57:11 -07001109 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001110 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001111 raise error.PayloadError(
1112 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001113 base_name)
1114
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001115 return total_data_used
1116
1117 def _CheckSignatures(self, report, pubkey_file_name):
1118 """Checks a payload's signature block."""
1119 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1120 sigs = update_metadata_pb2.Signatures()
1121 sigs.ParseFromString(sigs_raw)
1122 report.AddSection('signatures')
1123
Gilad Arnoldcb638912013-06-24 04:57:11 -07001124 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001125 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001126 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001127
Amin Hassani55c75412019-10-07 11:20:39 -07001128 # Check that we don't have the signature operation blob at the end (used to
1129 # be for major version 1).
1130 last_partition = self.payload.manifest.partitions[-1]
1131 if last_partition.operations:
1132 last_op = last_partition.operations[-1]
Amin Hassani8ea19572018-12-05 12:06:21 -08001133 # Check: signatures_{offset,size} must match the last (fake) operation.
Amin Hassani55c75412019-10-07 11:20:39 -07001134 if (last_op.type == common.OpType.REPLACE and
1135 last_op.data_offset == self.sigs_offset and
1136 last_op.data_length == self.sigs_size):
1137 raise error.PayloadError('It seems like the last operation is the '
1138 'signature blob. This is an invalid payload.')
1139
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001140 # Compute the checksum of all data up to signature blob.
1141 # TODO(garnold) we're re-reading the whole data section into a string
1142 # just to compute the checksum; instead, we could do it incrementally as
1143 # we read the blobs one-by-one, under the assumption that we're reading
1144 # them in order (which currently holds). This should be reconsidered.
1145 payload_hasher = self.payload.manifest_hasher.copy()
1146 common.Read(self.payload.payload_file, self.sigs_offset,
1147 offset=self.payload.data_offset, hasher=payload_hasher)
1148
1149 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1150 sig_report = report.AddSubReport(sig_name)
1151
Gilad Arnoldcb638912013-06-24 04:57:11 -07001152 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001153 self._CheckMandatoryField(sig, 'data', None, sig_name)
1154 sig_report.AddField('data len', len(sig.data))
1155
Gilad Arnoldcb638912013-06-24 04:57:11 -07001156 # Check: Signatures pertains to actual payload hash.
Amin Hassanic0840c42020-09-27 18:17:33 -07001157 if sig.data:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001158 self._CheckSha256Signature(sig.data, pubkey_file_name,
1159 payload_hasher.digest(), sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001160
Amin Hassania86b1082018-03-08 15:48:59 -08001161 def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0,
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001162 part_sizes=None, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001163 """Checker entry point, invoking all checks.
1164
1165 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001166 pubkey_file_name: Public key used for signature verification.
1167 metadata_sig_file: Metadata signature, if verification is desired.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001168 metadata_size: Metadata size, if verification is desired.
1169 part_sizes: Mapping of partition label to size in bytes (default: infer
1170 based on payload type and version or filesystem).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001171 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001172
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001173 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001174 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001175 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001176 if not pubkey_file_name:
1177 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1178
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001179 report = _PayloadReport()
1180
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001181 # Get payload file size.
1182 self.payload.payload_file.seek(0, 2)
1183 payload_file_size = self.payload.payload_file.tell()
1184 self.payload.ResetFile()
1185
1186 try:
Amin Hassania86b1082018-03-08 15:48:59 -08001187 # Check metadata_size (if provided).
Amin Hassani8ea19572018-12-05 12:06:21 -08001188 if metadata_size and self.payload.metadata_size != metadata_size:
Amin Hassania86b1082018-03-08 15:48:59 -08001189 raise error.PayloadError('Invalid payload metadata size in payload(%d) '
Amin Hassani8ea19572018-12-05 12:06:21 -08001190 'vs given(%d)' % (self.payload.metadata_size,
Amin Hassania86b1082018-03-08 15:48:59 -08001191 metadata_size))
1192
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001193 # Check metadata signature (if provided).
1194 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001195 metadata_sig = base64.b64decode(metadata_sig_file.read())
1196 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1197 self.payload.manifest_hasher.digest(),
1198 'metadata signature')
1199
Gilad Arnoldcb638912013-06-24 04:57:11 -07001200 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001201 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001202 # Check: Payload version is valid.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001203 if self.payload.header.version not in (1, 2):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001204 raise error.PayloadError('Unknown payload version (%d).' %
1205 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001206 report.AddField('version', self.payload.header.version)
1207 report.AddField('manifest len', self.payload.header.manifest_len)
1208
Gilad Arnoldcb638912013-06-24 04:57:11 -07001209 # Part 2: Check the manifest.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001210 self._CheckManifest(report, part_sizes)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001211 assert self.payload_type, 'payload type should be known by now'
1212
Amin Hassani55c75412019-10-07 11:20:39 -07001213 # Make sure deprecated values are not present in the payload.
1214 for field in ('install_operations', 'kernel_install_operations'):
1215 self._CheckRepeatedElemNotPresent(self.payload.manifest, field,
Tudor Brindus40506cd2018-06-18 20:18:17 -07001216 'manifest')
Amin Hassani55c75412019-10-07 11:20:39 -07001217 for field in ('old_kernel_info', 'old_rootfs_info',
1218 'new_kernel_info', 'new_rootfs_info'):
1219 self._CheckElemNotPresent(self.payload.manifest, field, 'manifest')
Tudor Brindus40506cd2018-06-18 20:18:17 -07001220
1221 total_blob_size = 0
Amin Hassani55c75412019-10-07 11:20:39 -07001222 for part, operations in ((p.partition_name, p.operations)
1223 for p in self.payload.manifest.partitions):
Tudor Brindus40506cd2018-06-18 20:18:17 -07001224 report.AddSection('%s operations' % part)
1225
1226 new_fs_usable_size = self.new_fs_sizes[part]
1227 old_fs_usable_size = self.old_fs_sizes[part]
1228
Xiaochu Liu6cf8e672019-03-14 16:15:42 -07001229 if part_sizes is not None and part_sizes.get(part, None):
Tudor Brindus40506cd2018-06-18 20:18:17 -07001230 new_fs_usable_size = old_fs_usable_size = part_sizes[part]
Tudor Brindus40506cd2018-06-18 20:18:17 -07001231
Amin Hassani0f59a9a2019-09-27 10:24:31 -07001232 # TODO(chromium:243559) only default to the filesystem size if no
1233 # explicit size provided *and* the partition size is not embedded in the
1234 # payload; see issue for more details.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001235 total_blob_size += self._CheckOperations(
1236 operations, report, '%s_install_operations' % part,
1237 self.old_fs_sizes[part], self.new_fs_sizes[part],
Amin Hassani55c75412019-10-07 11:20:39 -07001238 old_fs_usable_size, new_fs_usable_size, total_blob_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001239
Gilad Arnoldcb638912013-06-24 04:57:11 -07001240 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001241 used_payload_size = self.payload.data_offset + total_blob_size
Amin Hassani8ea19572018-12-05 12:06:21 -08001242 # Major versions 2 and higher have a signature at the end, so it should be
1243 # considered in the total size of the image.
Amin Hassani55c75412019-10-07 11:20:39 -07001244 if self.sigs_size:
Amin Hassani8ea19572018-12-05 12:06:21 -08001245 used_payload_size += self.sigs_size
1246
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001247 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001248 raise error.PayloadError(
1249 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001250 (used_payload_size, payload_file_size))
1251
Tudor Brindus40506cd2018-06-18 20:18:17 -07001252 # Part 4: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001253 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001254 self._CheckSignatures(report, pubkey_file_name)
1255
Tudor Brindus40506cd2018-06-18 20:18:17 -07001256 # Part 5: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001257 report.AddSection('summary')
1258 report.AddField('update type', self.payload_type)
1259
1260 report.Finalize()
1261 finally:
1262 if report_out_file:
1263 report.Dump(report_out_file)