1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Verifying the integrity of a Chrome OS update payload. 6 7This module is used internally by the main Payload class for verifying the 8integrity of an update payload. The interface for invoking the checks is as 9follows: 10 11 checker = PayloadChecker(payload) 12 checker.Run(...) 13""" 14 15from __future__ import print_function 16 17import array 18import base64 19import hashlib 20import itertools 21import os 22import subprocess 23 24import common 25import error 26import format_utils 27import histogram 28import update_metadata_pb2 29 30 31# 32# Constants. 33# 34 35_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents' 36_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block' 37_CHECK_PAYLOAD_SIG = 'payload-sig' 38CHECKS_TO_DISABLE = ( 39 _CHECK_DST_PSEUDO_EXTENTS, 40 _CHECK_MOVE_SAME_SRC_DST_BLOCK, 41 _CHECK_PAYLOAD_SIG, 42) 43 44_TYPE_FULL = 'full' 45_TYPE_DELTA = 'delta' 46 47_DEFAULT_BLOCK_SIZE = 4096 48 49_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem' 50_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__), 51 _DEFAULT_PUBKEY_BASE_NAME) 52 53# Supported minor version map to payload types allowed to be using them. 54_SUPPORTED_MINOR_VERSIONS = { 55 0: (_TYPE_FULL,), 56 1: (_TYPE_DELTA,), 57 2: (_TYPE_DELTA,), 58 3: (_TYPE_DELTA,), 59 4: (_TYPE_DELTA,), 60} 61 62_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024 63 64# 65# Helper functions. 66# 67 68def _IsPowerOfTwo(val): 69 """Returns True iff val is a power of two.""" 70 return val > 0 and (val & (val - 1)) == 0 71 72 73def _AddFormat(format_func, value): 74 """Adds a custom formatted representation to ordinary string representation. 75 76 Args: 77 format_func: A value formatter. 78 value: Value to be formatted and returned. 79 80 Returns: 81 A string 'x (y)' where x = str(value) and y = format_func(value). 82 """ 83 ret = str(value) 84 formatted_str = format_func(value) 85 if formatted_str: 86 ret += ' (%s)' % formatted_str 87 return ret 88 89 90def _AddHumanReadableSize(size): 91 """Adds a human readable representation to a byte size value.""" 92 return _AddFormat(format_utils.BytesToHumanReadable, size) 93 94 95# 96# Payload report generator. 97# 98 99class _PayloadReport(object): 100 """A payload report generator. 101 102 A report is essentially a sequence of nodes, which represent data points. It 103 is initialized to have a "global", untitled section. A node may be a 104 sub-report itself. 105 """ 106 107 # Report nodes: Field, sub-report, section. 108 class Node(object): 109 """A report node interface.""" 110 111 @staticmethod 112 def _Indent(indent, line): 113 """Indents a line by a given indentation amount. 114 115 Args: 116 indent: The indentation amount. 117 line: The line content (string). 118 119 Returns: 120 The properly indented line (string). 121 """ 122 return '%*s%s' % (indent, '', line) 123 124 def GenerateLines(self, base_indent, sub_indent, curr_section): 125 """Generates the report lines for this node. 126 127 Args: 128 base_indent: Base indentation for each line. 129 sub_indent: Additional indentation for sub-nodes. 130 curr_section: The current report section object. 131 132 Returns: 133 A pair consisting of a list of properly indented report lines and a new 134 current section object. 135 """ 136 raise NotImplementedError 137 138 class FieldNode(Node): 139 """A field report node, representing a (name, value) pair.""" 140 141 def __init__(self, name, value, linebreak, indent): 142 super(_PayloadReport.FieldNode, self).__init__() 143 self.name = name 144 self.value = value 145 self.linebreak = linebreak 146 self.indent = indent 147 148 def GenerateLines(self, base_indent, sub_indent, curr_section): 149 """Generates a properly formatted 'name : value' entry.""" 150 report_output = '' 151 if self.name: 152 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :' 153 value_lines = str(self.value).splitlines() 154 if self.linebreak and self.name: 155 report_output += '\n' + '\n'.join( 156 ['%*s%s' % (self.indent, '', line) for line in value_lines]) 157 else: 158 if self.name: 159 report_output += ' ' 160 report_output += '%*s' % (self.indent, '') 161 cont_line_indent = len(report_output) 162 indented_value_lines = [value_lines[0]] 163 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line) 164 for line in value_lines[1:]]) 165 report_output += '\n'.join(indented_value_lines) 166 167 report_lines = [self._Indent(base_indent, line + '\n') 168 for line in report_output.split('\n')] 169 return report_lines, curr_section 170 171 class SubReportNode(Node): 172 """A sub-report node, representing a nested report.""" 173 174 def __init__(self, title, report): 175 super(_PayloadReport.SubReportNode, self).__init__() 176 self.title = title 177 self.report = report 178 179 def GenerateLines(self, base_indent, sub_indent, curr_section): 180 """Recurse with indentation.""" 181 report_lines = [self._Indent(base_indent, self.title + ' =>\n')] 182 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent, 183 sub_indent)) 184 return report_lines, curr_section 185 186 class SectionNode(Node): 187 """A section header node.""" 188 189 def __init__(self, title=None): 190 super(_PayloadReport.SectionNode, self).__init__() 191 self.title = title 192 self.max_field_name_len = 0 193 194 def GenerateLines(self, base_indent, sub_indent, curr_section): 195 """Dump a title line, return self as the (new) current section.""" 196 report_lines = [] 197 if self.title: 198 report_lines.append(self._Indent(base_indent, 199 '=== %s ===\n' % self.title)) 200 return report_lines, self 201 202 def __init__(self): 203 self.report = [] 204 self.last_section = self.global_section = self.SectionNode() 205 self.is_finalized = False 206 207 def GenerateLines(self, base_indent, sub_indent): 208 """Generates the lines in the report, properly indented. 209 210 Args: 211 base_indent: The indentation used for root-level report lines. 212 sub_indent: The indentation offset used for sub-reports. 213 214 Returns: 215 A list of indented report lines. 216 """ 217 report_lines = [] 218 curr_section = self.global_section 219 for node in self.report: 220 node_report_lines, curr_section = node.GenerateLines( 221 base_indent, sub_indent, curr_section) 222 report_lines.extend(node_report_lines) 223 224 return report_lines 225 226 def Dump(self, out_file, base_indent=0, sub_indent=2): 227 """Dumps the report to a file. 228 229 Args: 230 out_file: File object to output the content to. 231 base_indent: Base indentation for report lines. 232 sub_indent: Added indentation for sub-reports. 233 """ 234 report_lines = self.GenerateLines(base_indent, sub_indent) 235 if report_lines and not self.is_finalized: 236 report_lines.append('(incomplete report)\n') 237 238 for line in report_lines: 239 out_file.write(line) 240 241 def AddField(self, name, value, linebreak=False, indent=0): 242 """Adds a field/value pair to the payload report. 243 244 Args: 245 name: The field's name. 246 value: The field's value. 247 linebreak: Whether the value should be printed on a new line. 248 indent: Amount of extra indent for each line of the value. 249 """ 250 assert not self.is_finalized 251 if name and self.last_section.max_field_name_len < len(name): 252 self.last_section.max_field_name_len = len(name) 253 self.report.append(self.FieldNode(name, value, linebreak, indent)) 254 255 def AddSubReport(self, title): 256 """Adds and returns a sub-report with a title.""" 257 assert not self.is_finalized 258 sub_report = self.SubReportNode(title, type(self)()) 259 self.report.append(sub_report) 260 return sub_report.report 261 262 def AddSection(self, title): 263 """Adds a new section title.""" 264 assert not self.is_finalized 265 self.last_section = self.SectionNode(title) 266 self.report.append(self.last_section) 267 268 def Finalize(self): 269 """Seals the report, marking it as complete.""" 270 self.is_finalized = True 271 272 273# 274# Payload verification. 275# 276 277class PayloadChecker(object): 278 """Checking the integrity of an update payload. 279 280 This is a short-lived object whose purpose is to isolate the logic used for 281 verifying the integrity of an update payload. 282 """ 283 284 def __init__(self, payload, assert_type=None, block_size=0, 285 allow_unhashed=False, disabled_tests=()): 286 """Initialize the checker. 287 288 Args: 289 payload: The payload object to check. 290 assert_type: Assert that payload is either 'full' or 'delta' (optional). 291 block_size: Expected filesystem / payload block size (optional). 292 allow_unhashed: Allow operations with unhashed data blobs. 293 disabled_tests: Sequence of tests to disable. 294 """ 295 if not payload.is_init: 296 raise ValueError('Uninitialized update payload.') 297 298 # Set checker configuration. 299 self.payload = payload 300 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE 301 if not _IsPowerOfTwo(self.block_size): 302 raise error.PayloadError( 303 'Expected block (%d) size is not a power of two.' % self.block_size) 304 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA): 305 raise error.PayloadError('Invalid assert_type value (%r).' % 306 assert_type) 307 self.payload_type = assert_type 308 self.allow_unhashed = allow_unhashed 309 310 # Disable specific tests. 311 self.check_dst_pseudo_extents = ( 312 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests) 313 self.check_move_same_src_dst_block = ( 314 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests) 315 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests 316 317 # Reset state; these will be assigned when the manifest is checked. 318 self.sigs_offset = 0 319 self.sigs_size = 0 320 self.old_rootfs_fs_size = 0 321 self.old_kernel_fs_size = 0 322 self.new_rootfs_fs_size = 0 323 self.new_kernel_fs_size = 0 324 self.minor_version = None 325 326 @staticmethod 327 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str, 328 msg_name=None, linebreak=False, indent=0): 329 """Adds an element from a protobuf message to the payload report. 330 331 Checks to see whether a message contains a given element, and if so adds 332 the element value to the provided report. A missing mandatory element 333 causes an exception to be raised. 334 335 Args: 336 msg: The message containing the element. 337 name: The name of the element. 338 report: A report object to add the element name/value to. 339 is_mandatory: Whether or not this element must be present. 340 is_submsg: Whether this element is itself a message. 341 convert: A function for converting the element value for reporting. 342 msg_name: The name of the message object (for error reporting). 343 linebreak: Whether the value report should induce a line break. 344 indent: Amount of indent used for reporting the value. 345 346 Returns: 347 A pair consisting of the element value and the generated sub-report for 348 it (if the element is a sub-message, None otherwise). If the element is 349 missing, returns (None, None). 350 351 Raises: 352 error.PayloadError if a mandatory element is missing. 353 """ 354 if not msg.HasField(name): 355 if is_mandatory: 356 raise error.PayloadError('%smissing mandatory %s %r.' % 357 (msg_name + ' ' if msg_name else '', 358 'sub-message' if is_submsg else 'field', 359 name)) 360 return None, None 361 362 value = getattr(msg, name) 363 if is_submsg: 364 return value, report and report.AddSubReport(name) 365 else: 366 if report: 367 report.AddField(name, convert(value), linebreak=linebreak, 368 indent=indent) 369 return value, None 370 371 @staticmethod 372 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str, 373 linebreak=False, indent=0): 374 """Adds a mandatory field; returning first component from _CheckElem.""" 375 return PayloadChecker._CheckElem(msg, field_name, report, True, False, 376 convert=convert, msg_name=msg_name, 377 linebreak=linebreak, indent=indent)[0] 378 379 @staticmethod 380 def _CheckOptionalField(msg, field_name, report, convert=str, 381 linebreak=False, indent=0): 382 """Adds an optional field; returning first component from _CheckElem.""" 383 return PayloadChecker._CheckElem(msg, field_name, report, False, False, 384 convert=convert, linebreak=linebreak, 385 indent=indent)[0] 386 387 @staticmethod 388 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name): 389 """Adds a mandatory sub-message; wrapper for _CheckElem.""" 390 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True, 391 msg_name) 392 393 @staticmethod 394 def _CheckOptionalSubMsg(msg, submsg_name, report): 395 """Adds an optional sub-message; wrapper for _CheckElem.""" 396 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True) 397 398 @staticmethod 399 def _CheckPresentIff(val1, val2, name1, name2, obj_name): 400 """Checks that val1 is None iff val2 is None. 401 402 Args: 403 val1: first value to be compared. 404 val2: second value to be compared. 405 name1: name of object holding the first value. 406 name2: name of object holding the second value. 407 obj_name: Name of the object containing these values. 408 409 Raises: 410 error.PayloadError if assertion does not hold. 411 """ 412 if None in (val1, val2) and val1 is not val2: 413 present, missing = (name1, name2) if val2 is None else (name2, name1) 414 raise error.PayloadError('%r present without %r%s.' % 415 (present, missing, 416 ' in ' + obj_name if obj_name else '')) 417 418 @staticmethod 419 def _Run(cmd, send_data=None): 420 """Runs a subprocess, returns its output. 421 422 Args: 423 cmd: Sequence of command-line argument for invoking the subprocess. 424 send_data: Data to feed to the process via its stdin. 425 426 Returns: 427 A tuple containing the stdout and stderr output of the process. 428 """ 429 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE, 430 stdout=subprocess.PIPE) 431 try: 432 result = run_process.communicate(input=send_data) 433 finally: 434 exit_code = run_process.wait() 435 436 if exit_code: 437 raise RuntimeError('Subprocess %r failed with code %r.' % 438 (cmd, exit_code)) 439 440 return result 441 442 @staticmethod 443 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name): 444 """Verifies an actual hash against a signed one. 445 446 Args: 447 sig_data: The raw signature data. 448 pubkey_file_name: Public key used for verifying signature. 449 actual_hash: The actual hash digest. 450 sig_name: Signature name for error reporting. 451 452 Raises: 453 error.PayloadError if signature could not be verified. 454 """ 455 if len(sig_data) != 256: 456 raise error.PayloadError( 457 '%s: signature size (%d) not as expected (256).' % 458 (sig_name, len(sig_data))) 459 signed_data, _ = PayloadChecker._Run( 460 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name], 461 send_data=sig_data) 462 463 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32: 464 raise error.PayloadError('%s: unexpected signed data length (%d).' % 465 (sig_name, len(signed_data))) 466 467 if not signed_data.startswith(common.SIG_ASN1_HEADER): 468 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' % 469 sig_name) 470 471 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):] 472 if signed_hash != actual_hash: 473 raise error.PayloadError( 474 '%s: signed hash (%s) different from actual (%s).' % 475 (sig_name, common.FormatSha256(signed_hash), 476 common.FormatSha256(actual_hash))) 477 478 @staticmethod 479 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name, 480 block_name=None): 481 """Checks that a given length fits given block space. 482 483 This ensures that the number of blocks allocated is appropriate for the 484 length of the data residing in these blocks. 485 486 Args: 487 length: The actual length of the data. 488 num_blocks: The number of blocks allocated for it. 489 block_size: The size of each block in bytes. 490 length_name: Name of length (used for error reporting). 491 block_name: Name of block (used for error reporting). 492 493 Raises: 494 error.PayloadError if the aforementioned invariant is not satisfied. 495 """ 496 # Check: length <= num_blocks * block_size. 497 if length > num_blocks * block_size: 498 raise error.PayloadError( 499 '%s (%d) > num %sblocks (%d) * block_size (%d).' % 500 (length_name, length, block_name or '', num_blocks, block_size)) 501 502 # Check: length > (num_blocks - 1) * block_size. 503 if length <= (num_blocks - 1) * block_size: 504 raise error.PayloadError( 505 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' % 506 (length_name, length, block_name or '', num_blocks - 1, block_size)) 507 508 def _CheckManifestMinorVersion(self, report): 509 """Checks the payload manifest minor_version field. 510 511 Args: 512 report: The report object to add to. 513 514 Raises: 515 error.PayloadError if any of the checks fail. 516 """ 517 self.minor_version = self._CheckOptionalField(self.payload.manifest, 518 'minor_version', report) 519 if self.minor_version in _SUPPORTED_MINOR_VERSIONS: 520 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]: 521 raise error.PayloadError( 522 'Minor version %d not compatible with payload type %s.' % 523 (self.minor_version, self.payload_type)) 524 elif self.minor_version is None: 525 raise error.PayloadError('Minor version is not set.') 526 else: 527 raise error.PayloadError('Unsupported minor version: %d' % 528 self.minor_version) 529 530 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0): 531 """Checks the payload manifest. 532 533 Args: 534 report: A report object to add to. 535 rootfs_part_size: Size of the rootfs partition in bytes. 536 kernel_part_size: Size of the kernel partition in bytes. 537 538 Returns: 539 A tuple consisting of the partition block size used during the update 540 (integer), the signatures block offset and size. 541 542 Raises: 543 error.PayloadError if any of the checks fail. 544 """ 545 manifest = self.payload.manifest 546 report.AddSection('manifest') 547 548 # Check: block_size must exist and match the expected value. 549 actual_block_size = self._CheckMandatoryField(manifest, 'block_size', 550 report, 'manifest') 551 if actual_block_size != self.block_size: 552 raise error.PayloadError('Block_size (%d) not as expected (%d).' % 553 (actual_block_size, self.block_size)) 554 555 # Check: signatures_offset <==> signatures_size. 556 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset', 557 report) 558 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size', 559 report) 560 self._CheckPresentIff(self.sigs_offset, self.sigs_size, 561 'signatures_offset', 'signatures_size', 'manifest') 562 563 # Check: old_kernel_info <==> old_rootfs_info. 564 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest, 565 'old_kernel_info', report) 566 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest, 567 'old_rootfs_info', report) 568 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info', 569 'old_rootfs_info', 'manifest') 570 if oki_msg: # equivalently, ori_msg 571 # Assert/mark delta payload. 572 if self.payload_type == _TYPE_FULL: 573 raise error.PayloadError( 574 'Apparent full payload contains old_{kernel,rootfs}_info.') 575 self.payload_type = _TYPE_DELTA 576 577 # Check: {size, hash} present in old_{kernel,rootfs}_info. 578 self.old_kernel_fs_size = self._CheckMandatoryField( 579 oki_msg, 'size', oki_report, 'old_kernel_info') 580 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info', 581 convert=common.FormatSha256) 582 self.old_rootfs_fs_size = self._CheckMandatoryField( 583 ori_msg, 'size', ori_report, 'old_rootfs_info') 584 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info', 585 convert=common.FormatSha256) 586 587 # Check: old_{kernel,rootfs} size must fit in respective partition. 588 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size: 589 raise error.PayloadError( 590 'Old kernel content (%d) exceed partition size (%d).' % 591 (self.old_kernel_fs_size, kernel_part_size)) 592 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size: 593 raise error.PayloadError( 594 'Old rootfs content (%d) exceed partition size (%d).' % 595 (self.old_rootfs_fs_size, rootfs_part_size)) 596 else: 597 # Assert/mark full payload. 598 if self.payload_type == _TYPE_DELTA: 599 raise error.PayloadError( 600 'Apparent delta payload missing old_{kernel,rootfs}_info.') 601 self.payload_type = _TYPE_FULL 602 603 # Check: new_kernel_info present; contains {size, hash}. 604 nki_msg, nki_report = self._CheckMandatorySubMsg( 605 manifest, 'new_kernel_info', report, 'manifest') 606 self.new_kernel_fs_size = self._CheckMandatoryField( 607 nki_msg, 'size', nki_report, 'new_kernel_info') 608 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info', 609 convert=common.FormatSha256) 610 611 # Check: new_rootfs_info present; contains {size, hash}. 612 nri_msg, nri_report = self._CheckMandatorySubMsg( 613 manifest, 'new_rootfs_info', report, 'manifest') 614 self.new_rootfs_fs_size = self._CheckMandatoryField( 615 nri_msg, 'size', nri_report, 'new_rootfs_info') 616 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info', 617 convert=common.FormatSha256) 618 619 # Check: new_{kernel,rootfs} size must fit in respective partition. 620 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size: 621 raise error.PayloadError( 622 'New kernel content (%d) exceed partition size (%d).' % 623 (self.new_kernel_fs_size, kernel_part_size)) 624 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size: 625 raise error.PayloadError( 626 'New rootfs content (%d) exceed partition size (%d).' % 627 (self.new_rootfs_fs_size, rootfs_part_size)) 628 629 # Check: minor_version makes sense for the payload type. This check should 630 # run after the payload type has been set. 631 self._CheckManifestMinorVersion(report) 632 633 def _CheckLength(self, length, total_blocks, op_name, length_name): 634 """Checks whether a length matches the space designated in extents. 635 636 Args: 637 length: The total length of the data. 638 total_blocks: The total number of blocks in extents. 639 op_name: Operation name (for error reporting). 640 length_name: Length name (for error reporting). 641 642 Raises: 643 error.PayloadError is there a problem with the length. 644 """ 645 # Check: length is non-zero. 646 if length == 0: 647 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name)) 648 649 # Check that length matches number of blocks. 650 self._CheckBlocksFitLength(length, total_blocks, self.block_size, 651 '%s: %s' % (op_name, length_name)) 652 653 def _CheckExtents(self, extents, usable_size, block_counters, name, 654 allow_pseudo=False, allow_signature=False): 655 """Checks a sequence of extents. 656 657 Args: 658 extents: The sequence of extents to check. 659 usable_size: The usable size of the partition to which the extents apply. 660 block_counters: Array of counters corresponding to the number of blocks. 661 name: The name of the extent block. 662 allow_pseudo: Whether or not pseudo block numbers are allowed. 663 allow_signature: Whether or not the extents are used for a signature. 664 665 Returns: 666 The total number of blocks in the extents. 667 668 Raises: 669 error.PayloadError if any of the entailed checks fails. 670 """ 671 total_num_blocks = 0 672 for ex, ex_name in common.ExtentIter(extents, name): 673 # Check: Mandatory fields. 674 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block', 675 None, ex_name) 676 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None, 677 ex_name) 678 end_block = start_block + num_blocks 679 680 # Check: num_blocks > 0. 681 if num_blocks == 0: 682 raise error.PayloadError('%s: extent length is zero.' % ex_name) 683 684 if start_block != common.PSEUDO_EXTENT_MARKER: 685 # Check: Make sure we're within the partition limit. 686 if usable_size and end_block * self.block_size > usable_size: 687 raise error.PayloadError( 688 '%s: extent (%s) exceeds usable partition size (%d).' % 689 (ex_name, common.FormatExtent(ex, self.block_size), usable_size)) 690 691 # Record block usage. 692 for i in xrange(start_block, end_block): 693 block_counters[i] += 1 694 elif not (allow_pseudo or (allow_signature and len(extents) == 1)): 695 # Pseudo-extents must be allowed explicitly, or otherwise be part of a 696 # signature operation (in which case there has to be exactly one). 697 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name) 698 699 total_num_blocks += num_blocks 700 701 return total_num_blocks 702 703 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name): 704 """Specific checks for REPLACE/REPLACE_BZ operations. 705 706 Args: 707 op: The operation object from the manifest. 708 data_length: The length of the data blob associated with the operation. 709 total_dst_blocks: Total number of blocks in dst_extents. 710 op_name: Operation name for error reporting. 711 712 Raises: 713 error.PayloadError if any check fails. 714 """ 715 # Check: Does not contain src extents. 716 if op.src_extents: 717 raise error.PayloadError('%s: contains src_extents.' % op_name) 718 719 # Check: Contains data. 720 if data_length is None: 721 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name) 722 723 if op.type == common.OpType.REPLACE: 724 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks, 725 self.block_size, 726 op_name + '.data_length', 'dst') 727 else: 728 # Check: data_length must be smaller than the alotted dst blocks. 729 if data_length >= total_dst_blocks * self.block_size: 730 raise error.PayloadError( 731 '%s: data_length (%d) must be less than allotted dst block ' 732 'space (%d * %d).' % 733 (op_name, data_length, total_dst_blocks, self.block_size)) 734 735 def _CheckMoveOperation(self, op, data_offset, total_src_blocks, 736 total_dst_blocks, op_name): 737 """Specific checks for MOVE operations. 738 739 Args: 740 op: The operation object from the manifest. 741 data_offset: The offset of a data blob for the operation. 742 total_src_blocks: Total number of blocks in src_extents. 743 total_dst_blocks: Total number of blocks in dst_extents. 744 op_name: Operation name for error reporting. 745 746 Raises: 747 error.PayloadError if any check fails. 748 """ 749 # Check: No data_{offset,length}. 750 if data_offset is not None: 751 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name) 752 753 # Check: total_src_blocks == total_dst_blocks. 754 if total_src_blocks != total_dst_blocks: 755 raise error.PayloadError( 756 '%s: total src blocks (%d) != total dst blocks (%d).' % 757 (op_name, total_src_blocks, total_dst_blocks)) 758 759 # Check: For all i, i-th src block index != i-th dst block index. 760 i = 0 761 src_extent_iter = iter(op.src_extents) 762 dst_extent_iter = iter(op.dst_extents) 763 src_extent = dst_extent = None 764 src_idx = src_num = dst_idx = dst_num = 0 765 while i < total_src_blocks: 766 # Get the next source extent, if needed. 767 if not src_extent: 768 try: 769 src_extent = src_extent_iter.next() 770 except StopIteration: 771 raise error.PayloadError('%s: ran out of src extents (%d/%d).' % 772 (op_name, i, total_src_blocks)) 773 src_idx = src_extent.start_block 774 src_num = src_extent.num_blocks 775 776 # Get the next dest extent, if needed. 777 if not dst_extent: 778 try: 779 dst_extent = dst_extent_iter.next() 780 except StopIteration: 781 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' % 782 (op_name, i, total_dst_blocks)) 783 dst_idx = dst_extent.start_block 784 dst_num = dst_extent.num_blocks 785 786 # Check: start block is not 0. See crbug/480751; there are still versions 787 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll, 788 # so we need to fail payloads that try to MOVE to/from block 0. 789 if src_idx == 0 or dst_idx == 0: 790 raise error.PayloadError( 791 '%s: MOVE operation cannot have extent with start block 0' % 792 op_name) 793 794 if self.check_move_same_src_dst_block and src_idx == dst_idx: 795 raise error.PayloadError( 796 '%s: src/dst block number %d is the same (%d).' % 797 (op_name, i, src_idx)) 798 799 advance = min(src_num, dst_num) 800 i += advance 801 802 src_idx += advance 803 src_num -= advance 804 if src_num == 0: 805 src_extent = None 806 807 dst_idx += advance 808 dst_num -= advance 809 if dst_num == 0: 810 dst_extent = None 811 812 # Make sure we've exhausted all src/dst extents. 813 if src_extent: 814 raise error.PayloadError('%s: excess src blocks.' % op_name) 815 if dst_extent: 816 raise error.PayloadError('%s: excess dst blocks.' % op_name) 817 818 def _CheckAnyDiffOperation(self, data_length, total_dst_blocks, op_name): 819 """Specific checks for BSDIFF, SOURCE_BSDIFF and IMGDIFF operations. 820 821 Args: 822 data_length: The length of the data blob associated with the operation. 823 total_dst_blocks: Total number of blocks in dst_extents. 824 op_name: Operation name for error reporting. 825 826 Raises: 827 error.PayloadError if any check fails. 828 """ 829 # Check: data_{offset,length} present. 830 if data_length is None: 831 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name) 832 833 # Check: data_length is strictly smaller than the alotted dst blocks. 834 if data_length >= total_dst_blocks * self.block_size: 835 raise error.PayloadError( 836 '%s: data_length (%d) must be smaller than allotted dst space ' 837 '(%d * %d = %d).' % 838 (op_name, data_length, total_dst_blocks, self.block_size, 839 total_dst_blocks * self.block_size)) 840 841 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks, 842 total_dst_blocks, op_name): 843 """Specific checks for SOURCE_COPY. 844 845 Args: 846 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 864 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name): 865 """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 879 # Check: src_sha256_hash present in minor version >= 3. 880 if self.minor_version >= 3 and op.src_sha256_hash is None: 881 raise error.PayloadError('%s: source hash missing.' % op_name) 882 883 def _CheckOperation(self, op, op_name, is_last, old_block_counters, 884 new_block_counters, old_usable_size, new_usable_size, 885 prev_data_offset, allow_signature, blob_hash_counts): 886 """Checks a single update operation. 887 888 Args: 889 op: The operation object. 890 op_name: Operation name string for error reporting. 891 is_last: Whether this is the last operation in the sequence. 892 old_block_counters: Arrays of block read counters. 893 new_block_counters: Arrays of block write counters. 894 old_usable_size: The overall usable size for src data in bytes. 895 new_usable_size: The overall usable size for dst data in bytes. 896 prev_data_offset: Offset of last used data bytes. 897 allow_signature: Whether this may be a signature operation. 898 blob_hash_counts: Counters for hashed/unhashed blobs. 899 900 Returns: 901 The amount of data blob associated with the operation. 902 903 Raises: 904 error.PayloadError if any check has failed. 905 """ 906 # Check extents. 907 total_src_blocks = self._CheckExtents( 908 op.src_extents, old_usable_size, old_block_counters, 909 op_name + '.src_extents', allow_pseudo=True) 910 allow_signature_in_extents = (allow_signature and is_last and 911 op.type == common.OpType.REPLACE) 912 total_dst_blocks = self._CheckExtents( 913 op.dst_extents, new_usable_size, new_block_counters, 914 op_name + '.dst_extents', 915 allow_pseudo=(not self.check_dst_pseudo_extents), 916 allow_signature=allow_signature_in_extents) 917 918 # Check: data_offset present <==> data_length present. 919 data_offset = self._CheckOptionalField(op, 'data_offset', None) 920 data_length = self._CheckOptionalField(op, 'data_length', None) 921 self._CheckPresentIff(data_offset, data_length, 'data_offset', 922 'data_length', op_name) 923 924 # Check: At least one dst_extent. 925 if not op.dst_extents: 926 raise error.PayloadError('%s: dst_extents is empty.' % op_name) 927 928 # Check {src,dst}_length, if present. 929 if op.HasField('src_length'): 930 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length') 931 if op.HasField('dst_length'): 932 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length') 933 934 if op.HasField('data_sha256_hash'): 935 blob_hash_counts['hashed'] += 1 936 937 # Check: Operation carries data. 938 if data_offset is None: 939 raise error.PayloadError( 940 '%s: data_sha256_hash present but no data_{offset,length}.' % 941 op_name) 942 943 # Check: Hash verifies correctly. 944 # pylint cannot find the method in hashlib, for some reason. 945 # pylint: disable=E1101 946 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset, 947 data_length)) 948 if op.data_sha256_hash != actual_hash.digest(): 949 raise error.PayloadError( 950 '%s: data_sha256_hash (%s) does not match actual hash (%s).' % 951 (op_name, common.FormatSha256(op.data_sha256_hash), 952 common.FormatSha256(actual_hash.digest()))) 953 elif data_offset is not None: 954 if allow_signature_in_extents: 955 blob_hash_counts['signature'] += 1 956 elif self.allow_unhashed: 957 blob_hash_counts['unhashed'] += 1 958 else: 959 raise error.PayloadError('%s: unhashed operation not allowed.' % 960 op_name) 961 962 if data_offset is not None: 963 # Check: Contiguous use of data section. 964 if data_offset != prev_data_offset: 965 raise error.PayloadError( 966 '%s: data offset (%d) not matching amount used so far (%d).' % 967 (op_name, data_offset, prev_data_offset)) 968 969 # Type-specific checks. 970 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ): 971 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name) 972 elif op.type == common.OpType.MOVE and self.minor_version == 1: 973 self._CheckMoveOperation(op, data_offset, total_src_blocks, 974 total_dst_blocks, op_name) 975 elif op.type == common.OpType.BSDIFF and self.minor_version == 1: 976 self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name) 977 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2: 978 self._CheckSourceCopyOperation(data_offset, total_src_blocks, 979 total_dst_blocks, op_name) 980 self._CheckAnySourceOperation(op, total_src_blocks, op_name) 981 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2: 982 self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name) 983 self._CheckAnySourceOperation(op, total_src_blocks, op_name) 984 elif op.type == common.OpType.IMGDIFF and self.minor_version >= 4: 985 self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name) 986 self._CheckAnySourceOperation(op, total_src_blocks, op_name) 987 else: 988 raise error.PayloadError( 989 'Operation %s (type %d) not allowed in minor version %d' % 990 (op_name, op.type, self.minor_version)) 991 return data_length if data_length is not None else 0 992 993 def _SizeToNumBlocks(self, size): 994 """Returns the number of blocks needed to contain a given byte size.""" 995 return (size + self.block_size - 1) / self.block_size 996 997 def _AllocBlockCounters(self, total_size): 998 """Returns a freshly initialized array of block counters. 999 1000 Note that the generated array is not portable as is due to byte-ordering 1001 issues, hence it should not be serialized. 1002 1003 Args: 1004 total_size: The total block size in bytes. 1005 1006 Returns: 1007 An array of unsigned short elements initialized to zero, one for each of 1008 the blocks necessary for containing the partition. 1009 """ 1010 return array.array('H', 1011 itertools.repeat(0, self._SizeToNumBlocks(total_size))) 1012 1013 def _CheckOperations(self, operations, report, base_name, old_fs_size, 1014 new_fs_size, new_usable_size, prev_data_offset, 1015 allow_signature): 1016 """Checks a sequence of update operations. 1017 1018 Args: 1019 operations: The sequence of operations to check. 1020 report: The report object to add to. 1021 base_name: The name of the operation block. 1022 old_fs_size: The old filesystem size in bytes. 1023 new_fs_size: The new filesystem size in bytes. 1024 new_usable_size: The overall usable size of the new partition in bytes. 1025 prev_data_offset: Offset of last used data bytes. 1026 allow_signature: Whether this sequence may contain signature operations. 1027 1028 Returns: 1029 The total data blob size used. 1030 1031 Raises: 1032 error.PayloadError if any of the checks fails. 1033 """ 1034 # The total size of data blobs used by operations scanned thus far. 1035 total_data_used = 0 1036 # Counts of specific operation types. 1037 op_counts = { 1038 common.OpType.REPLACE: 0, 1039 common.OpType.REPLACE_BZ: 0, 1040 common.OpType.MOVE: 0, 1041 common.OpType.BSDIFF: 0, 1042 common.OpType.SOURCE_COPY: 0, 1043 common.OpType.SOURCE_BSDIFF: 0, 1044 common.OpType.IMGDIFF: 0, 1045 } 1046 # Total blob sizes for each operation type. 1047 op_blob_totals = { 1048 common.OpType.REPLACE: 0, 1049 common.OpType.REPLACE_BZ: 0, 1050 # MOVE operations don't have blobs. 1051 common.OpType.BSDIFF: 0, 1052 # SOURCE_COPY operations don't have blobs. 1053 common.OpType.SOURCE_BSDIFF: 0, 1054 common.OpType.IMGDIFF: 0, 1055 } 1056 # Counts of hashed vs unhashed operations. 1057 blob_hash_counts = { 1058 'hashed': 0, 1059 'unhashed': 0, 1060 } 1061 if allow_signature: 1062 blob_hash_counts['signature'] = 0 1063 1064 # Allocate old and new block counters. 1065 old_block_counters = (self._AllocBlockCounters(new_usable_size) 1066 if old_fs_size else None) 1067 new_block_counters = self._AllocBlockCounters(new_usable_size) 1068 1069 # Process and verify each operation. 1070 op_num = 0 1071 for op, op_name in common.OperationIter(operations, base_name): 1072 op_num += 1 1073 1074 # Check: Type is valid. 1075 if op.type not in op_counts.keys(): 1076 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type)) 1077 op_counts[op.type] += 1 1078 1079 is_last = op_num == len(operations) 1080 curr_data_used = self._CheckOperation( 1081 op, op_name, is_last, old_block_counters, new_block_counters, 1082 new_usable_size if old_fs_size else 0, new_usable_size, 1083 prev_data_offset + total_data_used, allow_signature, 1084 blob_hash_counts) 1085 if curr_data_used: 1086 op_blob_totals[op.type] += curr_data_used 1087 total_data_used += curr_data_used 1088 1089 # Report totals and breakdown statistics. 1090 report.AddField('total operations', op_num) 1091 report.AddField( 1092 None, 1093 histogram.Histogram.FromCountDict(op_counts, 1094 key_names=common.OpType.NAMES), 1095 indent=1) 1096 report.AddField('total blobs', sum(blob_hash_counts.values())) 1097 report.AddField(None, 1098 histogram.Histogram.FromCountDict(blob_hash_counts), 1099 indent=1) 1100 report.AddField('total blob size', _AddHumanReadableSize(total_data_used)) 1101 report.AddField( 1102 None, 1103 histogram.Histogram.FromCountDict(op_blob_totals, 1104 formatter=_AddHumanReadableSize, 1105 key_names=common.OpType.NAMES), 1106 indent=1) 1107 1108 # Report read/write histograms. 1109 if old_block_counters: 1110 report.AddField('block read hist', 1111 histogram.Histogram.FromKeyList(old_block_counters), 1112 linebreak=True, indent=1) 1113 1114 new_write_hist = histogram.Histogram.FromKeyList( 1115 new_block_counters[:self._SizeToNumBlocks(new_fs_size)]) 1116 report.AddField('block write hist', new_write_hist, linebreak=True, 1117 indent=1) 1118 1119 # Check: Full update must write each dst block once. 1120 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]: 1121 raise error.PayloadError( 1122 '%s: not all blocks written exactly once during full update.' % 1123 base_name) 1124 1125 return total_data_used 1126 1127 def _CheckSignatures(self, report, pubkey_file_name): 1128 """Checks a payload's signature block.""" 1129 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size) 1130 sigs = update_metadata_pb2.Signatures() 1131 sigs.ParseFromString(sigs_raw) 1132 report.AddSection('signatures') 1133 1134 # Check: At least one signature present. 1135 # pylint cannot see through the protobuf object, it seems. 1136 # pylint: disable=E1101 1137 if not sigs.signatures: 1138 raise error.PayloadError('Signature block is empty.') 1139 1140 last_ops_section = (self.payload.manifest.kernel_install_operations or 1141 self.payload.manifest.install_operations) 1142 fake_sig_op = last_ops_section[-1] 1143 # Check: signatures_{offset,size} must match the last (fake) operation. 1144 if not (fake_sig_op.type == common.OpType.REPLACE and 1145 self.sigs_offset == fake_sig_op.data_offset and 1146 self.sigs_size == fake_sig_op.data_length): 1147 raise error.PayloadError( 1148 'Signatures_{offset,size} (%d+%d) does not match last operation ' 1149 '(%d+%d).' % 1150 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset, 1151 fake_sig_op.data_length)) 1152 1153 # Compute the checksum of all data up to signature blob. 1154 # TODO(garnold) we're re-reading the whole data section into a string 1155 # just to compute the checksum; instead, we could do it incrementally as 1156 # we read the blobs one-by-one, under the assumption that we're reading 1157 # them in order (which currently holds). This should be reconsidered. 1158 payload_hasher = self.payload.manifest_hasher.copy() 1159 common.Read(self.payload.payload_file, self.sigs_offset, 1160 offset=self.payload.data_offset, hasher=payload_hasher) 1161 1162 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'): 1163 sig_report = report.AddSubReport(sig_name) 1164 1165 # Check: Signature contains mandatory fields. 1166 self._CheckMandatoryField(sig, 'version', sig_report, sig_name) 1167 self._CheckMandatoryField(sig, 'data', None, sig_name) 1168 sig_report.AddField('data len', len(sig.data)) 1169 1170 # Check: Signatures pertains to actual payload hash. 1171 if sig.version == 1: 1172 self._CheckSha256Signature(sig.data, pubkey_file_name, 1173 payload_hasher.digest(), sig_name) 1174 else: 1175 raise error.PayloadError('Unknown signature version (%d).' % 1176 sig.version) 1177 1178 def Run(self, pubkey_file_name=None, metadata_sig_file=None, 1179 rootfs_part_size=0, kernel_part_size=0, report_out_file=None): 1180 """Checker entry point, invoking all checks. 1181 1182 Args: 1183 pubkey_file_name: Public key used for signature verification. 1184 metadata_sig_file: Metadata signature, if verification is desired. 1185 rootfs_part_size: The size of rootfs partitions in bytes (default: infer 1186 based on payload type and version). 1187 kernel_part_size: The size of kernel partitions in bytes (default: use 1188 reported filesystem size). 1189 report_out_file: File object to dump the report to. 1190 1191 Raises: 1192 error.PayloadError if payload verification failed. 1193 """ 1194 if not pubkey_file_name: 1195 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME 1196 1197 report = _PayloadReport() 1198 1199 # Get payload file size. 1200 self.payload.payload_file.seek(0, 2) 1201 payload_file_size = self.payload.payload_file.tell() 1202 self.payload.ResetFile() 1203 1204 try: 1205 # Check metadata signature (if provided). 1206 if metadata_sig_file: 1207 metadata_sig = base64.b64decode(metadata_sig_file.read()) 1208 self._CheckSha256Signature(metadata_sig, pubkey_file_name, 1209 self.payload.manifest_hasher.digest(), 1210 'metadata signature') 1211 1212 # Part 1: Check the file header. 1213 report.AddSection('header') 1214 # Check: Payload version is valid. 1215 if self.payload.header.version != 1: 1216 raise error.PayloadError('Unknown payload version (%d).' % 1217 self.payload.header.version) 1218 report.AddField('version', self.payload.header.version) 1219 report.AddField('manifest len', self.payload.header.manifest_len) 1220 1221 # Part 2: Check the manifest. 1222 self._CheckManifest(report, rootfs_part_size, kernel_part_size) 1223 assert self.payload_type, 'payload type should be known by now' 1224 1225 # Infer the usable partition size when validating rootfs operations: 1226 # - If rootfs partition size was provided, use that. 1227 # - Otherwise, if this is an older delta (minor version < 2), stick with 1228 # a known constant size. This is necessary because older deltas may 1229 # exceed the filesystem size when moving data blocks around. 1230 # - Otherwise, use the encoded filesystem size. 1231 new_rootfs_usable_size = self.new_rootfs_fs_size 1232 if rootfs_part_size: 1233 new_rootfs_usable_size = rootfs_part_size 1234 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1): 1235 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE 1236 1237 # Part 3: Examine rootfs operations. 1238 # TODO(garnold)(chromium:243559) only default to the filesystem size if 1239 # no explicit size provided *and* the partition size is not embedded in 1240 # the payload; see issue for more details. 1241 report.AddSection('rootfs operations') 1242 total_blob_size = self._CheckOperations( 1243 self.payload.manifest.install_operations, report, 1244 'install_operations', self.old_rootfs_fs_size, 1245 self.new_rootfs_fs_size, new_rootfs_usable_size, 0, False) 1246 1247 # Part 4: Examine kernel operations. 1248 # TODO(garnold)(chromium:243559) as above. 1249 report.AddSection('kernel operations') 1250 total_blob_size += self._CheckOperations( 1251 self.payload.manifest.kernel_install_operations, report, 1252 'kernel_install_operations', self.old_kernel_fs_size, 1253 self.new_kernel_fs_size, 1254 kernel_part_size if kernel_part_size else self.new_kernel_fs_size, 1255 total_blob_size, True) 1256 1257 # Check: Operations data reach the end of the payload file. 1258 used_payload_size = self.payload.data_offset + total_blob_size 1259 if used_payload_size != payload_file_size: 1260 raise error.PayloadError( 1261 'Used payload size (%d) different from actual file size (%d).' % 1262 (used_payload_size, payload_file_size)) 1263 1264 # Part 5: Handle payload signatures message. 1265 if self.check_payload_sig and self.sigs_size: 1266 self._CheckSignatures(report, pubkey_file_name) 1267 1268 # Part 6: Summary. 1269 report.AddSection('summary') 1270 report.AddField('update type', self.payload_type) 1271 1272 report.Finalize() 1273 finally: 1274 if report_out_file: 1275 report.Dump(report_out_file) 1276