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