blob: 58ad85cb2b605ddbac8ac95900c0b50c2f949a74 [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,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070074}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080075
Andrew Lassalle165843c2019-11-05 13:30:34 -080076
Gilad Arnold553b0ec2013-01-26 01:00:39 -080077#
78# Helper functions.
79#
Gilad Arnoldcb638912013-06-24 04:57:11 -070080
Gilad Arnold553b0ec2013-01-26 01:00:39 -080081def _IsPowerOfTwo(val):
82 """Returns True iff val is a power of two."""
83 return val > 0 and (val & (val - 1)) == 0
84
85
86def _AddFormat(format_func, value):
87 """Adds a custom formatted representation to ordinary string representation.
88
89 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070090 format_func: A value formatter.
91 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080092
Gilad Arnold553b0ec2013-01-26 01:00:39 -080093 Returns:
94 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080095 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070096 ret = str(value)
97 formatted_str = format_func(value)
98 if formatted_str:
99 ret += ' (%s)' % formatted_str
100 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800101
102
103def _AddHumanReadableSize(size):
104 """Adds a human readable representation to a byte size value."""
105 return _AddFormat(format_utils.BytesToHumanReadable, size)
106
107
108#
109# Payload report generator.
110#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700111
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800112class _PayloadReport(object):
113 """A payload report generator.
114
115 A report is essentially a sequence of nodes, which represent data points. It
116 is initialized to have a "global", untitled section. A node may be a
117 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800118 """
119
Gilad Arnoldcb638912013-06-24 04:57:11 -0700120 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800121 class Node(object):
122 """A report node interface."""
123
124 @staticmethod
125 def _Indent(indent, line):
126 """Indents a line by a given indentation amount.
127
128 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700129 indent: The indentation amount.
130 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800131
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800132 Returns:
133 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800134 """
135 return '%*s%s' % (indent, '', line)
136
137 def GenerateLines(self, base_indent, sub_indent, curr_section):
138 """Generates the report lines for this node.
139
140 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700141 base_indent: Base indentation for each line.
142 sub_indent: Additional indentation for sub-nodes.
143 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800144
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800145 Returns:
146 A pair consisting of a list of properly indented report lines and a new
147 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800148 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700149 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800150
151 class FieldNode(Node):
152 """A field report node, representing a (name, value) pair."""
153
154 def __init__(self, name, value, linebreak, indent):
155 super(_PayloadReport.FieldNode, self).__init__()
156 self.name = name
157 self.value = value
158 self.linebreak = linebreak
159 self.indent = indent
160
161 def GenerateLines(self, base_indent, sub_indent, curr_section):
162 """Generates a properly formatted 'name : value' entry."""
163 report_output = ''
164 if self.name:
165 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
166 value_lines = str(self.value).splitlines()
167 if self.linebreak and self.name:
168 report_output += '\n' + '\n'.join(
169 ['%*s%s' % (self.indent, '', line) for line in value_lines])
170 else:
171 if self.name:
172 report_output += ' '
173 report_output += '%*s' % (self.indent, '')
174 cont_line_indent = len(report_output)
175 indented_value_lines = [value_lines[0]]
176 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
177 for line in value_lines[1:]])
178 report_output += '\n'.join(indented_value_lines)
179
180 report_lines = [self._Indent(base_indent, line + '\n')
181 for line in report_output.split('\n')]
182 return report_lines, curr_section
183
184 class SubReportNode(Node):
185 """A sub-report node, representing a nested report."""
186
187 def __init__(self, title, report):
188 super(_PayloadReport.SubReportNode, self).__init__()
189 self.title = title
190 self.report = report
191
192 def GenerateLines(self, base_indent, sub_indent, curr_section):
193 """Recurse with indentation."""
194 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
195 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
196 sub_indent))
197 return report_lines, curr_section
198
199 class SectionNode(Node):
200 """A section header node."""
201
202 def __init__(self, title=None):
203 super(_PayloadReport.SectionNode, self).__init__()
204 self.title = title
205 self.max_field_name_len = 0
206
207 def GenerateLines(self, base_indent, sub_indent, curr_section):
208 """Dump a title line, return self as the (new) current section."""
209 report_lines = []
210 if self.title:
211 report_lines.append(self._Indent(base_indent,
212 '=== %s ===\n' % self.title))
213 return report_lines, self
214
215 def __init__(self):
216 self.report = []
217 self.last_section = self.global_section = self.SectionNode()
218 self.is_finalized = False
219
220 def GenerateLines(self, base_indent, sub_indent):
221 """Generates the lines in the report, properly indented.
222
223 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700224 base_indent: The indentation used for root-level report lines.
225 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800226
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800227 Returns:
228 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800229 """
230 report_lines = []
231 curr_section = self.global_section
232 for node in self.report:
233 node_report_lines, curr_section = node.GenerateLines(
234 base_indent, sub_indent, curr_section)
235 report_lines.extend(node_report_lines)
236
237 return report_lines
238
239 def Dump(self, out_file, base_indent=0, sub_indent=2):
240 """Dumps the report to a file.
241
242 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700243 out_file: File object to output the content to.
244 base_indent: Base indentation for report lines.
245 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800246 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800247 report_lines = self.GenerateLines(base_indent, sub_indent)
248 if report_lines and not self.is_finalized:
249 report_lines.append('(incomplete report)\n')
250
251 for line in report_lines:
252 out_file.write(line)
253
254 def AddField(self, name, value, linebreak=False, indent=0):
255 """Adds a field/value pair to the payload report.
256
257 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700258 name: The field's name.
259 value: The field's value.
260 linebreak: Whether the value should be printed on a new line.
261 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800262 """
263 assert not self.is_finalized
264 if name and self.last_section.max_field_name_len < len(name):
265 self.last_section.max_field_name_len = len(name)
266 self.report.append(self.FieldNode(name, value, linebreak, indent))
267
268 def AddSubReport(self, title):
269 """Adds and returns a sub-report with a title."""
270 assert not self.is_finalized
271 sub_report = self.SubReportNode(title, type(self)())
272 self.report.append(sub_report)
273 return sub_report.report
274
275 def AddSection(self, title):
276 """Adds a new section title."""
277 assert not self.is_finalized
278 self.last_section = self.SectionNode(title)
279 self.report.append(self.last_section)
280
281 def Finalize(self):
282 """Seals the report, marking it as complete."""
283 self.is_finalized = True
284
285
286#
287# Payload verification.
288#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700289
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800290class PayloadChecker(object):
291 """Checking the integrity of an update payload.
292
293 This is a short-lived object whose purpose is to isolate the logic used for
294 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800295 """
296
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700297 def __init__(self, payload, assert_type=None, block_size=0,
298 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700299 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700300
301 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700302 payload: The payload object to check.
303 assert_type: Assert that payload is either 'full' or 'delta' (optional).
304 block_size: Expected filesystem / payload block size (optional).
305 allow_unhashed: Allow operations with unhashed data blobs.
306 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700307 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700308 if not payload.is_init:
309 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700310
311 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800312 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700313 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
314 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700315 raise error.PayloadError(
316 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700317 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700318 raise error.PayloadError('Invalid assert_type value (%r).' %
319 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700320 self.payload_type = assert_type
321 self.allow_unhashed = allow_unhashed
322
323 # Disable specific tests.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700324 self.check_move_same_src_dst_block = (
325 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
326 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800327
328 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800329 self.sigs_offset = 0
330 self.sigs_size = 0
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700331 self.old_part_info = {}
332 self.new_part_info = {}
333 self.new_fs_sizes = collections.defaultdict(int)
334 self.old_fs_sizes = collections.defaultdict(int)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700335 self.minor_version = None
Tudor Brindus40506cd2018-06-18 20:18:17 -0700336 self.major_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800337
338 @staticmethod
339 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
340 msg_name=None, linebreak=False, indent=0):
341 """Adds an element from a protobuf message to the payload report.
342
343 Checks to see whether a message contains a given element, and if so adds
344 the element value to the provided report. A missing mandatory element
345 causes an exception to be raised.
346
347 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700348 msg: The message containing the element.
349 name: The name of the element.
350 report: A report object to add the element name/value to.
351 is_mandatory: Whether or not this element must be present.
352 is_submsg: Whether this element is itself a message.
353 convert: A function for converting the element value for reporting.
354 msg_name: The name of the message object (for error reporting).
355 linebreak: Whether the value report should induce a line break.
356 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800357
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800358 Returns:
359 A pair consisting of the element value and the generated sub-report for
360 it (if the element is a sub-message, None otherwise). If the element is
361 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800362
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800363 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700364 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800365 """
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700366 element_result = collections.namedtuple('element_result', ['msg', 'report'])
367
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800368 if not msg.HasField(name):
369 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700370 raise error.PayloadError('%smissing mandatory %s %r.' %
371 (msg_name + ' ' if msg_name else '',
372 'sub-message' if is_submsg else 'field',
373 name))
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700374 return element_result(None, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800375
376 value = getattr(msg, name)
377 if is_submsg:
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700378 return element_result(value, report and report.AddSubReport(name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800379 else:
380 if report:
381 report.AddField(name, convert(value), linebreak=linebreak,
382 indent=indent)
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700383 return element_result(value, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800384
385 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700386 def _CheckRepeatedElemNotPresent(msg, field_name, msg_name):
387 """Checks that a repeated element is not specified in the message.
388
389 Args:
390 msg: The message containing the element.
391 field_name: The name of the element.
392 msg_name: The name of the message object (for error reporting).
393
394 Raises:
395 error.PayloadError if the repeated element is present or non-empty.
396 """
397 if getattr(msg, field_name, None):
398 raise error.PayloadError('%sfield %r not empty.' %
399 (msg_name + ' ' if msg_name else '', field_name))
400
401 @staticmethod
402 def _CheckElemNotPresent(msg, field_name, msg_name):
403 """Checks that an element is not specified in the message.
404
405 Args:
406 msg: The message containing the element.
407 field_name: The name of the element.
408 msg_name: The name of the message object (for error reporting).
409
410 Raises:
411 error.PayloadError if the repeated element is present.
412 """
413 if msg.HasField(field_name):
414 raise error.PayloadError('%sfield %r exists.' %
415 (msg_name + ' ' if msg_name else '', field_name))
416
417 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800418 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
419 linebreak=False, indent=0):
420 """Adds a mandatory field; returning first component from _CheckElem."""
421 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
422 convert=convert, msg_name=msg_name,
423 linebreak=linebreak, indent=indent)[0]
424
425 @staticmethod
426 def _CheckOptionalField(msg, field_name, report, convert=str,
427 linebreak=False, indent=0):
428 """Adds an optional field; returning first component from _CheckElem."""
429 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
430 convert=convert, linebreak=linebreak,
431 indent=indent)[0]
432
433 @staticmethod
434 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
435 """Adds a mandatory sub-message; wrapper for _CheckElem."""
436 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
437 msg_name)
438
439 @staticmethod
440 def _CheckOptionalSubMsg(msg, submsg_name, report):
441 """Adds an optional sub-message; wrapper for _CheckElem."""
442 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
443
444 @staticmethod
445 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
446 """Checks that val1 is None iff val2 is None.
447
448 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700449 val1: first value to be compared.
450 val2: second value to be compared.
451 name1: name of object holding the first value.
452 name2: name of object holding the second value.
453 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800454
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800455 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700456 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800457 """
458 if None in (val1, val2) and val1 is not val2:
459 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700460 raise error.PayloadError('%r present without %r%s.' %
461 (present, missing,
462 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800463
464 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700465 def _CheckPresentIffMany(vals, name, obj_name):
466 """Checks that a set of vals and names imply every other element.
467
468 Args:
469 vals: The set of values to be compared.
470 name: The name of the objects holding the corresponding value.
471 obj_name: Name of the object containing these values.
472
473 Raises:
474 error.PayloadError if assertion does not hold.
475 """
476 if any(vals) and not all(vals):
477 raise error.PayloadError('%r is not present in all values%s.' %
478 (name, ' in ' + obj_name if obj_name else ''))
479
480 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800481 def _Run(cmd, send_data=None):
482 """Runs a subprocess, returns its output.
483
484 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700485 cmd: Sequence of command-line argument for invoking the subprocess.
486 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800487
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800488 Returns:
489 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800490 """
491 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
492 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700493 try:
494 result = run_process.communicate(input=send_data)
495 finally:
496 exit_code = run_process.wait()
497
498 if exit_code:
499 raise RuntimeError('Subprocess %r failed with code %r.' %
500 (cmd, exit_code))
501
502 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800503
504 @staticmethod
505 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
506 """Verifies an actual hash against a signed one.
507
508 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700509 sig_data: The raw signature data.
510 pubkey_file_name: Public key used for verifying signature.
511 actual_hash: The actual hash digest.
512 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800513
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800514 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700515 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800516 """
517 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700518 raise error.PayloadError(
519 '%s: signature size (%d) not as expected (256).' %
520 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800521 signed_data, _ = PayloadChecker._Run(
522 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
523 send_data=sig_data)
524
Gilad Arnold5502b562013-03-08 13:22:31 -0800525 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700526 raise error.PayloadError('%s: unexpected signed data length (%d).' %
527 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800528
Gilad Arnold5502b562013-03-08 13:22:31 -0800529 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700530 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
531 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800532
Gilad Arnold5502b562013-03-08 13:22:31 -0800533 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800534 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700535 raise error.PayloadError(
536 '%s: signed hash (%s) different from actual (%s).' %
537 (sig_name, common.FormatSha256(signed_hash),
538 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800539
540 @staticmethod
541 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
542 block_name=None):
543 """Checks that a given length fits given block space.
544
545 This ensures that the number of blocks allocated is appropriate for the
546 length of the data residing in these blocks.
547
548 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700549 length: The actual length of the data.
550 num_blocks: The number of blocks allocated for it.
551 block_size: The size of each block in bytes.
552 length_name: Name of length (used for error reporting).
553 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800554
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800555 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700556 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800557 """
558 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700559 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700560 raise error.PayloadError(
561 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800562 (length_name, length, block_name or '', num_blocks, block_size))
563
564 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700565 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700566 raise error.PayloadError(
567 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800568 (length_name, length, block_name or '', num_blocks - 1, block_size))
569
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700570 def _CheckManifestMinorVersion(self, report):
571 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800572
573 Args:
574 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800575
576 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700577 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800578 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700579 self.minor_version = self._CheckOptionalField(self.payload.manifest,
580 'minor_version', report)
581 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
582 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800583 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700584 'Minor version %d not compatible with payload type %s.' %
585 (self.minor_version, self.payload_type))
586 elif self.minor_version is None:
587 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800588 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700589 raise error.PayloadError('Unsupported minor version: %d' %
590 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800591
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700592 def _CheckManifest(self, report, part_sizes=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800593 """Checks the payload manifest.
594
595 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700596 report: A report object to add to.
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700597 part_sizes: Map of partition label to partition size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800598
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800599 Returns:
600 A tuple consisting of the partition block size used during the update
601 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800602
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800603 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700604 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800605 """
Tudor Brindus40506cd2018-06-18 20:18:17 -0700606 self.major_version = self.payload.header.version
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700607
Xiaochu Liu6cf8e672019-03-14 16:15:42 -0700608 part_sizes = part_sizes or collections.defaultdict(int)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800609 manifest = self.payload.manifest
610 report.AddSection('manifest')
611
612 # Check: block_size must exist and match the expected value.
613 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
614 report, 'manifest')
615 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700616 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
617 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800618
619 # Check: signatures_offset <==> signatures_size.
620 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
621 report)
622 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
623 report)
624 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
625 'signatures_offset', 'signatures_size', 'manifest')
626
Amin Hassani55c75412019-10-07 11:20:39 -0700627 for part in manifest.partitions:
628 name = part.partition_name
629 self.old_part_info[name] = self._CheckOptionalSubMsg(
630 part, 'old_partition_info', report)
631 self.new_part_info[name] = self._CheckMandatorySubMsg(
632 part, 'new_partition_info', report, 'manifest.partitions')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700633
Amin Hassani55c75412019-10-07 11:20:39 -0700634 # Check: Old-style partition infos should not be specified.
635 for _, part in common.CROS_PARTITIONS:
636 self._CheckElemNotPresent(manifest, 'old_%s_info' % part, 'manifest')
637 self._CheckElemNotPresent(manifest, 'new_%s_info' % part, 'manifest')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700638
Amin Hassani55c75412019-10-07 11:20:39 -0700639 # Check: If old_partition_info is specified anywhere, it must be
640 # specified everywhere.
641 old_part_msgs = [part.msg for part in self.old_part_info.values() if part]
642 self._CheckPresentIffMany(old_part_msgs, 'old_partition_info',
643 'manifest.partitions')
Tudor Brindus40506cd2018-06-18 20:18:17 -0700644
645 is_delta = any(part and part.msg for part in self.old_part_info.values())
646 if is_delta:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800647 # Assert/mark delta payload.
648 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700649 raise error.PayloadError(
650 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800651 self.payload_type = _TYPE_DELTA
652
Andrew Lassalle165843c2019-11-05 13:30:34 -0800653 for part, (msg, part_report) in self.old_part_info.items():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700654 # Check: {size, hash} present in old_{kernel,rootfs}_info.
655 field = 'old_%s_info' % part
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700656 self.old_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
Tudor Brindus40506cd2018-06-18 20:18:17 -0700657 part_report, field)
658 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700659 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700660
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700661 # Check: old_{kernel,rootfs} size must fit in respective partition.
662 if self.old_fs_sizes[part] > part_sizes[part] > 0:
663 raise error.PayloadError(
664 'Old %s content (%d) exceed partition size (%d).' %
665 (part, self.old_fs_sizes[part], part_sizes[part]))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800666 else:
667 # Assert/mark full payload.
668 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700669 raise error.PayloadError(
670 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800671 self.payload_type = _TYPE_FULL
672
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700673 # Check: new_{kernel,rootfs}_info present; contains {size, hash}.
Andrew Lassalle165843c2019-11-05 13:30:34 -0800674 for part, (msg, part_report) in self.new_part_info.items():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700675 field = 'new_%s_info' % part
Tudor Brindus40506cd2018-06-18 20:18:17 -0700676 self.new_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
677 part_report, field)
678 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700679 convert=common.FormatSha256)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800680
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700681 # Check: new_{kernel,rootfs} size must fit in respective partition.
682 if self.new_fs_sizes[part] > part_sizes[part] > 0:
683 raise error.PayloadError(
684 'New %s content (%d) exceed partition size (%d).' %
685 (part, self.new_fs_sizes[part], part_sizes[part]))
Gilad Arnold382df5c2013-05-03 12:49:28 -0700686
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800687 # Check: minor_version makes sense for the payload type. This check should
688 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700689 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800690
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800691 def _CheckLength(self, length, total_blocks, op_name, length_name):
692 """Checks whether a length matches the space designated in extents.
693
694 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700695 length: The total length of the data.
696 total_blocks: The total number of blocks in extents.
697 op_name: Operation name (for error reporting).
698 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800699
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800700 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700701 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800702 """
703 # Check: length is non-zero.
704 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700705 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800706
707 # Check that length matches number of blocks.
708 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
709 '%s: %s' % (op_name, length_name))
710
Amin Hassani55c75412019-10-07 11:20:39 -0700711 def _CheckExtents(self, extents, usable_size, block_counters, name):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800712 """Checks a sequence of extents.
713
714 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700715 extents: The sequence of extents to check.
716 usable_size: The usable size of the partition to which the extents apply.
717 block_counters: Array of counters corresponding to the number of blocks.
718 name: The name of the extent block.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800719
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800720 Returns:
721 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800722
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800723 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700724 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800725 """
726 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800727 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700728 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800729 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
730 None, ex_name)
731 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
732 ex_name)
733 end_block = start_block + num_blocks
734
735 # Check: num_blocks > 0.
736 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700737 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800738
Amin Hassani55c75412019-10-07 11:20:39 -0700739 # Check: Make sure we're within the partition limit.
740 if usable_size and end_block * self.block_size > usable_size:
741 raise error.PayloadError(
742 '%s: extent (%s) exceeds usable partition size (%d).' %
743 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800744
Amin Hassani55c75412019-10-07 11:20:39 -0700745 # Record block usage.
Andrew Lassalle165843c2019-11-05 13:30:34 -0800746 for i in range(start_block, end_block):
Amin Hassani55c75412019-10-07 11:20:39 -0700747 block_counters[i] += 1
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800748
749 total_num_blocks += num_blocks
750
751 return total_num_blocks
752
753 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0de7f782017-12-07 12:13:03 -0800754 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800755
756 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700757 op: The operation object from the manifest.
758 data_length: The length of the data blob associated with the operation.
759 total_dst_blocks: Total number of blocks in dst_extents.
760 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800761
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800762 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700763 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800764 """
Andrew Lassalle165843c2019-11-05 13:30:34 -0800765 # Check: total_dst_blocks is not a floating point.
766 if isinstance(total_dst_blocks, float):
767 raise error.PayloadError('%s: contains invalid data type of '
768 'total_dst_blocks.' % op_name)
769
Gilad Arnoldcb638912013-06-24 04:57:11 -0700770 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800771 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700772 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800773
Gilad Arnoldcb638912013-06-24 04:57:11 -0700774 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800775 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700776 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800777
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800778 if op.type == common.OpType.REPLACE:
779 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
780 self.block_size,
781 op_name + '.data_length', 'dst')
782 else:
Sen Jiang771f6482018-04-04 17:59:10 -0700783 # Check: data_length must be smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800784 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700785 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800786 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700787 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800788 (op_name, data_length, total_dst_blocks, self.block_size))
789
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700790 def _CheckZeroOperation(self, op, op_name):
791 """Specific checks for ZERO operations.
792
793 Args:
794 op: The operation object from the manifest.
795 op_name: Operation name for error reporting.
796
797 Raises:
798 error.PayloadError if any check fails.
799 """
800 # Check: Does not contain src extents, data_length and data_offset.
801 if op.src_extents:
802 raise error.PayloadError('%s: contains src_extents.' % op_name)
803 if op.data_length:
804 raise error.PayloadError('%s: contains data_length.' % op_name)
805 if op.data_offset:
806 raise error.PayloadError('%s: contains data_offset.' % op_name)
807
Amin Hassaniefa62d92017-11-09 13:46:56 -0800808 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0f59a9a2019-09-27 10:24:31 -0700809 """Specific checks for SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
Amin Hassaniefa62d92017-11-09 13:46:56 -0800810 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800811
812 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800813 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700814 data_length: The length of the data blob associated with the operation.
815 total_dst_blocks: Total number of blocks in dst_extents.
816 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800817
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800818 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700819 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800820 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800821 # Check: data_{offset,length} present.
822 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700823 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800824
Sen Jiang771f6482018-04-04 17:59:10 -0700825 # Check: data_length is strictly smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800826 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700827 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800828 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700829 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800830 (op_name, data_length, total_dst_blocks, self.block_size,
831 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800832
Amin Hassaniefa62d92017-11-09 13:46:56 -0800833 # Check the existence of src_length and dst_length for legacy bsdiffs.
Amin Hassani0f59a9a2019-09-27 10:24:31 -0700834 if op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800835 if not op.HasField('src_length') or not op.HasField('dst_length'):
836 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
837 else:
838 if op.HasField('src_length') or op.HasField('dst_length'):
839 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
840
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800841 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
842 total_dst_blocks, op_name):
843 """Specific checks for SOURCE_COPY.
844
845 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800846 data_offset: The offset of a data blob for the operation.
847 total_src_blocks: Total number of blocks in src_extents.
848 total_dst_blocks: Total number of blocks in dst_extents.
849 op_name: Operation name for error reporting.
850
851 Raises:
852 error.PayloadError if any check fails.
853 """
854 # Check: No data_{offset,length}.
855 if data_offset is not None:
856 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
857
858 # Check: total_src_blocks == total_dst_blocks.
859 if total_src_blocks != total_dst_blocks:
860 raise error.PayloadError(
861 '%s: total src blocks (%d) != total dst blocks (%d).' %
862 (op_name, total_src_blocks, total_dst_blocks))
863
Sen Jiangd6122bb2015-12-11 10:27:04 -0800864 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800865 """Specific checks for SOURCE_* operations.
866
867 Args:
868 op: The operation object from the manifest.
869 total_src_blocks: Total number of blocks in src_extents.
870 op_name: Operation name for error reporting.
871
872 Raises:
873 error.PayloadError if any check fails.
874 """
875 # Check: total_src_blocks != 0.
876 if total_src_blocks == 0:
877 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
878
Sen Jiang92161a72016-06-28 16:09:38 -0700879 # Check: src_sha256_hash present in minor version >= 3.
880 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800881 raise error.PayloadError('%s: source hash missing.' % op_name)
882
Amin Hassani55c75412019-10-07 11:20:39 -0700883 def _CheckOperation(self, op, op_name, old_block_counters, new_block_counters,
884 old_usable_size, new_usable_size, prev_data_offset,
885 blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800886 """Checks a single update operation.
887
888 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700889 op: The operation object.
890 op_name: Operation name string for error reporting.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700891 old_block_counters: Arrays of block read counters.
892 new_block_counters: Arrays of block write counters.
893 old_usable_size: The overall usable size for src data in bytes.
894 new_usable_size: The overall usable size for dst data in bytes.
895 prev_data_offset: Offset of last used data bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700896 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800897
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800898 Returns:
899 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800900
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800901 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700902 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800903 """
904 # Check extents.
905 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700906 op.src_extents, old_usable_size, old_block_counters,
Amin Hassani55c75412019-10-07 11:20:39 -0700907 op_name + '.src_extents')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800908 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700909 op.dst_extents, new_usable_size, new_block_counters,
Amin Hassani55c75412019-10-07 11:20:39 -0700910 op_name + '.dst_extents')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800911
912 # Check: data_offset present <==> data_length present.
913 data_offset = self._CheckOptionalField(op, 'data_offset', None)
914 data_length = self._CheckOptionalField(op, 'data_length', None)
915 self._CheckPresentIff(data_offset, data_length, 'data_offset',
916 'data_length', op_name)
917
Gilad Arnoldcb638912013-06-24 04:57:11 -0700918 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800919 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700920 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800921
922 # Check {src,dst}_length, if present.
923 if op.HasField('src_length'):
924 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
925 if op.HasField('dst_length'):
926 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
927
928 if op.HasField('data_sha256_hash'):
929 blob_hash_counts['hashed'] += 1
930
Gilad Arnoldcb638912013-06-24 04:57:11 -0700931 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800932 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700933 raise error.PayloadError(
934 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800935 op_name)
936
Gilad Arnoldcb638912013-06-24 04:57:11 -0700937 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800938 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
939 data_length))
940 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700941 raise error.PayloadError(
942 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700943 (op_name, common.FormatSha256(op.data_sha256_hash),
944 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800945 elif data_offset is not None:
Amin Hassani55c75412019-10-07 11:20:39 -0700946 if self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800947 blob_hash_counts['unhashed'] += 1
948 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700949 raise error.PayloadError('%s: unhashed operation not allowed.' %
950 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800951
952 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700953 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800954 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700955 raise error.PayloadError(
956 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800957 (op_name, data_offset, prev_data_offset))
958
959 # Type-specific checks.
Amin Hassani55c75412019-10-07 11:20:39 -0700960 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ,
961 common.OpType.REPLACE_XZ):
Amin Hassani0de7f782017-12-07 12:13:03 -0800962 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700963 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
964 self._CheckZeroOperation(op, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700965 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800966 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
967 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800968 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700969 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800970 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700971 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani77d7cbc2018-02-07 16:21:33 -0800972 elif op.type == common.OpType.BROTLI_BSDIFF and self.minor_version >= 4:
973 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
974 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
975 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 5:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800976 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800977 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800978 else:
Allie Wood7cf9f132015-02-26 14:28:19 -0800979 raise error.PayloadError(
980 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700981 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800982 return data_length if data_length is not None else 0
983
Gilad Arnold382df5c2013-05-03 12:49:28 -0700984 def _SizeToNumBlocks(self, size):
985 """Returns the number of blocks needed to contain a given byte size."""
Andrew Lassalle165843c2019-11-05 13:30:34 -0800986 return (size + self.block_size - 1) // self.block_size
Gilad Arnold382df5c2013-05-03 12:49:28 -0700987
988 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800989 """Returns a freshly initialized array of block counters.
990
Gilad Arnoldcb638912013-06-24 04:57:11 -0700991 Note that the generated array is not portable as is due to byte-ordering
992 issues, hence it should not be serialized.
993
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800994 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700995 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800996
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800997 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -0700998 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800999 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001000 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001001 return array.array('H',
1002 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001003
Gilad Arnold382df5c2013-05-03 12:49:28 -07001004 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001005 new_fs_size, old_usable_size, new_usable_size,
Amin Hassani55c75412019-10-07 11:20:39 -07001006 prev_data_offset):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001007 """Checks a sequence of update operations.
1008
1009 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001010 operations: The sequence of operations to check.
1011 report: The report object to add to.
1012 base_name: The name of the operation block.
1013 old_fs_size: The old filesystem size in bytes.
1014 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001015 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001016 new_usable_size: The overall usable size of the new partition in bytes.
1017 prev_data_offset: Offset of last used data bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001018
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001019 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001020 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001021
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001022 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001023 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001024 """
1025 # The total size of data blobs used by operations scanned thus far.
1026 total_data_used = 0
1027 # Counts of specific operation types.
1028 op_counts = {
1029 common.OpType.REPLACE: 0,
1030 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001031 common.OpType.REPLACE_XZ: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001032 common.OpType.ZERO: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001033 common.OpType.SOURCE_COPY: 0,
1034 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001035 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001036 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001037 }
1038 # Total blob sizes for each operation type.
1039 op_blob_totals = {
1040 common.OpType.REPLACE: 0,
1041 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001042 common.OpType.REPLACE_XZ: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001043 # SOURCE_COPY operations don't have blobs.
1044 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001045 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001046 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001047 }
1048 # Counts of hashed vs unhashed operations.
1049 blob_hash_counts = {
1050 'hashed': 0,
1051 'unhashed': 0,
1052 }
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001053
1054 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001055 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001056 if old_fs_size else None)
1057 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001058
1059 # Process and verify each operation.
1060 op_num = 0
1061 for op, op_name in common.OperationIter(operations, base_name):
1062 op_num += 1
1063
Gilad Arnoldcb638912013-06-24 04:57:11 -07001064 # Check: Type is valid.
Andrew Lassalle165843c2019-11-05 13:30:34 -08001065 if op.type not in op_counts:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001066 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001067 op_counts[op.type] += 1
1068
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001069 curr_data_used = self._CheckOperation(
Amin Hassani55c75412019-10-07 11:20:39 -07001070 op, op_name, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001071 old_usable_size, new_usable_size,
Amin Hassani55c75412019-10-07 11:20:39 -07001072 prev_data_offset + total_data_used, blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001073 if curr_data_used:
1074 op_blob_totals[op.type] += curr_data_used
1075 total_data_used += curr_data_used
1076
1077 # Report totals and breakdown statistics.
1078 report.AddField('total operations', op_num)
1079 report.AddField(
1080 None,
1081 histogram.Histogram.FromCountDict(op_counts,
1082 key_names=common.OpType.NAMES),
1083 indent=1)
1084 report.AddField('total blobs', sum(blob_hash_counts.values()))
1085 report.AddField(None,
1086 histogram.Histogram.FromCountDict(blob_hash_counts),
1087 indent=1)
1088 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1089 report.AddField(
1090 None,
1091 histogram.Histogram.FromCountDict(op_blob_totals,
1092 formatter=_AddHumanReadableSize,
1093 key_names=common.OpType.NAMES),
1094 indent=1)
1095
1096 # Report read/write histograms.
1097 if old_block_counters:
1098 report.AddField('block read hist',
1099 histogram.Histogram.FromKeyList(old_block_counters),
1100 linebreak=True, indent=1)
1101
Gilad Arnold382df5c2013-05-03 12:49:28 -07001102 new_write_hist = histogram.Histogram.FromKeyList(
1103 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1104 report.AddField('block write hist', new_write_hist, linebreak=True,
1105 indent=1)
1106
Gilad Arnoldcb638912013-06-24 04:57:11 -07001107 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001108 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001109 raise error.PayloadError(
1110 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001111 base_name)
1112
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001113 return total_data_used
1114
1115 def _CheckSignatures(self, report, pubkey_file_name):
1116 """Checks a payload's signature block."""
1117 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1118 sigs = update_metadata_pb2.Signatures()
1119 sigs.ParseFromString(sigs_raw)
1120 report.AddSection('signatures')
1121
Gilad Arnoldcb638912013-06-24 04:57:11 -07001122 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001123 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001124 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001125
Amin Hassani55c75412019-10-07 11:20:39 -07001126 # Check that we don't have the signature operation blob at the end (used to
1127 # be for major version 1).
1128 last_partition = self.payload.manifest.partitions[-1]
1129 if last_partition.operations:
1130 last_op = last_partition.operations[-1]
Amin Hassani8ea19572018-12-05 12:06:21 -08001131 # Check: signatures_{offset,size} must match the last (fake) operation.
Amin Hassani55c75412019-10-07 11:20:39 -07001132 if (last_op.type == common.OpType.REPLACE and
1133 last_op.data_offset == self.sigs_offset and
1134 last_op.data_length == self.sigs_size):
1135 raise error.PayloadError('It seems like the last operation is the '
1136 'signature blob. This is an invalid payload.')
1137
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001138 # Compute the checksum of all data up to signature blob.
1139 # TODO(garnold) we're re-reading the whole data section into a string
1140 # just to compute the checksum; instead, we could do it incrementally as
1141 # we read the blobs one-by-one, under the assumption that we're reading
1142 # them in order (which currently holds). This should be reconsidered.
1143 payload_hasher = self.payload.manifest_hasher.copy()
1144 common.Read(self.payload.payload_file, self.sigs_offset,
1145 offset=self.payload.data_offset, hasher=payload_hasher)
1146
1147 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1148 sig_report = report.AddSubReport(sig_name)
1149
Gilad Arnoldcb638912013-06-24 04:57:11 -07001150 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001151 self._CheckMandatoryField(sig, 'data', None, sig_name)
1152 sig_report.AddField('data len', len(sig.data))
1153
Gilad Arnoldcb638912013-06-24 04:57:11 -07001154 # Check: Signatures pertains to actual payload hash.
Amin Hassanic0840c42020-09-27 18:17:33 -07001155 if sig.data:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001156 self._CheckSha256Signature(sig.data, pubkey_file_name,
1157 payload_hasher.digest(), sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001158
Amin Hassania86b1082018-03-08 15:48:59 -08001159 def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0,
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001160 part_sizes=None, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001161 """Checker entry point, invoking all checks.
1162
1163 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001164 pubkey_file_name: Public key used for signature verification.
1165 metadata_sig_file: Metadata signature, if verification is desired.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001166 metadata_size: Metadata size, if verification is desired.
1167 part_sizes: Mapping of partition label to size in bytes (default: infer
1168 based on payload type and version or filesystem).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001169 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001170
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001171 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001172 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001173 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001174 if not pubkey_file_name:
1175 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1176
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001177 report = _PayloadReport()
1178
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001179 # Get payload file size.
1180 self.payload.payload_file.seek(0, 2)
1181 payload_file_size = self.payload.payload_file.tell()
1182 self.payload.ResetFile()
1183
1184 try:
Amin Hassania86b1082018-03-08 15:48:59 -08001185 # Check metadata_size (if provided).
Amin Hassani8ea19572018-12-05 12:06:21 -08001186 if metadata_size and self.payload.metadata_size != metadata_size:
Amin Hassania86b1082018-03-08 15:48:59 -08001187 raise error.PayloadError('Invalid payload metadata size in payload(%d) '
Amin Hassani8ea19572018-12-05 12:06:21 -08001188 'vs given(%d)' % (self.payload.metadata_size,
Amin Hassania86b1082018-03-08 15:48:59 -08001189 metadata_size))
1190
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001191 # Check metadata signature (if provided).
1192 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001193 metadata_sig = base64.b64decode(metadata_sig_file.read())
1194 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1195 self.payload.manifest_hasher.digest(),
1196 'metadata signature')
1197
Gilad Arnoldcb638912013-06-24 04:57:11 -07001198 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001199 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001200 # Check: Payload version is valid.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001201 if self.payload.header.version not in (1, 2):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001202 raise error.PayloadError('Unknown payload version (%d).' %
1203 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001204 report.AddField('version', self.payload.header.version)
1205 report.AddField('manifest len', self.payload.header.manifest_len)
1206
Gilad Arnoldcb638912013-06-24 04:57:11 -07001207 # Part 2: Check the manifest.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001208 self._CheckManifest(report, part_sizes)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001209 assert self.payload_type, 'payload type should be known by now'
1210
Amin Hassani55c75412019-10-07 11:20:39 -07001211 # Make sure deprecated values are not present in the payload.
1212 for field in ('install_operations', 'kernel_install_operations'):
1213 self._CheckRepeatedElemNotPresent(self.payload.manifest, field,
Tudor Brindus40506cd2018-06-18 20:18:17 -07001214 'manifest')
Amin Hassani55c75412019-10-07 11:20:39 -07001215 for field in ('old_kernel_info', 'old_rootfs_info',
1216 'new_kernel_info', 'new_rootfs_info'):
1217 self._CheckElemNotPresent(self.payload.manifest, field, 'manifest')
Tudor Brindus40506cd2018-06-18 20:18:17 -07001218
1219 total_blob_size = 0
Amin Hassani55c75412019-10-07 11:20:39 -07001220 for part, operations in ((p.partition_name, p.operations)
1221 for p in self.payload.manifest.partitions):
Tudor Brindus40506cd2018-06-18 20:18:17 -07001222 report.AddSection('%s operations' % part)
1223
1224 new_fs_usable_size = self.new_fs_sizes[part]
1225 old_fs_usable_size = self.old_fs_sizes[part]
1226
Xiaochu Liu6cf8e672019-03-14 16:15:42 -07001227 if part_sizes is not None and part_sizes.get(part, None):
Tudor Brindus40506cd2018-06-18 20:18:17 -07001228 new_fs_usable_size = old_fs_usable_size = part_sizes[part]
Tudor Brindus40506cd2018-06-18 20:18:17 -07001229
Amin Hassani0f59a9a2019-09-27 10:24:31 -07001230 # TODO(chromium:243559) only default to the filesystem size if no
1231 # explicit size provided *and* the partition size is not embedded in the
1232 # payload; see issue for more details.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001233 total_blob_size += self._CheckOperations(
1234 operations, report, '%s_install_operations' % part,
1235 self.old_fs_sizes[part], self.new_fs_sizes[part],
Amin Hassani55c75412019-10-07 11:20:39 -07001236 old_fs_usable_size, new_fs_usable_size, total_blob_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001237
Gilad Arnoldcb638912013-06-24 04:57:11 -07001238 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001239 used_payload_size = self.payload.data_offset + total_blob_size
Amin Hassani8ea19572018-12-05 12:06:21 -08001240 # Major versions 2 and higher have a signature at the end, so it should be
1241 # considered in the total size of the image.
Amin Hassani55c75412019-10-07 11:20:39 -07001242 if self.sigs_size:
Amin Hassani8ea19572018-12-05 12:06:21 -08001243 used_payload_size += self.sigs_size
1244
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001245 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001246 raise error.PayloadError(
1247 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001248 (used_payload_size, payload_file_size))
1249
Tudor Brindus40506cd2018-06-18 20:18:17 -07001250 # Part 4: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001251 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001252 self._CheckSignatures(report, pubkey_file_name)
1253
Tudor Brindus40506cd2018-06-18 20:18:17 -07001254 # Part 5: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001255 report.AddSection('summary')
1256 report.AddField('update type', self.payload_type)
1257
1258 report.Finalize()
1259 finally:
1260 if report_out_file:
1261 report.Dump(report_out_file)