blob: 4558872b870670d1f4ca59aed0d59f9d8dfdc0b2 [file] [log] [blame]
Amin Hassanif94b6432018-01-26 17:39:47 -08001#
2# Copyright (C) 2013 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
Gilad Arnold553b0ec2013-01-26 01:00:39 -080016
17"""Verifying the integrity of a Chrome OS update payload.
18
19This module is used internally by the main Payload class for verifying the
20integrity of an update payload. The interface for invoking the checks is as
21follows:
22
23 checker = PayloadChecker(payload)
24 checker.Run(...)
Gilad Arnold553b0ec2013-01-26 01:00:39 -080025"""
26
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080027from __future__ import print_function
28
Gilad Arnold553b0ec2013-01-26 01:00:39 -080029import array
30import base64
Tudor Brindus8d05a7e2018-06-14 11:18:18 -070031import collections
Gilad Arnold553b0ec2013-01-26 01:00:39 -080032import hashlib
Gilad Arnoldcb638912013-06-24 04:57:11 -070033import itertools
Gilad Arnold9b90c932013-05-22 17:12:56 -070034import os
Gilad Arnold553b0ec2013-01-26 01:00:39 -080035import subprocess
36
Amin Hassanib05a65a2017-12-18 15:15:32 -080037from update_payload import common
38from update_payload import error
39from update_payload import format_utils
40from update_payload import histogram
41from update_payload import update_metadata_pb2
Gilad Arnold553b0ec2013-01-26 01:00:39 -080042
43
44#
Gilad Arnold9b90c932013-05-22 17:12:56 -070045# Constants.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080046#
Gilad Arnoldcb638912013-06-24 04:57:11 -070047
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070048_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
49_CHECK_PAYLOAD_SIG = 'payload-sig'
50CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070051 _CHECK_MOVE_SAME_SRC_DST_BLOCK,
52 _CHECK_PAYLOAD_SIG,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070053)
54
Gilad Arnold553b0ec2013-01-26 01:00:39 -080055_TYPE_FULL = 'full'
56_TYPE_DELTA = 'delta'
57
58_DEFAULT_BLOCK_SIZE = 4096
59
Gilad Arnold9b90c932013-05-22 17:12:56 -070060_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem'
61_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
62 _DEFAULT_PUBKEY_BASE_NAME)
63
Gilad Arnold0d575cd2015-07-13 17:29:21 -070064# Supported minor version map to payload types allowed to be using them.
65_SUPPORTED_MINOR_VERSIONS = {
66 0: (_TYPE_FULL,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070067 2: (_TYPE_DELTA,),
Sen Jiang912c4df2015-12-10 12:17:13 -080068 3: (_TYPE_DELTA,),
Sen Jiang92161a72016-06-28 16:09:38 -070069 4: (_TYPE_DELTA,),
Amin Hassani77d7cbc2018-02-07 16:21:33 -080070 5: (_TYPE_DELTA,),
Amin Hassanief1af272019-01-10 14:04:27 -080071 6: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070072}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080073
74#
75# Helper functions.
76#
Gilad Arnoldcb638912013-06-24 04:57:11 -070077
Gilad Arnold553b0ec2013-01-26 01:00:39 -080078def _IsPowerOfTwo(val):
79 """Returns True iff val is a power of two."""
80 return val > 0 and (val & (val - 1)) == 0
81
82
83def _AddFormat(format_func, value):
84 """Adds a custom formatted representation to ordinary string representation.
85
86 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070087 format_func: A value formatter.
88 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080089
Gilad Arnold553b0ec2013-01-26 01:00:39 -080090 Returns:
91 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080092 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070093 ret = str(value)
94 formatted_str = format_func(value)
95 if formatted_str:
96 ret += ' (%s)' % formatted_str
97 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -080098
99
100def _AddHumanReadableSize(size):
101 """Adds a human readable representation to a byte size value."""
102 return _AddFormat(format_utils.BytesToHumanReadable, size)
103
104
105#
106# Payload report generator.
107#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700108
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800109class _PayloadReport(object):
110 """A payload report generator.
111
112 A report is essentially a sequence of nodes, which represent data points. It
113 is initialized to have a "global", untitled section. A node may be a
114 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800115 """
116
Gilad Arnoldcb638912013-06-24 04:57:11 -0700117 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800118 class Node(object):
119 """A report node interface."""
120
121 @staticmethod
122 def _Indent(indent, line):
123 """Indents a line by a given indentation amount.
124
125 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700126 indent: The indentation amount.
127 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800128
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800129 Returns:
130 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800131 """
132 return '%*s%s' % (indent, '', line)
133
134 def GenerateLines(self, base_indent, sub_indent, curr_section):
135 """Generates the report lines for this node.
136
137 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700138 base_indent: Base indentation for each line.
139 sub_indent: Additional indentation for sub-nodes.
140 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800141
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800142 Returns:
143 A pair consisting of a list of properly indented report lines and a new
144 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800145 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700146 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800147
148 class FieldNode(Node):
149 """A field report node, representing a (name, value) pair."""
150
151 def __init__(self, name, value, linebreak, indent):
152 super(_PayloadReport.FieldNode, self).__init__()
153 self.name = name
154 self.value = value
155 self.linebreak = linebreak
156 self.indent = indent
157
158 def GenerateLines(self, base_indent, sub_indent, curr_section):
159 """Generates a properly formatted 'name : value' entry."""
160 report_output = ''
161 if self.name:
162 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
163 value_lines = str(self.value).splitlines()
164 if self.linebreak and self.name:
165 report_output += '\n' + '\n'.join(
166 ['%*s%s' % (self.indent, '', line) for line in value_lines])
167 else:
168 if self.name:
169 report_output += ' '
170 report_output += '%*s' % (self.indent, '')
171 cont_line_indent = len(report_output)
172 indented_value_lines = [value_lines[0]]
173 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
174 for line in value_lines[1:]])
175 report_output += '\n'.join(indented_value_lines)
176
177 report_lines = [self._Indent(base_indent, line + '\n')
178 for line in report_output.split('\n')]
179 return report_lines, curr_section
180
181 class SubReportNode(Node):
182 """A sub-report node, representing a nested report."""
183
184 def __init__(self, title, report):
185 super(_PayloadReport.SubReportNode, self).__init__()
186 self.title = title
187 self.report = report
188
189 def GenerateLines(self, base_indent, sub_indent, curr_section):
190 """Recurse with indentation."""
191 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
192 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
193 sub_indent))
194 return report_lines, curr_section
195
196 class SectionNode(Node):
197 """A section header node."""
198
199 def __init__(self, title=None):
200 super(_PayloadReport.SectionNode, self).__init__()
201 self.title = title
202 self.max_field_name_len = 0
203
204 def GenerateLines(self, base_indent, sub_indent, curr_section):
205 """Dump a title line, return self as the (new) current section."""
206 report_lines = []
207 if self.title:
208 report_lines.append(self._Indent(base_indent,
209 '=== %s ===\n' % self.title))
210 return report_lines, self
211
212 def __init__(self):
213 self.report = []
214 self.last_section = self.global_section = self.SectionNode()
215 self.is_finalized = False
216
217 def GenerateLines(self, base_indent, sub_indent):
218 """Generates the lines in the report, properly indented.
219
220 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700221 base_indent: The indentation used for root-level report lines.
222 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800223
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800224 Returns:
225 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800226 """
227 report_lines = []
228 curr_section = self.global_section
229 for node in self.report:
230 node_report_lines, curr_section = node.GenerateLines(
231 base_indent, sub_indent, curr_section)
232 report_lines.extend(node_report_lines)
233
234 return report_lines
235
236 def Dump(self, out_file, base_indent=0, sub_indent=2):
237 """Dumps the report to a file.
238
239 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700240 out_file: File object to output the content to.
241 base_indent: Base indentation for report lines.
242 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800243 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800244 report_lines = self.GenerateLines(base_indent, sub_indent)
245 if report_lines and not self.is_finalized:
246 report_lines.append('(incomplete report)\n')
247
248 for line in report_lines:
249 out_file.write(line)
250
251 def AddField(self, name, value, linebreak=False, indent=0):
252 """Adds a field/value pair to the payload report.
253
254 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700255 name: The field's name.
256 value: The field's value.
257 linebreak: Whether the value should be printed on a new line.
258 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800259 """
260 assert not self.is_finalized
261 if name and self.last_section.max_field_name_len < len(name):
262 self.last_section.max_field_name_len = len(name)
263 self.report.append(self.FieldNode(name, value, linebreak, indent))
264
265 def AddSubReport(self, title):
266 """Adds and returns a sub-report with a title."""
267 assert not self.is_finalized
268 sub_report = self.SubReportNode(title, type(self)())
269 self.report.append(sub_report)
270 return sub_report.report
271
272 def AddSection(self, title):
273 """Adds a new section title."""
274 assert not self.is_finalized
275 self.last_section = self.SectionNode(title)
276 self.report.append(self.last_section)
277
278 def Finalize(self):
279 """Seals the report, marking it as complete."""
280 self.is_finalized = True
281
282
283#
284# Payload verification.
285#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700286
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800287class PayloadChecker(object):
288 """Checking the integrity of an update payload.
289
290 This is a short-lived object whose purpose is to isolate the logic used for
291 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800292 """
293
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700294 def __init__(self, payload, assert_type=None, block_size=0,
295 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700296 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700297
298 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700299 payload: The payload object to check.
300 assert_type: Assert that payload is either 'full' or 'delta' (optional).
301 block_size: Expected filesystem / payload block size (optional).
302 allow_unhashed: Allow operations with unhashed data blobs.
303 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700304 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700305 if not payload.is_init:
306 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700307
308 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800309 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700310 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
311 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700312 raise error.PayloadError(
313 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700314 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700315 raise error.PayloadError('Invalid assert_type value (%r).' %
316 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700317 self.payload_type = assert_type
318 self.allow_unhashed = allow_unhashed
319
320 # Disable specific tests.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700321 self.check_move_same_src_dst_block = (
322 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
323 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800324
325 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800326 self.sigs_offset = 0
327 self.sigs_size = 0
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700328 self.old_part_info = {}
329 self.new_part_info = {}
330 self.new_fs_sizes = collections.defaultdict(int)
331 self.old_fs_sizes = collections.defaultdict(int)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700332 self.minor_version = None
Tudor Brindus40506cd2018-06-18 20:18:17 -0700333 self.major_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800334
335 @staticmethod
336 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
337 msg_name=None, linebreak=False, indent=0):
338 """Adds an element from a protobuf message to the payload report.
339
340 Checks to see whether a message contains a given element, and if so adds
341 the element value to the provided report. A missing mandatory element
342 causes an exception to be raised.
343
344 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700345 msg: The message containing the element.
346 name: The name of the element.
347 report: A report object to add the element name/value to.
348 is_mandatory: Whether or not this element must be present.
349 is_submsg: Whether this element is itself a message.
350 convert: A function for converting the element value for reporting.
351 msg_name: The name of the message object (for error reporting).
352 linebreak: Whether the value report should induce a line break.
353 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800354
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800355 Returns:
356 A pair consisting of the element value and the generated sub-report for
357 it (if the element is a sub-message, None otherwise). If the element is
358 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800359
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800360 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700361 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800362 """
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700363 element_result = collections.namedtuple('element_result', ['msg', 'report'])
364
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800365 if not msg.HasField(name):
366 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700367 raise error.PayloadError('%smissing mandatory %s %r.' %
368 (msg_name + ' ' if msg_name else '',
369 'sub-message' if is_submsg else 'field',
370 name))
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700371 return element_result(None, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800372
373 value = getattr(msg, name)
374 if is_submsg:
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700375 return element_result(value, report and report.AddSubReport(name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800376 else:
377 if report:
378 report.AddField(name, convert(value), linebreak=linebreak,
379 indent=indent)
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700380 return element_result(value, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800381
382 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700383 def _CheckRepeatedElemNotPresent(msg, field_name, msg_name):
384 """Checks that a repeated element is not specified in the message.
385
386 Args:
387 msg: The message containing the element.
388 field_name: The name of the element.
389 msg_name: The name of the message object (for error reporting).
390
391 Raises:
392 error.PayloadError if the repeated element is present or non-empty.
393 """
394 if getattr(msg, field_name, None):
395 raise error.PayloadError('%sfield %r not empty.' %
396 (msg_name + ' ' if msg_name else '', field_name))
397
398 @staticmethod
399 def _CheckElemNotPresent(msg, field_name, msg_name):
400 """Checks that an element is not specified in the message.
401
402 Args:
403 msg: The message containing the element.
404 field_name: The name of the element.
405 msg_name: The name of the message object (for error reporting).
406
407 Raises:
408 error.PayloadError if the repeated element is present.
409 """
410 if msg.HasField(field_name):
411 raise error.PayloadError('%sfield %r exists.' %
412 (msg_name + ' ' if msg_name else '', field_name))
413
414 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800415 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
416 linebreak=False, indent=0):
417 """Adds a mandatory field; returning first component from _CheckElem."""
418 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
419 convert=convert, msg_name=msg_name,
420 linebreak=linebreak, indent=indent)[0]
421
422 @staticmethod
423 def _CheckOptionalField(msg, field_name, report, convert=str,
424 linebreak=False, indent=0):
425 """Adds an optional field; returning first component from _CheckElem."""
426 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
427 convert=convert, linebreak=linebreak,
428 indent=indent)[0]
429
430 @staticmethod
431 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
432 """Adds a mandatory sub-message; wrapper for _CheckElem."""
433 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
434 msg_name)
435
436 @staticmethod
437 def _CheckOptionalSubMsg(msg, submsg_name, report):
438 """Adds an optional sub-message; wrapper for _CheckElem."""
439 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
440
441 @staticmethod
442 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
443 """Checks that val1 is None iff val2 is None.
444
445 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700446 val1: first value to be compared.
447 val2: second value to be compared.
448 name1: name of object holding the first value.
449 name2: name of object holding the second value.
450 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800451
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800452 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700453 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800454 """
455 if None in (val1, val2) and val1 is not val2:
456 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700457 raise error.PayloadError('%r present without %r%s.' %
458 (present, missing,
459 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800460
461 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700462 def _CheckPresentIffMany(vals, name, obj_name):
463 """Checks that a set of vals and names imply every other element.
464
465 Args:
466 vals: The set of values to be compared.
467 name: The name of the objects holding the corresponding value.
468 obj_name: Name of the object containing these values.
469
470 Raises:
471 error.PayloadError if assertion does not hold.
472 """
473 if any(vals) and not all(vals):
474 raise error.PayloadError('%r is not present in all values%s.' %
475 (name, ' in ' + obj_name if obj_name else ''))
476
477 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800478 def _Run(cmd, send_data=None):
479 """Runs a subprocess, returns its output.
480
481 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700482 cmd: Sequence of command-line argument for invoking the subprocess.
483 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800484
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800485 Returns:
486 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800487 """
488 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
489 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700490 try:
491 result = run_process.communicate(input=send_data)
492 finally:
493 exit_code = run_process.wait()
494
495 if exit_code:
496 raise RuntimeError('Subprocess %r failed with code %r.' %
497 (cmd, exit_code))
498
499 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800500
501 @staticmethod
502 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
503 """Verifies an actual hash against a signed one.
504
505 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700506 sig_data: The raw signature data.
507 pubkey_file_name: Public key used for verifying signature.
508 actual_hash: The actual hash digest.
509 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800510
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800511 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700512 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800513 """
514 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700515 raise error.PayloadError(
516 '%s: signature size (%d) not as expected (256).' %
517 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800518 signed_data, _ = PayloadChecker._Run(
519 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
520 send_data=sig_data)
521
Gilad Arnold5502b562013-03-08 13:22:31 -0800522 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700523 raise error.PayloadError('%s: unexpected signed data length (%d).' %
524 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800525
Gilad Arnold5502b562013-03-08 13:22:31 -0800526 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700527 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
528 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800529
Gilad Arnold5502b562013-03-08 13:22:31 -0800530 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800531 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700532 raise error.PayloadError(
533 '%s: signed hash (%s) different from actual (%s).' %
534 (sig_name, common.FormatSha256(signed_hash),
535 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800536
537 @staticmethod
538 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
539 block_name=None):
540 """Checks that a given length fits given block space.
541
542 This ensures that the number of blocks allocated is appropriate for the
543 length of the data residing in these blocks.
544
545 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700546 length: The actual length of the data.
547 num_blocks: The number of blocks allocated for it.
548 block_size: The size of each block in bytes.
549 length_name: Name of length (used for error reporting).
550 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800551
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800552 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700553 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800554 """
555 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700556 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700557 raise error.PayloadError(
558 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800559 (length_name, length, block_name or '', num_blocks, block_size))
560
561 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700562 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700563 raise error.PayloadError(
564 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800565 (length_name, length, block_name or '', num_blocks - 1, block_size))
566
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700567 def _CheckManifestMinorVersion(self, report):
568 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800569
570 Args:
571 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800572
573 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700574 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800575 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700576 self.minor_version = self._CheckOptionalField(self.payload.manifest,
577 'minor_version', report)
578 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
579 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800580 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700581 'Minor version %d not compatible with payload type %s.' %
582 (self.minor_version, self.payload_type))
583 elif self.minor_version is None:
584 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800585 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700586 raise error.PayloadError('Unsupported minor version: %d' %
587 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800588
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700589 def _CheckManifest(self, report, part_sizes=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800590 """Checks the payload manifest.
591
592 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700593 report: A report object to add to.
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700594 part_sizes: Map of partition label to partition size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800595
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800596 Returns:
597 A tuple consisting of the partition block size used during the update
598 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800599
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800600 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700601 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800602 """
Tudor Brindus40506cd2018-06-18 20:18:17 -0700603 self.major_version = self.payload.header.version
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700604
Xiaochu Liu6cf8e672019-03-14 16:15:42 -0700605 part_sizes = part_sizes or collections.defaultdict(int)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800606 manifest = self.payload.manifest
607 report.AddSection('manifest')
608
609 # Check: block_size must exist and match the expected value.
610 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
611 report, 'manifest')
612 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700613 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
614 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800615
616 # Check: signatures_offset <==> signatures_size.
617 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
618 report)
619 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
620 report)
621 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
622 'signatures_offset', 'signatures_size', 'manifest')
623
Amin Hassani55c75412019-10-07 11:20:39 -0700624 for part in manifest.partitions:
625 name = part.partition_name
626 self.old_part_info[name] = self._CheckOptionalSubMsg(
627 part, 'old_partition_info', report)
628 self.new_part_info[name] = self._CheckMandatorySubMsg(
629 part, 'new_partition_info', report, 'manifest.partitions')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700630
Amin Hassani55c75412019-10-07 11:20:39 -0700631 # Check: Old-style partition infos should not be specified.
632 for _, part in common.CROS_PARTITIONS:
633 self._CheckElemNotPresent(manifest, 'old_%s_info' % part, 'manifest')
634 self._CheckElemNotPresent(manifest, 'new_%s_info' % part, 'manifest')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700635
Amin Hassani55c75412019-10-07 11:20:39 -0700636 # Check: If old_partition_info is specified anywhere, it must be
637 # specified everywhere.
638 old_part_msgs = [part.msg for part in self.old_part_info.values() if part]
639 self._CheckPresentIffMany(old_part_msgs, 'old_partition_info',
640 'manifest.partitions')
Tudor Brindus40506cd2018-06-18 20:18:17 -0700641
642 is_delta = any(part and part.msg for part in self.old_part_info.values())
643 if is_delta:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800644 # Assert/mark delta payload.
645 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700646 raise error.PayloadError(
647 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800648 self.payload_type = _TYPE_DELTA
649
Tudor Brindus40506cd2018-06-18 20:18:17 -0700650 for part, (msg, part_report) in self.old_part_info.iteritems():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700651 # Check: {size, hash} present in old_{kernel,rootfs}_info.
652 field = 'old_%s_info' % part
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700653 self.old_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
Tudor Brindus40506cd2018-06-18 20:18:17 -0700654 part_report, field)
655 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700656 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700657
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700658 # Check: old_{kernel,rootfs} size must fit in respective partition.
659 if self.old_fs_sizes[part] > part_sizes[part] > 0:
660 raise error.PayloadError(
661 'Old %s content (%d) exceed partition size (%d).' %
662 (part, self.old_fs_sizes[part], part_sizes[part]))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800663 else:
664 # Assert/mark full payload.
665 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700666 raise error.PayloadError(
667 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800668 self.payload_type = _TYPE_FULL
669
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700670 # Check: new_{kernel,rootfs}_info present; contains {size, hash}.
Tudor Brindus40506cd2018-06-18 20:18:17 -0700671 for part, (msg, part_report) in self.new_part_info.iteritems():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700672 field = 'new_%s_info' % part
Tudor Brindus40506cd2018-06-18 20:18:17 -0700673 self.new_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
674 part_report, field)
675 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700676 convert=common.FormatSha256)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800677
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700678 # Check: new_{kernel,rootfs} size must fit in respective partition.
679 if self.new_fs_sizes[part] > part_sizes[part] > 0:
680 raise error.PayloadError(
681 'New %s content (%d) exceed partition size (%d).' %
682 (part, self.new_fs_sizes[part], part_sizes[part]))
Gilad Arnold382df5c2013-05-03 12:49:28 -0700683
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800684 # Check: minor_version makes sense for the payload type. This check should
685 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700686 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800687
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800688 def _CheckLength(self, length, total_blocks, op_name, length_name):
689 """Checks whether a length matches the space designated in extents.
690
691 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700692 length: The total length of the data.
693 total_blocks: The total number of blocks in extents.
694 op_name: Operation name (for error reporting).
695 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800696
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800697 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700698 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800699 """
700 # Check: length is non-zero.
701 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700702 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800703
704 # Check that length matches number of blocks.
705 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
706 '%s: %s' % (op_name, length_name))
707
Amin Hassani55c75412019-10-07 11:20:39 -0700708 def _CheckExtents(self, extents, usable_size, block_counters, name):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800709 """Checks a sequence of extents.
710
711 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700712 extents: The sequence of extents to check.
713 usable_size: The usable size of the partition to which the extents apply.
714 block_counters: Array of counters corresponding to the number of blocks.
715 name: The name of the extent block.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800716
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800717 Returns:
718 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800719
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800720 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700721 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800722 """
723 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800724 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700725 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800726 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
727 None, ex_name)
728 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
729 ex_name)
730 end_block = start_block + num_blocks
731
732 # Check: num_blocks > 0.
733 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700734 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800735
Amin Hassani55c75412019-10-07 11:20:39 -0700736 # Check: Make sure we're within the partition limit.
737 if usable_size and end_block * self.block_size > usable_size:
738 raise error.PayloadError(
739 '%s: extent (%s) exceeds usable partition size (%d).' %
740 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800741
Amin Hassani55c75412019-10-07 11:20:39 -0700742 # Record block usage.
743 for i in xrange(start_block, end_block):
744 block_counters[i] += 1
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800745
746 total_num_blocks += num_blocks
747
748 return total_num_blocks
749
750 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0de7f782017-12-07 12:13:03 -0800751 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800752
753 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700754 op: The operation object from the manifest.
755 data_length: The length of the data blob associated with the operation.
756 total_dst_blocks: Total number of blocks in dst_extents.
757 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800758
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800759 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700760 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800761 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700762 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800763 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700764 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800765
Gilad Arnoldcb638912013-06-24 04:57:11 -0700766 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800767 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700768 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800769
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800770 if op.type == common.OpType.REPLACE:
771 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
772 self.block_size,
773 op_name + '.data_length', 'dst')
774 else:
Sen Jiang771f6482018-04-04 17:59:10 -0700775 # Check: data_length must be smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800776 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700777 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800778 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700779 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800780 (op_name, data_length, total_dst_blocks, self.block_size))
781
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700782 def _CheckZeroOperation(self, op, op_name):
783 """Specific checks for ZERO operations.
784
785 Args:
786 op: The operation object from the manifest.
787 op_name: Operation name for error reporting.
788
789 Raises:
790 error.PayloadError if any check fails.
791 """
792 # Check: Does not contain src extents, data_length and data_offset.
793 if op.src_extents:
794 raise error.PayloadError('%s: contains src_extents.' % op_name)
795 if op.data_length:
796 raise error.PayloadError('%s: contains data_length.' % op_name)
797 if op.data_offset:
798 raise error.PayloadError('%s: contains data_offset.' % op_name)
799
Amin Hassaniefa62d92017-11-09 13:46:56 -0800800 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0f59a9a2019-09-27 10:24:31 -0700801 """Specific checks for SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
Amin Hassaniefa62d92017-11-09 13:46:56 -0800802 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800803
804 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800805 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700806 data_length: The length of the data blob associated with the operation.
807 total_dst_blocks: Total number of blocks in dst_extents.
808 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800809
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800810 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700811 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800812 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800813 # Check: data_{offset,length} present.
814 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700815 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800816
Sen Jiang771f6482018-04-04 17:59:10 -0700817 # Check: data_length is strictly smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800818 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700819 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800820 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700821 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800822 (op_name, data_length, total_dst_blocks, self.block_size,
823 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800824
Amin Hassaniefa62d92017-11-09 13:46:56 -0800825 # Check the existence of src_length and dst_length for legacy bsdiffs.
Amin Hassani0f59a9a2019-09-27 10:24:31 -0700826 if op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800827 if not op.HasField('src_length') or not op.HasField('dst_length'):
828 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
829 else:
830 if op.HasField('src_length') or op.HasField('dst_length'):
831 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
832
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800833 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
834 total_dst_blocks, op_name):
835 """Specific checks for SOURCE_COPY.
836
837 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800838 data_offset: The offset of a data blob for the operation.
839 total_src_blocks: Total number of blocks in src_extents.
840 total_dst_blocks: Total number of blocks in dst_extents.
841 op_name: Operation name for error reporting.
842
843 Raises:
844 error.PayloadError if any check fails.
845 """
846 # Check: No data_{offset,length}.
847 if data_offset is not None:
848 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
849
850 # Check: total_src_blocks == total_dst_blocks.
851 if total_src_blocks != total_dst_blocks:
852 raise error.PayloadError(
853 '%s: total src blocks (%d) != total dst blocks (%d).' %
854 (op_name, total_src_blocks, total_dst_blocks))
855
Sen Jiangd6122bb2015-12-11 10:27:04 -0800856 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800857 """Specific checks for SOURCE_* operations.
858
859 Args:
860 op: The operation object from the manifest.
861 total_src_blocks: Total number of blocks in src_extents.
862 op_name: Operation name for error reporting.
863
864 Raises:
865 error.PayloadError if any check fails.
866 """
867 # Check: total_src_blocks != 0.
868 if total_src_blocks == 0:
869 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
870
Sen Jiang92161a72016-06-28 16:09:38 -0700871 # Check: src_sha256_hash present in minor version >= 3.
872 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800873 raise error.PayloadError('%s: source hash missing.' % op_name)
874
Amin Hassani55c75412019-10-07 11:20:39 -0700875 def _CheckOperation(self, op, op_name, old_block_counters, new_block_counters,
876 old_usable_size, new_usable_size, prev_data_offset,
877 blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800878 """Checks a single update operation.
879
880 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700881 op: The operation object.
882 op_name: Operation name string for error reporting.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700883 old_block_counters: Arrays of block read counters.
884 new_block_counters: Arrays of block write counters.
885 old_usable_size: The overall usable size for src data in bytes.
886 new_usable_size: The overall usable size for dst data in bytes.
887 prev_data_offset: Offset of last used data bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700888 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800889
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800890 Returns:
891 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800892
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800893 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700894 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800895 """
896 # Check extents.
897 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700898 op.src_extents, old_usable_size, old_block_counters,
Amin Hassani55c75412019-10-07 11:20:39 -0700899 op_name + '.src_extents')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800900 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700901 op.dst_extents, new_usable_size, new_block_counters,
Amin Hassani55c75412019-10-07 11:20:39 -0700902 op_name + '.dst_extents')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800903
904 # Check: data_offset present <==> data_length present.
905 data_offset = self._CheckOptionalField(op, 'data_offset', None)
906 data_length = self._CheckOptionalField(op, 'data_length', None)
907 self._CheckPresentIff(data_offset, data_length, 'data_offset',
908 'data_length', op_name)
909
Gilad Arnoldcb638912013-06-24 04:57:11 -0700910 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800911 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700912 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800913
914 # Check {src,dst}_length, if present.
915 if op.HasField('src_length'):
916 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
917 if op.HasField('dst_length'):
918 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
919
920 if op.HasField('data_sha256_hash'):
921 blob_hash_counts['hashed'] += 1
922
Gilad Arnoldcb638912013-06-24 04:57:11 -0700923 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800924 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700925 raise error.PayloadError(
926 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800927 op_name)
928
Gilad Arnoldcb638912013-06-24 04:57:11 -0700929 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800930 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
931 data_length))
932 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700933 raise error.PayloadError(
934 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700935 (op_name, common.FormatSha256(op.data_sha256_hash),
936 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800937 elif data_offset is not None:
Amin Hassani55c75412019-10-07 11:20:39 -0700938 if self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800939 blob_hash_counts['unhashed'] += 1
940 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700941 raise error.PayloadError('%s: unhashed operation not allowed.' %
942 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800943
944 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700945 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800946 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700947 raise error.PayloadError(
948 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800949 (op_name, data_offset, prev_data_offset))
950
951 # Type-specific checks.
Amin Hassani55c75412019-10-07 11:20:39 -0700952 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ,
953 common.OpType.REPLACE_XZ):
Amin Hassani0de7f782017-12-07 12:13:03 -0800954 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700955 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
956 self._CheckZeroOperation(op, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700957 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800958 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
959 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800960 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700961 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800962 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700963 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani77d7cbc2018-02-07 16:21:33 -0800964 elif op.type == common.OpType.BROTLI_BSDIFF and self.minor_version >= 4:
965 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
966 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
967 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 5:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800968 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800969 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800970 else:
Allie Wood7cf9f132015-02-26 14:28:19 -0800971 raise error.PayloadError(
972 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700973 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800974 return data_length if data_length is not None else 0
975
Gilad Arnold382df5c2013-05-03 12:49:28 -0700976 def _SizeToNumBlocks(self, size):
977 """Returns the number of blocks needed to contain a given byte size."""
978 return (size + self.block_size - 1) / self.block_size
979
980 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800981 """Returns a freshly initialized array of block counters.
982
Gilad Arnoldcb638912013-06-24 04:57:11 -0700983 Note that the generated array is not portable as is due to byte-ordering
984 issues, hence it should not be serialized.
985
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800986 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700987 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800988
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800989 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -0700990 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800991 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800992 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700993 return array.array('H',
994 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800995
Gilad Arnold382df5c2013-05-03 12:49:28 -0700996 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -0700997 new_fs_size, old_usable_size, new_usable_size,
Amin Hassani55c75412019-10-07 11:20:39 -0700998 prev_data_offset):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800999 """Checks a sequence of update operations.
1000
1001 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001002 operations: The sequence of operations to check.
1003 report: The report object to add to.
1004 base_name: The name of the operation block.
1005 old_fs_size: The old filesystem size in bytes.
1006 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001007 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001008 new_usable_size: The overall usable size of the new partition in bytes.
1009 prev_data_offset: Offset of last used data bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001010
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001011 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001012 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001013
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001014 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001015 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001016 """
1017 # The total size of data blobs used by operations scanned thus far.
1018 total_data_used = 0
1019 # Counts of specific operation types.
1020 op_counts = {
1021 common.OpType.REPLACE: 0,
1022 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001023 common.OpType.REPLACE_XZ: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001024 common.OpType.ZERO: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001025 common.OpType.SOURCE_COPY: 0,
1026 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001027 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001028 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001029 }
1030 # Total blob sizes for each operation type.
1031 op_blob_totals = {
1032 common.OpType.REPLACE: 0,
1033 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001034 common.OpType.REPLACE_XZ: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001035 # SOURCE_COPY operations don't have blobs.
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 # Counts of hashed vs unhashed operations.
1041 blob_hash_counts = {
1042 'hashed': 0,
1043 'unhashed': 0,
1044 }
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001045
1046 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001047 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001048 if old_fs_size else None)
1049 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001050
1051 # Process and verify each operation.
1052 op_num = 0
1053 for op, op_name in common.OperationIter(operations, base_name):
1054 op_num += 1
1055
Gilad Arnoldcb638912013-06-24 04:57:11 -07001056 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001057 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001058 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001059 op_counts[op.type] += 1
1060
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001061 curr_data_used = self._CheckOperation(
Amin Hassani55c75412019-10-07 11:20:39 -07001062 op, op_name, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001063 old_usable_size, new_usable_size,
Amin Hassani55c75412019-10-07 11:20:39 -07001064 prev_data_offset + total_data_used, blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001065 if curr_data_used:
1066 op_blob_totals[op.type] += curr_data_used
1067 total_data_used += curr_data_used
1068
1069 # Report totals and breakdown statistics.
1070 report.AddField('total operations', op_num)
1071 report.AddField(
1072 None,
1073 histogram.Histogram.FromCountDict(op_counts,
1074 key_names=common.OpType.NAMES),
1075 indent=1)
1076 report.AddField('total blobs', sum(blob_hash_counts.values()))
1077 report.AddField(None,
1078 histogram.Histogram.FromCountDict(blob_hash_counts),
1079 indent=1)
1080 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1081 report.AddField(
1082 None,
1083 histogram.Histogram.FromCountDict(op_blob_totals,
1084 formatter=_AddHumanReadableSize,
1085 key_names=common.OpType.NAMES),
1086 indent=1)
1087
1088 # Report read/write histograms.
1089 if old_block_counters:
1090 report.AddField('block read hist',
1091 histogram.Histogram.FromKeyList(old_block_counters),
1092 linebreak=True, indent=1)
1093
Gilad Arnold382df5c2013-05-03 12:49:28 -07001094 new_write_hist = histogram.Histogram.FromKeyList(
1095 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1096 report.AddField('block write hist', new_write_hist, linebreak=True,
1097 indent=1)
1098
Gilad Arnoldcb638912013-06-24 04:57:11 -07001099 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001100 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001101 raise error.PayloadError(
1102 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001103 base_name)
1104
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001105 return total_data_used
1106
1107 def _CheckSignatures(self, report, pubkey_file_name):
1108 """Checks a payload's signature block."""
1109 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1110 sigs = update_metadata_pb2.Signatures()
1111 sigs.ParseFromString(sigs_raw)
1112 report.AddSection('signatures')
1113
Gilad Arnoldcb638912013-06-24 04:57:11 -07001114 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001115 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001116 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001117
Amin Hassani55c75412019-10-07 11:20:39 -07001118 # Check that we don't have the signature operation blob at the end (used to
1119 # be for major version 1).
1120 last_partition = self.payload.manifest.partitions[-1]
1121 if last_partition.operations:
1122 last_op = last_partition.operations[-1]
Amin Hassani8ea19572018-12-05 12:06:21 -08001123 # Check: signatures_{offset,size} must match the last (fake) operation.
Amin Hassani55c75412019-10-07 11:20:39 -07001124 if (last_op.type == common.OpType.REPLACE and
1125 last_op.data_offset == self.sigs_offset and
1126 last_op.data_length == self.sigs_size):
1127 raise error.PayloadError('It seems like the last operation is the '
1128 'signature blob. This is an invalid payload.')
1129
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001130
1131 # Compute the checksum of all data up to signature blob.
1132 # TODO(garnold) we're re-reading the whole data section into a string
1133 # just to compute the checksum; instead, we could do it incrementally as
1134 # we read the blobs one-by-one, under the assumption that we're reading
1135 # them in order (which currently holds). This should be reconsidered.
1136 payload_hasher = self.payload.manifest_hasher.copy()
1137 common.Read(self.payload.payload_file, self.sigs_offset,
1138 offset=self.payload.data_offset, hasher=payload_hasher)
1139
1140 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1141 sig_report = report.AddSubReport(sig_name)
1142
Gilad Arnoldcb638912013-06-24 04:57:11 -07001143 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001144 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1145 self._CheckMandatoryField(sig, 'data', None, sig_name)
1146 sig_report.AddField('data len', len(sig.data))
1147
Gilad Arnoldcb638912013-06-24 04:57:11 -07001148 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001149 if sig.version == 1:
1150 self._CheckSha256Signature(sig.data, pubkey_file_name,
1151 payload_hasher.digest(), sig_name)
1152 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001153 raise error.PayloadError('Unknown signature version (%d).' %
1154 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001155
Amin Hassania86b1082018-03-08 15:48:59 -08001156 def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0,
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001157 part_sizes=None, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001158 """Checker entry point, invoking all checks.
1159
1160 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001161 pubkey_file_name: Public key used for signature verification.
1162 metadata_sig_file: Metadata signature, if verification is desired.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001163 metadata_size: Metadata size, if verification is desired.
1164 part_sizes: Mapping of partition label to size in bytes (default: infer
1165 based on payload type and version or filesystem).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001166 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001167
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001168 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001169 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001170 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001171 if not pubkey_file_name:
1172 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1173
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001174 report = _PayloadReport()
1175
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001176 # Get payload file size.
1177 self.payload.payload_file.seek(0, 2)
1178 payload_file_size = self.payload.payload_file.tell()
1179 self.payload.ResetFile()
1180
1181 try:
Amin Hassania86b1082018-03-08 15:48:59 -08001182 # Check metadata_size (if provided).
Amin Hassani8ea19572018-12-05 12:06:21 -08001183 if metadata_size and self.payload.metadata_size != metadata_size:
Amin Hassania86b1082018-03-08 15:48:59 -08001184 raise error.PayloadError('Invalid payload metadata size in payload(%d) '
Amin Hassani8ea19572018-12-05 12:06:21 -08001185 'vs given(%d)' % (self.payload.metadata_size,
Amin Hassania86b1082018-03-08 15:48:59 -08001186 metadata_size))
1187
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001188 # Check metadata signature (if provided).
1189 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001190 metadata_sig = base64.b64decode(metadata_sig_file.read())
1191 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1192 self.payload.manifest_hasher.digest(),
1193 'metadata signature')
1194
Gilad Arnoldcb638912013-06-24 04:57:11 -07001195 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001196 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001197 # Check: Payload version is valid.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001198 if self.payload.header.version not in (1, 2):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001199 raise error.PayloadError('Unknown payload version (%d).' %
1200 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001201 report.AddField('version', self.payload.header.version)
1202 report.AddField('manifest len', self.payload.header.manifest_len)
1203
Gilad Arnoldcb638912013-06-24 04:57:11 -07001204 # Part 2: Check the manifest.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001205 self._CheckManifest(report, part_sizes)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001206 assert self.payload_type, 'payload type should be known by now'
1207
Amin Hassani55c75412019-10-07 11:20:39 -07001208 # Make sure deprecated values are not present in the payload.
1209 for field in ('install_operations', 'kernel_install_operations'):
1210 self._CheckRepeatedElemNotPresent(self.payload.manifest, field,
Tudor Brindus40506cd2018-06-18 20:18:17 -07001211 'manifest')
Amin Hassani55c75412019-10-07 11:20:39 -07001212 for field in ('old_kernel_info', 'old_rootfs_info',
1213 'new_kernel_info', 'new_rootfs_info'):
1214 self._CheckElemNotPresent(self.payload.manifest, field, 'manifest')
Tudor Brindus40506cd2018-06-18 20:18:17 -07001215
1216 total_blob_size = 0
Amin Hassani55c75412019-10-07 11:20:39 -07001217 for part, operations in ((p.partition_name, p.operations)
1218 for p in self.payload.manifest.partitions):
Tudor Brindus40506cd2018-06-18 20:18:17 -07001219 report.AddSection('%s operations' % part)
1220
1221 new_fs_usable_size = self.new_fs_sizes[part]
1222 old_fs_usable_size = self.old_fs_sizes[part]
1223
Xiaochu Liu6cf8e672019-03-14 16:15:42 -07001224 if part_sizes is not None and part_sizes.get(part, None):
Tudor Brindus40506cd2018-06-18 20:18:17 -07001225 new_fs_usable_size = old_fs_usable_size = part_sizes[part]
Tudor Brindus40506cd2018-06-18 20:18:17 -07001226
Amin Hassani0f59a9a2019-09-27 10:24:31 -07001227 # TODO(chromium:243559) only default to the filesystem size if no
1228 # explicit size provided *and* the partition size is not embedded in the
1229 # payload; see issue for more details.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001230 total_blob_size += self._CheckOperations(
1231 operations, report, '%s_install_operations' % part,
1232 self.old_fs_sizes[part], self.new_fs_sizes[part],
Amin Hassani55c75412019-10-07 11:20:39 -07001233 old_fs_usable_size, new_fs_usable_size, total_blob_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001234
Gilad Arnoldcb638912013-06-24 04:57:11 -07001235 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001236 used_payload_size = self.payload.data_offset + total_blob_size
Amin Hassani8ea19572018-12-05 12:06:21 -08001237 # Major versions 2 and higher have a signature at the end, so it should be
1238 # considered in the total size of the image.
Amin Hassani55c75412019-10-07 11:20:39 -07001239 if self.sigs_size:
Amin Hassani8ea19572018-12-05 12:06:21 -08001240 used_payload_size += self.sigs_size
1241
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001242 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001243 raise error.PayloadError(
1244 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001245 (used_payload_size, payload_file_size))
1246
Tudor Brindus40506cd2018-06-18 20:18:17 -07001247 # Part 4: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001248 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001249 self._CheckSignatures(report, pubkey_file_name)
1250
Tudor Brindus40506cd2018-06-18 20:18:17 -07001251 # Part 5: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001252 report.AddSection('summary')
1253 report.AddField('update type', self.payload_type)
1254
1255 report.Finalize()
1256 finally:
1257 if report_out_file:
1258 report.Dump(report_out_file)