blob: 2a1dd31c7e85b18b0a3838539286b4acba195daa [file] [log] [blame]
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001# 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"""
15
16import array
17import base64
18import hashlib
Gilad Arnold9b90c932013-05-22 17:12:56 -070019import os
Gilad Arnold553b0ec2013-01-26 01:00:39 -080020import subprocess
21
22import common
23from error import PayloadError
24import format_utils
25import histogram
26import update_metadata_pb2
27
28
29#
Gilad Arnold9b90c932013-05-22 17:12:56 -070030# Constants.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080031#
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070032_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents'
33_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
34_CHECK_PAYLOAD_SIG = 'payload-sig'
35CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070036 _CHECK_DST_PSEUDO_EXTENTS,
37 _CHECK_MOVE_SAME_SRC_DST_BLOCK,
38 _CHECK_PAYLOAD_SIG,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070039)
40
Gilad Arnold553b0ec2013-01-26 01:00:39 -080041_TYPE_FULL = 'full'
42_TYPE_DELTA = 'delta'
43
44_DEFAULT_BLOCK_SIZE = 4096
45
Gilad Arnold9b90c932013-05-22 17:12:56 -070046_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem'
47_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
48 _DEFAULT_PUBKEY_BASE_NAME)
49
Gilad Arnold553b0ec2013-01-26 01:00:39 -080050
51#
52# Helper functions.
53#
54def _IsPowerOfTwo(val):
55 """Returns True iff val is a power of two."""
56 return val > 0 and (val & (val - 1)) == 0
57
58
59def _AddFormat(format_func, value):
60 """Adds a custom formatted representation to ordinary string representation.
61
62 Args:
63 format_func: a value formatter
64 value: value to be formatted and returned
65 Returns:
66 A string 'x (y)' where x = str(value) and y = format_func(value).
67
68 """
69 return '%s (%s)' % (value, format_func(value))
70
71
72def _AddHumanReadableSize(size):
73 """Adds a human readable representation to a byte size value."""
74 return _AddFormat(format_utils.BytesToHumanReadable, size)
75
76
77#
78# Payload report generator.
79#
80class _PayloadReport(object):
81 """A payload report generator.
82
83 A report is essentially a sequence of nodes, which represent data points. It
84 is initialized to have a "global", untitled section. A node may be a
85 sub-report itself.
86
87 """
88
89 # Report nodes: field, sub-report, section.
90 class Node(object):
91 """A report node interface."""
92
93 @staticmethod
94 def _Indent(indent, line):
95 """Indents a line by a given indentation amount.
96
97 Args:
98 indent: the indentation amount
99 line: the line content (string)
100 Returns:
101 The properly indented line (string).
102
103 """
104 return '%*s%s' % (indent, '', line)
105
106 def GenerateLines(self, base_indent, sub_indent, curr_section):
107 """Generates the report lines for this node.
108
109 Args:
110 base_indent: base indentation for each line
111 sub_indent: additional indentation for sub-nodes
112 curr_section: the current report section object
113 Returns:
114 A pair consisting of a list of properly indented report lines and a new
115 current section object.
116
117 """
118 raise NotImplementedError()
119
120 class FieldNode(Node):
121 """A field report node, representing a (name, value) pair."""
122
123 def __init__(self, name, value, linebreak, indent):
124 super(_PayloadReport.FieldNode, self).__init__()
125 self.name = name
126 self.value = value
127 self.linebreak = linebreak
128 self.indent = indent
129
130 def GenerateLines(self, base_indent, sub_indent, curr_section):
131 """Generates a properly formatted 'name : value' entry."""
132 report_output = ''
133 if self.name:
134 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
135 value_lines = str(self.value).splitlines()
136 if self.linebreak and self.name:
137 report_output += '\n' + '\n'.join(
138 ['%*s%s' % (self.indent, '', line) for line in value_lines])
139 else:
140 if self.name:
141 report_output += ' '
142 report_output += '%*s' % (self.indent, '')
143 cont_line_indent = len(report_output)
144 indented_value_lines = [value_lines[0]]
145 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
146 for line in value_lines[1:]])
147 report_output += '\n'.join(indented_value_lines)
148
149 report_lines = [self._Indent(base_indent, line + '\n')
150 for line in report_output.split('\n')]
151 return report_lines, curr_section
152
153 class SubReportNode(Node):
154 """A sub-report node, representing a nested report."""
155
156 def __init__(self, title, report):
157 super(_PayloadReport.SubReportNode, self).__init__()
158 self.title = title
159 self.report = report
160
161 def GenerateLines(self, base_indent, sub_indent, curr_section):
162 """Recurse with indentation."""
163 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
164 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
165 sub_indent))
166 return report_lines, curr_section
167
168 class SectionNode(Node):
169 """A section header node."""
170
171 def __init__(self, title=None):
172 super(_PayloadReport.SectionNode, self).__init__()
173 self.title = title
174 self.max_field_name_len = 0
175
176 def GenerateLines(self, base_indent, sub_indent, curr_section):
177 """Dump a title line, return self as the (new) current section."""
178 report_lines = []
179 if self.title:
180 report_lines.append(self._Indent(base_indent,
181 '=== %s ===\n' % self.title))
182 return report_lines, self
183
184 def __init__(self):
185 self.report = []
186 self.last_section = self.global_section = self.SectionNode()
187 self.is_finalized = False
188
189 def GenerateLines(self, base_indent, sub_indent):
190 """Generates the lines in the report, properly indented.
191
192 Args:
193 base_indent: the indentation used for root-level report lines
194 sub_indent: the indentation offset used for sub-reports
195 Returns:
196 A list of indented report lines.
197
198 """
199 report_lines = []
200 curr_section = self.global_section
201 for node in self.report:
202 node_report_lines, curr_section = node.GenerateLines(
203 base_indent, sub_indent, curr_section)
204 report_lines.extend(node_report_lines)
205
206 return report_lines
207
208 def Dump(self, out_file, base_indent=0, sub_indent=2):
209 """Dumps the report to a file.
210
211 Args:
212 out_file: file object to output the content to
213 base_indent: base indentation for report lines
214 sub_indent: added indentation for sub-reports
215
216 """
217
218 report_lines = self.GenerateLines(base_indent, sub_indent)
219 if report_lines and not self.is_finalized:
220 report_lines.append('(incomplete report)\n')
221
222 for line in report_lines:
223 out_file.write(line)
224
225 def AddField(self, name, value, linebreak=False, indent=0):
226 """Adds a field/value pair to the payload report.
227
228 Args:
229 name: the field's name
230 value: the field's value
231 linebreak: whether the value should be printed on a new line
232 indent: amount of extra indent for each line of the value
233
234 """
235 assert not self.is_finalized
236 if name and self.last_section.max_field_name_len < len(name):
237 self.last_section.max_field_name_len = len(name)
238 self.report.append(self.FieldNode(name, value, linebreak, indent))
239
240 def AddSubReport(self, title):
241 """Adds and returns a sub-report with a title."""
242 assert not self.is_finalized
243 sub_report = self.SubReportNode(title, type(self)())
244 self.report.append(sub_report)
245 return sub_report.report
246
247 def AddSection(self, title):
248 """Adds a new section title."""
249 assert not self.is_finalized
250 self.last_section = self.SectionNode(title)
251 self.report.append(self.last_section)
252
253 def Finalize(self):
254 """Seals the report, marking it as complete."""
255 self.is_finalized = True
256
257
258#
259# Payload verification.
260#
261class PayloadChecker(object):
262 """Checking the integrity of an update payload.
263
264 This is a short-lived object whose purpose is to isolate the logic used for
265 verifying the integrity of an update payload.
266
267 """
268
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700269 def __init__(self, payload, assert_type=None, block_size=0,
270 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700271 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700272
273 Args:
274 payload: the payload object to check
275 assert_type: assert that payload is either 'full' or 'delta' (optional)
276 block_size: expected filesystem / payload block size (optional)
277 allow_unhashed: allow operations with unhashed data blobs
278 disabled_tests: list of tests to disable
279
280 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800281 assert payload.is_init, 'uninitialized update payload'
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700282
283 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800284 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700285 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
286 if not _IsPowerOfTwo(self.block_size):
287 raise PayloadError('expected block (%d) size is not a power of two' %
288 self.block_size)
289 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
290 raise PayloadError("invalid assert_type value (`%s')" % assert_type)
291 self.payload_type = assert_type
292 self.allow_unhashed = allow_unhashed
293
294 # Disable specific tests.
295 self.check_dst_pseudo_extents = (
296 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
297 self.check_move_same_src_dst_block = (
298 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
299 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800300
301 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800302 self.sigs_offset = 0
303 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700304 self.old_rootfs_fs_size = 0
305 self.old_kernel_fs_size = 0
306 self.new_rootfs_fs_size = 0
307 self.new_kernel_fs_size = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800308
309 @staticmethod
310 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
311 msg_name=None, linebreak=False, indent=0):
312 """Adds an element from a protobuf message to the payload report.
313
314 Checks to see whether a message contains a given element, and if so adds
315 the element value to the provided report. A missing mandatory element
316 causes an exception to be raised.
317
318 Args:
319 msg: the message containing the element
320 name: the name of the element
321 report: a report object to add the element name/value to
322 is_mandatory: whether or not this element must be present
323 is_submsg: whether this element is itself a message
324 convert: a function for converting the element value for reporting
325 msg_name: the name of the message object (for error reporting)
326 linebreak: whether the value report should induce a line break
327 indent: amount of indent used for reporting the value
328 Returns:
329 A pair consisting of the element value and the generated sub-report for
330 it (if the element is a sub-message, None otherwise). If the element is
331 missing, returns (None, None).
332 Raises:
333 PayloadError if a mandatory element is missing.
334
335 """
336 if not msg.HasField(name):
337 if is_mandatory:
338 raise PayloadError("%smissing mandatory %s '%s'" %
339 (msg_name + ' ' if msg_name else '',
340 'sub-message' if is_submsg else 'field',
341 name))
342 return (None, None)
343
344 value = getattr(msg, name)
345 if is_submsg:
346 return (value, report and report.AddSubReport(name))
347 else:
348 if report:
349 report.AddField(name, convert(value), linebreak=linebreak,
350 indent=indent)
351 return (value, None)
352
353 @staticmethod
354 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
355 linebreak=False, indent=0):
356 """Adds a mandatory field; returning first component from _CheckElem."""
357 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
358 convert=convert, msg_name=msg_name,
359 linebreak=linebreak, indent=indent)[0]
360
361 @staticmethod
362 def _CheckOptionalField(msg, field_name, report, convert=str,
363 linebreak=False, indent=0):
364 """Adds an optional field; returning first component from _CheckElem."""
365 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
366 convert=convert, linebreak=linebreak,
367 indent=indent)[0]
368
369 @staticmethod
370 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
371 """Adds a mandatory sub-message; wrapper for _CheckElem."""
372 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
373 msg_name)
374
375 @staticmethod
376 def _CheckOptionalSubMsg(msg, submsg_name, report):
377 """Adds an optional sub-message; wrapper for _CheckElem."""
378 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
379
380 @staticmethod
381 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
382 """Checks that val1 is None iff val2 is None.
383
384 Args:
385 val1: first value to be compared
386 val2: second value to be compared
387 name1: name of object holding the first value
388 name2: name of object holding the second value
389 obj_name: name of the object containing these values
390 Raises:
391 PayloadError if assertion does not hold.
392
393 """
394 if None in (val1, val2) and val1 is not val2:
395 present, missing = (name1, name2) if val2 is None else (name2, name1)
396 raise PayloadError("'%s' present without '%s'%s" %
397 (present, missing,
398 ' in ' + obj_name if obj_name else ''))
399
400 @staticmethod
401 def _Run(cmd, send_data=None):
402 """Runs a subprocess, returns its output.
403
404 Args:
405 cmd: list of command-line argument for invoking the subprocess
406 send_data: data to feed to the process via its stdin
407 Returns:
408 A tuple containing the stdout and stderr output of the process.
409
410 """
411 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
412 stdout=subprocess.PIPE)
413 return run_process.communicate(input=send_data)
414
415 @staticmethod
416 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
417 """Verifies an actual hash against a signed one.
418
419 Args:
420 sig_data: the raw signature data
421 pubkey_file_name: public key used for verifying signature
422 actual_hash: the actual hash digest
423 sig_name: signature name for error reporting
424 Raises:
425 PayloadError if signature could not be verified.
426
427 """
428 if len(sig_data) != 256:
429 raise PayloadError('%s: signature size (%d) not as expected (256)' %
430 (sig_name, len(sig_data)))
431 signed_data, _ = PayloadChecker._Run(
432 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
433 send_data=sig_data)
434
Gilad Arnold5502b562013-03-08 13:22:31 -0800435 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800436 raise PayloadError('%s: unexpected signed data length (%d)' %
437 (sig_name, len(signed_data)))
438
Gilad Arnold5502b562013-03-08 13:22:31 -0800439 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800440 raise PayloadError('%s: not containing standard ASN.1 prefix' % sig_name)
441
Gilad Arnold5502b562013-03-08 13:22:31 -0800442 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800443 if signed_hash != actual_hash:
444 raise PayloadError('%s: signed hash (%s) different from actual (%s)' %
Gilad Arnold96405372013-05-04 00:24:58 -0700445 (sig_name, common.FormatSha256(signed_hash),
446 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800447
448 @staticmethod
449 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
450 block_name=None):
451 """Checks that a given length fits given block space.
452
453 This ensures that the number of blocks allocated is appropriate for the
454 length of the data residing in these blocks.
455
456 Args:
457 length: the actual length of the data
458 num_blocks: the number of blocks allocated for it
459 block_size: the size of each block in bytes
460 length_name: name of length (used for error reporting)
461 block_name: name of block (used for error reporting)
462 Raises:
463 PayloadError if the aforementioned invariant is not satisfied.
464
465 """
466 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700467 if length > num_blocks * block_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800468 raise PayloadError(
469 '%s (%d) > num %sblocks (%d) * block_size (%d)' %
470 (length_name, length, block_name or '', num_blocks, block_size))
471
472 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700473 if length <= (num_blocks - 1) * block_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800474 raise PayloadError(
475 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d)' %
476 (length_name, length, block_name or '', num_blocks - 1, block_size))
477
Gilad Arnold382df5c2013-05-03 12:49:28 -0700478 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800479 """Checks the payload manifest.
480
481 Args:
482 report: a report object to add to
Gilad Arnold382df5c2013-05-03 12:49:28 -0700483 rootfs_part_size: size of the rootfs partition in bytes
484 kernel_part_size: size of the kernel partition in bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800485 Returns:
486 A tuple consisting of the partition block size used during the update
487 (integer), the signatures block offset and size.
488 Raises:
489 PayloadError if any of the checks fail.
490
491 """
492 manifest = self.payload.manifest
493 report.AddSection('manifest')
494
495 # Check: block_size must exist and match the expected value.
496 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
497 report, 'manifest')
498 if actual_block_size != self.block_size:
499 raise PayloadError('block_size (%d) not as expected (%d)' %
500 (actual_block_size, self.block_size))
501
502 # Check: signatures_offset <==> signatures_size.
503 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
504 report)
505 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
506 report)
507 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
508 'signatures_offset', 'signatures_size', 'manifest')
509
510 # Check: old_kernel_info <==> old_rootfs_info.
511 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
512 'old_kernel_info', report)
513 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
514 'old_rootfs_info', report)
515 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
516 'old_rootfs_info', 'manifest')
517 if oki_msg: # equivalently, ori_msg
518 # Assert/mark delta payload.
519 if self.payload_type == _TYPE_FULL:
520 raise PayloadError(
521 'apparent full payload contains old_{kernel,rootfs}_info')
522 self.payload_type = _TYPE_DELTA
523
524 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700525 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800526 oki_msg, 'size', oki_report, 'old_kernel_info')
527 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
528 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700529 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800530 ori_msg, 'size', ori_report, 'old_rootfs_info')
531 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
532 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700533
534 # Check: old_{kernel,rootfs} size must fit in respective partition.
535 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
536 raise PayloadError(
537 'old kernel content (%d) exceed partition size (%d)' %
538 (self.old_kernel_fs_size, kernel_part_size))
539 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
540 raise PayloadError(
541 'old rootfs content (%d) exceed partition size (%d)' %
542 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800543 else:
544 # Assert/mark full payload.
545 if self.payload_type == _TYPE_DELTA:
546 raise PayloadError(
547 'apparent delta payload missing old_{kernel,rootfs}_info')
548 self.payload_type = _TYPE_FULL
549
550 # Check: new_kernel_info present; contains {size, hash}.
551 nki_msg, nki_report = self._CheckMandatorySubMsg(
552 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700553 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800554 nki_msg, 'size', nki_report, 'new_kernel_info')
555 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
556 convert=common.FormatSha256)
557
558 # Check: new_rootfs_info present; contains {size, hash}.
559 nri_msg, nri_report = self._CheckMandatorySubMsg(
560 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700561 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800562 nri_msg, 'size', nri_report, 'new_rootfs_info')
563 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
564 convert=common.FormatSha256)
565
Gilad Arnold382df5c2013-05-03 12:49:28 -0700566 # Check: new_{kernel,rootfs} size must fit in respective partition.
567 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
568 raise PayloadError(
569 'new kernel content (%d) exceed partition size (%d)' %
570 (self.new_kernel_fs_size, kernel_part_size))
571 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
572 raise PayloadError(
573 'new rootfs content (%d) exceed partition size (%d)' %
574 (self.new_rootfs_fs_size, rootfs_part_size))
575
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800576 # Check: payload must contain at least one operation.
577 if not(len(manifest.install_operations) or
578 len(manifest.kernel_install_operations)):
579 raise PayloadError('payload contains no operations')
580
581 def _CheckLength(self, length, total_blocks, op_name, length_name):
582 """Checks whether a length matches the space designated in extents.
583
584 Args:
585 length: the total length of the data
586 total_blocks: the total number of blocks in extents
587 op_name: operation name (for error reporting)
588 length_name: length name (for error reporting)
589 Raises:
590 PayloadError is there a problem with the length.
591
592 """
593 # Check: length is non-zero.
594 if length == 0:
595 raise PayloadError('%s: %s is zero' % (op_name, length_name))
596
597 # Check that length matches number of blocks.
598 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
599 '%s: %s' % (op_name, length_name))
600
Gilad Arnold382df5c2013-05-03 12:49:28 -0700601 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800602 allow_pseudo=False, allow_signature=False):
603 """Checks a sequence of extents.
604
605 Args:
606 extents: the sequence of extents to check
Gilad Arnold382df5c2013-05-03 12:49:28 -0700607 usable_size: the usable size of the partition to which the extents apply
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800608 block_counters: an array of counters corresponding to the number of blocks
609 name: the name of the extent block
610 allow_pseudo: whether or not pseudo block numbers are allowed
611 allow_signature: whether or not the extents are used for a signature
612 Returns:
613 The total number of blocks in the extents.
614 Raises:
615 PayloadError if any of the entailed checks fails.
616
617 """
618 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800619 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800620 # Check: mandatory fields.
621 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
622 None, ex_name)
623 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
624 ex_name)
625 end_block = start_block + num_blocks
626
627 # Check: num_blocks > 0.
628 if num_blocks == 0:
629 raise PayloadError('%s: extent length is zero' % ex_name)
630
631 if start_block != common.PSEUDO_EXTENT_MARKER:
632 # Check: make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700633 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800634 raise PayloadError(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700635 '%s: extent (%s) exceeds usable partition size (%d)' %
636 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800637
638 # Record block usage.
639 for i in range(start_block, end_block):
640 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800641 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
642 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
643 # signature operation (in which case there has to be exactly one).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800644 raise PayloadError('%s: unexpected pseudo-extent' % ex_name)
645
646 total_num_blocks += num_blocks
647
648 return total_num_blocks
649
650 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
651 """Specific checks for REPLACE/REPLACE_BZ operations.
652
653 Args:
654 op: the operation object from the manifest
655 data_length: the length of the data blob associated with the operation
656 total_dst_blocks: total number of blocks in dst_extents
657 op_name: operation name for error reporting
658 Raises:
659 PayloadError if any check fails.
660
661 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800662 # Check: does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800663 if op.src_extents:
664 raise PayloadError('%s: contains src_extents' % op_name)
665
Gilad Arnold5502b562013-03-08 13:22:31 -0800666 # Check: contains data.
667 if data_length is None:
668 raise PayloadError('%s: missing data_{offset,length}' % op_name)
669
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800670 if op.type == common.OpType.REPLACE:
671 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
672 self.block_size,
673 op_name + '.data_length', 'dst')
674 else:
675 # Check: data_length must be smaller than the alotted dst blocks.
676 if data_length >= total_dst_blocks * self.block_size:
677 raise PayloadError(
678 '%s: data_length (%d) must be less than allotted dst block '
679 'space (%d * %d)' %
680 (op_name, data_length, total_dst_blocks, self.block_size))
681
682 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
683 total_dst_blocks, op_name):
684 """Specific checks for MOVE operations.
685
686 Args:
687 op: the operation object from the manifest
688 data_offset: the offset of a data blob for the operation
689 total_src_blocks: total number of blocks in src_extents
690 total_dst_blocks: total number of blocks in dst_extents
691 op_name: operation name for error reporting
692 Raises:
693 PayloadError if any check fails.
694
695 """
696 # Check: no data_{offset,length}.
697 if data_offset is not None:
698 raise PayloadError('%s: contains data_{offset,length}' % op_name)
699
700 # Check: total src blocks == total dst blocks.
701 if total_src_blocks != total_dst_blocks:
702 raise PayloadError(
703 '%s: total src blocks (%d) != total dst blocks (%d)' %
704 (op_name, total_src_blocks, total_dst_blocks))
705
706 # Check: for all i, i-th src block index != i-th dst block index.
707 i = 0
708 src_extent_iter = iter(op.src_extents)
709 dst_extent_iter = iter(op.dst_extents)
710 src_extent = dst_extent = None
711 src_idx = src_num = dst_idx = dst_num = 0
712 while i < total_src_blocks:
713 # Get the next source extent, if needed.
714 if not src_extent:
715 try:
716 src_extent = src_extent_iter.next()
717 except StopIteration:
718 raise PayloadError('%s: ran out of src extents (%d/%d)' %
719 (op_name, i, total_src_blocks))
720 src_idx = src_extent.start_block
721 src_num = src_extent.num_blocks
722
723 # Get the next dest extent, if needed.
724 if not dst_extent:
725 try:
726 dst_extent = dst_extent_iter.next()
727 except StopIteration:
728 raise PayloadError('%s: ran out of dst extents (%d/%d)' %
729 (op_name, i, total_dst_blocks))
730 dst_idx = dst_extent.start_block
731 dst_num = dst_extent.num_blocks
732
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700733 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnold5502b562013-03-08 13:22:31 -0800734 raise PayloadError('%s: src/dst block number %d is the same (%d)' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800735 (op_name, i, src_idx))
736
737 advance = min(src_num, dst_num)
738 i += advance
739
740 src_idx += advance
741 src_num -= advance
742 if src_num == 0:
743 src_extent = None
744
745 dst_idx += advance
746 dst_num -= advance
747 if dst_num == 0:
748 dst_extent = None
749
Gilad Arnold5502b562013-03-08 13:22:31 -0800750 # Make sure we've exhausted all src/dst extents.
751 if src_extent:
752 raise PayloadError('%s: excess src blocks' % op_name)
753 if dst_extent:
754 raise PayloadError('%s: excess dst blocks' % op_name)
755
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800756 def _CheckBsdiffOperation(self, data_length, total_dst_blocks, op_name):
757 """Specific checks for BSDIFF operations.
758
759 Args:
760 data_length: the length of the data blob associated with the operation
761 total_dst_blocks: total number of blocks in dst_extents
762 op_name: operation name for error reporting
763 Raises:
764 PayloadError if any check fails.
765
766 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800767 # Check: data_{offset,length} present.
768 if data_length is None:
769 raise PayloadError('%s: missing data_{offset,length}' % op_name)
770
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800771 # Check: data_length is strictly smaller than the alotted dst blocks.
772 if data_length >= total_dst_blocks * self.block_size:
773 raise PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800774 '%s: data_length (%d) must be smaller than allotted dst space '
775 '(%d * %d = %d)' %
776 (op_name, data_length, total_dst_blocks, self.block_size,
777 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800778
779 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700780 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700781 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800782 """Checks a single update operation.
783
784 Args:
785 op: the operation object
786 op_name: operation name string for error reporting
787 is_last: whether this is the last operation in the sequence
788 old_block_counters: arrays of block read counters
789 new_block_counters: arrays of block write counters
Gilad Arnold4f50b412013-05-14 09:19:17 -0700790 old_usable_size: the overall usable size for src data in bytes
791 new_usable_size: the overall usable size for dst data in bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800792 prev_data_offset: offset of last used data bytes
793 allow_signature: whether this may be a signature operation
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800794 blob_hash_counts: counters for hashed/unhashed blobs
795 Returns:
796 The amount of data blob associated with the operation.
797 Raises:
798 PayloadError if any check has failed.
799
800 """
801 # Check extents.
802 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700803 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800804 op_name + '.src_extents', allow_pseudo=True)
805 allow_signature_in_extents = (allow_signature and is_last and
806 op.type == common.OpType.REPLACE)
807 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700808 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700809 op_name + '.dst_extents',
810 allow_pseudo=(not self.check_dst_pseudo_extents),
811 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800812
813 # Check: data_offset present <==> data_length present.
814 data_offset = self._CheckOptionalField(op, 'data_offset', None)
815 data_length = self._CheckOptionalField(op, 'data_length', None)
816 self._CheckPresentIff(data_offset, data_length, 'data_offset',
817 'data_length', op_name)
818
819 # Check: at least one dst_extent.
820 if not op.dst_extents:
821 raise PayloadError('%s: dst_extents is empty' % op_name)
822
823 # Check {src,dst}_length, if present.
824 if op.HasField('src_length'):
825 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
826 if op.HasField('dst_length'):
827 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
828
829 if op.HasField('data_sha256_hash'):
830 blob_hash_counts['hashed'] += 1
831
832 # Check: operation carries data.
833 if data_offset is None:
834 raise PayloadError(
835 '%s: data_sha256_hash present but no data_{offset,length}' %
836 op_name)
837
838 # Check: hash verifies correctly.
839 # pylint: disable=E1101
840 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
841 data_length))
842 if op.data_sha256_hash != actual_hash.digest():
843 raise PayloadError(
844 '%s: data_sha256_hash (%s) does not match actual hash (%s)' %
Gilad Arnold96405372013-05-04 00:24:58 -0700845 (op_name, common.FormatSha256(op.data_sha256_hash),
846 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800847 elif data_offset is not None:
848 if allow_signature_in_extents:
849 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700850 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800851 blob_hash_counts['unhashed'] += 1
852 else:
853 raise PayloadError('%s: unhashed operation not allowed' % op_name)
854
855 if data_offset is not None:
856 # Check: contiguous use of data section.
857 if data_offset != prev_data_offset:
858 raise PayloadError(
859 '%s: data offset (%d) not matching amount used so far (%d)' %
860 (op_name, data_offset, prev_data_offset))
861
862 # Type-specific checks.
863 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
864 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
865 elif self.payload_type == _TYPE_FULL:
866 raise PayloadError('%s: non-REPLACE operation in a full payload' %
867 op_name)
868 elif op.type == common.OpType.MOVE:
869 self._CheckMoveOperation(op, data_offset, total_src_blocks,
870 total_dst_blocks, op_name)
871 elif op.type == common.OpType.BSDIFF:
872 self._CheckBsdiffOperation(data_length, total_dst_blocks, op_name)
873 else:
874 assert False, 'cannot get here'
875
876 return data_length if data_length is not None else 0
877
Gilad Arnold382df5c2013-05-03 12:49:28 -0700878 def _SizeToNumBlocks(self, size):
879 """Returns the number of blocks needed to contain a given byte size."""
880 return (size + self.block_size - 1) / self.block_size
881
882 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800883 """Returns a freshly initialized array of block counters.
884
885 Args:
Gilad Arnold382df5c2013-05-03 12:49:28 -0700886 total_size: the total block size in bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800887 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -0700888 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800889 the blocks necessary for containing the partition.
890
891 """
Gilad Arnold9753f3d2013-07-23 08:34:45 -0700892 return array.array('H', [0] * self._SizeToNumBlocks(total_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800893
Gilad Arnold382df5c2013-05-03 12:49:28 -0700894 def _CheckOperations(self, operations, report, base_name, old_fs_size,
895 new_fs_size, new_usable_size, prev_data_offset,
896 allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800897 """Checks a sequence of update operations.
898
899 Args:
900 operations: the sequence of operations to check
901 report: the report object to add to
902 base_name: the name of the operation block
Gilad Arnold382df5c2013-05-03 12:49:28 -0700903 old_fs_size: the old filesystem size in bytes
904 new_fs_size: the new filesystem size in bytes
Gilad Arnold4f50b412013-05-14 09:19:17 -0700905 new_usable_size: the overall usable size of the new partition in bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800906 prev_data_offset: offset of last used data bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800907 allow_signature: whether this sequence may contain signature operations
908 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -0800909 The total data blob size used.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800910 Raises:
911 PayloadError if any of the checks fails.
912
913 """
914 # The total size of data blobs used by operations scanned thus far.
915 total_data_used = 0
916 # Counts of specific operation types.
917 op_counts = {
918 common.OpType.REPLACE: 0,
919 common.OpType.REPLACE_BZ: 0,
920 common.OpType.MOVE: 0,
921 common.OpType.BSDIFF: 0,
922 }
923 # Total blob sizes for each operation type.
924 op_blob_totals = {
925 common.OpType.REPLACE: 0,
926 common.OpType.REPLACE_BZ: 0,
927 # MOVE operations don't have blobs
928 common.OpType.BSDIFF: 0,
929 }
930 # Counts of hashed vs unhashed operations.
931 blob_hash_counts = {
932 'hashed': 0,
933 'unhashed': 0,
934 }
935 if allow_signature:
936 blob_hash_counts['signature'] = 0
937
938 # Allocate old and new block counters.
Gilad Arnold4f50b412013-05-14 09:19:17 -0700939 old_block_counters = (self._AllocBlockCounters(new_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700940 if old_fs_size else None)
941 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800942
943 # Process and verify each operation.
944 op_num = 0
945 for op, op_name in common.OperationIter(operations, base_name):
946 op_num += 1
947
948 # Check: type is valid.
949 if op.type not in op_counts.keys():
950 raise PayloadError('%s: invalid type (%d)' % (op_name, op.type))
951 op_counts[op.type] += 1
952
953 is_last = op_num == len(operations)
954 curr_data_used = self._CheckOperation(
955 op, op_name, is_last, old_block_counters, new_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700956 new_usable_size if old_fs_size else 0, new_usable_size,
957 prev_data_offset + total_data_used, allow_signature,
958 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800959 if curr_data_used:
960 op_blob_totals[op.type] += curr_data_used
961 total_data_used += curr_data_used
962
963 # Report totals and breakdown statistics.
964 report.AddField('total operations', op_num)
965 report.AddField(
966 None,
967 histogram.Histogram.FromCountDict(op_counts,
968 key_names=common.OpType.NAMES),
969 indent=1)
970 report.AddField('total blobs', sum(blob_hash_counts.values()))
971 report.AddField(None,
972 histogram.Histogram.FromCountDict(blob_hash_counts),
973 indent=1)
974 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
975 report.AddField(
976 None,
977 histogram.Histogram.FromCountDict(op_blob_totals,
978 formatter=_AddHumanReadableSize,
979 key_names=common.OpType.NAMES),
980 indent=1)
981
982 # Report read/write histograms.
983 if old_block_counters:
984 report.AddField('block read hist',
985 histogram.Histogram.FromKeyList(old_block_counters),
986 linebreak=True, indent=1)
987
Gilad Arnold382df5c2013-05-03 12:49:28 -0700988 new_write_hist = histogram.Histogram.FromKeyList(
989 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
990 report.AddField('block write hist', new_write_hist, linebreak=True,
991 indent=1)
992
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800993 # Check: full update must write each dst block once.
994 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
995 raise PayloadError(
996 '%s: not all blocks written exactly once during full update' %
997 base_name)
998
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800999 return total_data_used
1000
1001 def _CheckSignatures(self, report, pubkey_file_name):
1002 """Checks a payload's signature block."""
1003 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1004 sigs = update_metadata_pb2.Signatures()
1005 sigs.ParseFromString(sigs_raw)
1006 report.AddSection('signatures')
1007
1008 # Check: at least one signature present.
1009 # pylint: disable=E1101
1010 if not sigs.signatures:
1011 raise PayloadError('signature block is empty')
1012
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001013 last_ops_section = (self.payload.manifest.kernel_install_operations or
1014 self.payload.manifest.install_operations)
1015 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001016 # Check: signatures_{offset,size} must match the last (fake) operation.
1017 if not (fake_sig_op.type == common.OpType.REPLACE and
1018 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001019 self.sigs_size == fake_sig_op.data_length):
1020 raise PayloadError(
1021 'signatures_{offset,size} (%d+%d) does not match last operation '
1022 '(%d+%d)' %
1023 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1024 fake_sig_op.data_length))
1025
1026 # Compute the checksum of all data up to signature blob.
1027 # TODO(garnold) we're re-reading the whole data section into a string
1028 # just to compute the checksum; instead, we could do it incrementally as
1029 # we read the blobs one-by-one, under the assumption that we're reading
1030 # them in order (which currently holds). This should be reconsidered.
1031 payload_hasher = self.payload.manifest_hasher.copy()
1032 common.Read(self.payload.payload_file, self.sigs_offset,
1033 offset=self.payload.data_offset, hasher=payload_hasher)
1034
1035 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1036 sig_report = report.AddSubReport(sig_name)
1037
1038 # Check: signature contains mandatory fields.
1039 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1040 self._CheckMandatoryField(sig, 'data', None, sig_name)
1041 sig_report.AddField('data len', len(sig.data))
1042
1043 # Check: signatures pertains to actual payload hash.
1044 if sig.version == 1:
1045 self._CheckSha256Signature(sig.data, pubkey_file_name,
1046 payload_hasher.digest(), sig_name)
1047 else:
1048 raise PayloadError('unknown signature version (%d)' % sig.version)
1049
1050 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001051 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001052 """Checker entry point, invoking all checks.
1053
1054 Args:
1055 pubkey_file_name: public key used for signature verification
1056 metadata_sig_file: metadata signature, if verification is desired
Gilad Arnold382df5c2013-05-03 12:49:28 -07001057 rootfs_part_size: the size of rootfs partitions in bytes (default: use
1058 reported filesystem size)
1059 kernel_part_size: the size of kernel partitions in bytes (default: use
1060 reported filesystem size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001061 report_out_file: file object to dump the report to
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001062 Raises:
1063 PayloadError if payload verification failed.
1064
1065 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001066 if not pubkey_file_name:
1067 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1068
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001069 report = _PayloadReport()
1070
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001071 # Get payload file size.
1072 self.payload.payload_file.seek(0, 2)
1073 payload_file_size = self.payload.payload_file.tell()
1074 self.payload.ResetFile()
1075
1076 try:
1077 # Check metadata signature (if provided).
1078 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001079 metadata_sig = base64.b64decode(metadata_sig_file.read())
1080 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1081 self.payload.manifest_hasher.digest(),
1082 'metadata signature')
1083
1084 # Part 1: check the file header.
1085 report.AddSection('header')
1086 # Check: payload version is valid.
1087 if self.payload.header.version != 1:
1088 raise PayloadError('unknown payload version (%d)' %
1089 self.payload.header.version)
1090 report.AddField('version', self.payload.header.version)
1091 report.AddField('manifest len', self.payload.header.manifest_len)
1092
1093 # Part 2: check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001094 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001095 assert self.payload_type, 'payload type should be known by now'
1096
1097 # Part 3: examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001098 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1099 # no explicit size provided *and* the partition size is not embedded in
1100 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001101 report.AddSection('rootfs operations')
1102 total_blob_size = self._CheckOperations(
1103 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001104 'install_operations', self.old_rootfs_fs_size,
1105 self.new_rootfs_fs_size,
1106 rootfs_part_size if rootfs_part_size else self.new_rootfs_fs_size,
1107 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001108
1109 # Part 4: examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001110 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001111 report.AddSection('kernel operations')
1112 total_blob_size += self._CheckOperations(
1113 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001114 'kernel_install_operations', self.old_kernel_fs_size,
1115 self.new_kernel_fs_size,
1116 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1117 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001118
1119 # Check: operations data reach the end of the payload file.
1120 used_payload_size = self.payload.data_offset + total_blob_size
1121 if used_payload_size != payload_file_size:
1122 raise PayloadError(
1123 'used payload size (%d) different from actual file size (%d)' %
1124 (used_payload_size, payload_file_size))
1125
1126 # Part 5: handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001127 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001128 self._CheckSignatures(report, pubkey_file_name)
1129
1130 # Part 6: summary.
1131 report.AddSection('summary')
1132 report.AddField('update type', self.payload_type)
1133
1134 report.Finalize()
1135 finally:
1136 if report_out_file:
1137 report.Dump(report_out_file)