blob: 13a0518141f21f367321705f9a14e5172d69721a [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
19import subprocess
20
21import common
22from error import PayloadError
23import format_utils
24import histogram
25import update_metadata_pb2
26
27
28#
29# Constants / helper functions.
30#
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070031_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents'
32_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
33_CHECK_PAYLOAD_SIG = 'payload-sig'
34CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070035 _CHECK_DST_PSEUDO_EXTENTS,
36 _CHECK_MOVE_SAME_SRC_DST_BLOCK,
37 _CHECK_PAYLOAD_SIG,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070038)
39
Gilad Arnold553b0ec2013-01-26 01:00:39 -080040_TYPE_FULL = 'full'
41_TYPE_DELTA = 'delta'
42
43_DEFAULT_BLOCK_SIZE = 4096
44
45
46#
47# Helper functions.
48#
49def _IsPowerOfTwo(val):
50 """Returns True iff val is a power of two."""
51 return val > 0 and (val & (val - 1)) == 0
52
53
54def _AddFormat(format_func, value):
55 """Adds a custom formatted representation to ordinary string representation.
56
57 Args:
58 format_func: a value formatter
59 value: value to be formatted and returned
60 Returns:
61 A string 'x (y)' where x = str(value) and y = format_func(value).
62
63 """
64 return '%s (%s)' % (value, format_func(value))
65
66
67def _AddHumanReadableSize(size):
68 """Adds a human readable representation to a byte size value."""
69 return _AddFormat(format_utils.BytesToHumanReadable, size)
70
71
72#
73# Payload report generator.
74#
75class _PayloadReport(object):
76 """A payload report generator.
77
78 A report is essentially a sequence of nodes, which represent data points. It
79 is initialized to have a "global", untitled section. A node may be a
80 sub-report itself.
81
82 """
83
84 # Report nodes: field, sub-report, section.
85 class Node(object):
86 """A report node interface."""
87
88 @staticmethod
89 def _Indent(indent, line):
90 """Indents a line by a given indentation amount.
91
92 Args:
93 indent: the indentation amount
94 line: the line content (string)
95 Returns:
96 The properly indented line (string).
97
98 """
99 return '%*s%s' % (indent, '', line)
100
101 def GenerateLines(self, base_indent, sub_indent, curr_section):
102 """Generates the report lines for this node.
103
104 Args:
105 base_indent: base indentation for each line
106 sub_indent: additional indentation for sub-nodes
107 curr_section: the current report section object
108 Returns:
109 A pair consisting of a list of properly indented report lines and a new
110 current section object.
111
112 """
113 raise NotImplementedError()
114
115 class FieldNode(Node):
116 """A field report node, representing a (name, value) pair."""
117
118 def __init__(self, name, value, linebreak, indent):
119 super(_PayloadReport.FieldNode, self).__init__()
120 self.name = name
121 self.value = value
122 self.linebreak = linebreak
123 self.indent = indent
124
125 def GenerateLines(self, base_indent, sub_indent, curr_section):
126 """Generates a properly formatted 'name : value' entry."""
127 report_output = ''
128 if self.name:
129 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
130 value_lines = str(self.value).splitlines()
131 if self.linebreak and self.name:
132 report_output += '\n' + '\n'.join(
133 ['%*s%s' % (self.indent, '', line) for line in value_lines])
134 else:
135 if self.name:
136 report_output += ' '
137 report_output += '%*s' % (self.indent, '')
138 cont_line_indent = len(report_output)
139 indented_value_lines = [value_lines[0]]
140 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
141 for line in value_lines[1:]])
142 report_output += '\n'.join(indented_value_lines)
143
144 report_lines = [self._Indent(base_indent, line + '\n')
145 for line in report_output.split('\n')]
146 return report_lines, curr_section
147
148 class SubReportNode(Node):
149 """A sub-report node, representing a nested report."""
150
151 def __init__(self, title, report):
152 super(_PayloadReport.SubReportNode, self).__init__()
153 self.title = title
154 self.report = report
155
156 def GenerateLines(self, base_indent, sub_indent, curr_section):
157 """Recurse with indentation."""
158 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
159 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
160 sub_indent))
161 return report_lines, curr_section
162
163 class SectionNode(Node):
164 """A section header node."""
165
166 def __init__(self, title=None):
167 super(_PayloadReport.SectionNode, self).__init__()
168 self.title = title
169 self.max_field_name_len = 0
170
171 def GenerateLines(self, base_indent, sub_indent, curr_section):
172 """Dump a title line, return self as the (new) current section."""
173 report_lines = []
174 if self.title:
175 report_lines.append(self._Indent(base_indent,
176 '=== %s ===\n' % self.title))
177 return report_lines, self
178
179 def __init__(self):
180 self.report = []
181 self.last_section = self.global_section = self.SectionNode()
182 self.is_finalized = False
183
184 def GenerateLines(self, base_indent, sub_indent):
185 """Generates the lines in the report, properly indented.
186
187 Args:
188 base_indent: the indentation used for root-level report lines
189 sub_indent: the indentation offset used for sub-reports
190 Returns:
191 A list of indented report lines.
192
193 """
194 report_lines = []
195 curr_section = self.global_section
196 for node in self.report:
197 node_report_lines, curr_section = node.GenerateLines(
198 base_indent, sub_indent, curr_section)
199 report_lines.extend(node_report_lines)
200
201 return report_lines
202
203 def Dump(self, out_file, base_indent=0, sub_indent=2):
204 """Dumps the report to a file.
205
206 Args:
207 out_file: file object to output the content to
208 base_indent: base indentation for report lines
209 sub_indent: added indentation for sub-reports
210
211 """
212
213 report_lines = self.GenerateLines(base_indent, sub_indent)
214 if report_lines and not self.is_finalized:
215 report_lines.append('(incomplete report)\n')
216
217 for line in report_lines:
218 out_file.write(line)
219
220 def AddField(self, name, value, linebreak=False, indent=0):
221 """Adds a field/value pair to the payload report.
222
223 Args:
224 name: the field's name
225 value: the field's value
226 linebreak: whether the value should be printed on a new line
227 indent: amount of extra indent for each line of the value
228
229 """
230 assert not self.is_finalized
231 if name and self.last_section.max_field_name_len < len(name):
232 self.last_section.max_field_name_len = len(name)
233 self.report.append(self.FieldNode(name, value, linebreak, indent))
234
235 def AddSubReport(self, title):
236 """Adds and returns a sub-report with a title."""
237 assert not self.is_finalized
238 sub_report = self.SubReportNode(title, type(self)())
239 self.report.append(sub_report)
240 return sub_report.report
241
242 def AddSection(self, title):
243 """Adds a new section title."""
244 assert not self.is_finalized
245 self.last_section = self.SectionNode(title)
246 self.report.append(self.last_section)
247
248 def Finalize(self):
249 """Seals the report, marking it as complete."""
250 self.is_finalized = True
251
252
253#
254# Payload verification.
255#
256class PayloadChecker(object):
257 """Checking the integrity of an update payload.
258
259 This is a short-lived object whose purpose is to isolate the logic used for
260 verifying the integrity of an update payload.
261
262 """
263
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700264 def __init__(self, payload, assert_type=None, block_size=0,
265 allow_unhashed=False, disabled_tests=()):
266 """Initialize the checker object.
267
268 Args:
269 payload: the payload object to check
270 assert_type: assert that payload is either 'full' or 'delta' (optional)
271 block_size: expected filesystem / payload block size (optional)
272 allow_unhashed: allow operations with unhashed data blobs
273 disabled_tests: list of tests to disable
274
275 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800276 assert payload.is_init, 'uninitialized update payload'
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700277
278 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800279 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700280 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
281 if not _IsPowerOfTwo(self.block_size):
282 raise PayloadError('expected block (%d) size is not a power of two' %
283 self.block_size)
284 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
285 raise PayloadError("invalid assert_type value (`%s')" % assert_type)
286 self.payload_type = assert_type
287 self.allow_unhashed = allow_unhashed
288
289 # Disable specific tests.
290 self.check_dst_pseudo_extents = (
291 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
292 self.check_move_same_src_dst_block = (
293 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
294 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800295
296 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800297 self.sigs_offset = 0
298 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700299 self.old_rootfs_fs_size = 0
300 self.old_kernel_fs_size = 0
301 self.new_rootfs_fs_size = 0
302 self.new_kernel_fs_size = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800303
304 @staticmethod
305 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
306 msg_name=None, linebreak=False, indent=0):
307 """Adds an element from a protobuf message to the payload report.
308
309 Checks to see whether a message contains a given element, and if so adds
310 the element value to the provided report. A missing mandatory element
311 causes an exception to be raised.
312
313 Args:
314 msg: the message containing the element
315 name: the name of the element
316 report: a report object to add the element name/value to
317 is_mandatory: whether or not this element must be present
318 is_submsg: whether this element is itself a message
319 convert: a function for converting the element value for reporting
320 msg_name: the name of the message object (for error reporting)
321 linebreak: whether the value report should induce a line break
322 indent: amount of indent used for reporting the value
323 Returns:
324 A pair consisting of the element value and the generated sub-report for
325 it (if the element is a sub-message, None otherwise). If the element is
326 missing, returns (None, None).
327 Raises:
328 PayloadError if a mandatory element is missing.
329
330 """
331 if not msg.HasField(name):
332 if is_mandatory:
333 raise PayloadError("%smissing mandatory %s '%s'" %
334 (msg_name + ' ' if msg_name else '',
335 'sub-message' if is_submsg else 'field',
336 name))
337 return (None, None)
338
339 value = getattr(msg, name)
340 if is_submsg:
341 return (value, report and report.AddSubReport(name))
342 else:
343 if report:
344 report.AddField(name, convert(value), linebreak=linebreak,
345 indent=indent)
346 return (value, None)
347
348 @staticmethod
349 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
350 linebreak=False, indent=0):
351 """Adds a mandatory field; returning first component from _CheckElem."""
352 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
353 convert=convert, msg_name=msg_name,
354 linebreak=linebreak, indent=indent)[0]
355
356 @staticmethod
357 def _CheckOptionalField(msg, field_name, report, convert=str,
358 linebreak=False, indent=0):
359 """Adds an optional field; returning first component from _CheckElem."""
360 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
361 convert=convert, linebreak=linebreak,
362 indent=indent)[0]
363
364 @staticmethod
365 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
366 """Adds a mandatory sub-message; wrapper for _CheckElem."""
367 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
368 msg_name)
369
370 @staticmethod
371 def _CheckOptionalSubMsg(msg, submsg_name, report):
372 """Adds an optional sub-message; wrapper for _CheckElem."""
373 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
374
375 @staticmethod
376 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
377 """Checks that val1 is None iff val2 is None.
378
379 Args:
380 val1: first value to be compared
381 val2: second value to be compared
382 name1: name of object holding the first value
383 name2: name of object holding the second value
384 obj_name: name of the object containing these values
385 Raises:
386 PayloadError if assertion does not hold.
387
388 """
389 if None in (val1, val2) and val1 is not val2:
390 present, missing = (name1, name2) if val2 is None else (name2, name1)
391 raise PayloadError("'%s' present without '%s'%s" %
392 (present, missing,
393 ' in ' + obj_name if obj_name else ''))
394
395 @staticmethod
396 def _Run(cmd, send_data=None):
397 """Runs a subprocess, returns its output.
398
399 Args:
400 cmd: list of command-line argument for invoking the subprocess
401 send_data: data to feed to the process via its stdin
402 Returns:
403 A tuple containing the stdout and stderr output of the process.
404
405 """
406 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
407 stdout=subprocess.PIPE)
408 return run_process.communicate(input=send_data)
409
410 @staticmethod
411 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
412 """Verifies an actual hash against a signed one.
413
414 Args:
415 sig_data: the raw signature data
416 pubkey_file_name: public key used for verifying signature
417 actual_hash: the actual hash digest
418 sig_name: signature name for error reporting
419 Raises:
420 PayloadError if signature could not be verified.
421
422 """
423 if len(sig_data) != 256:
424 raise PayloadError('%s: signature size (%d) not as expected (256)' %
425 (sig_name, len(sig_data)))
426 signed_data, _ = PayloadChecker._Run(
427 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
428 send_data=sig_data)
429
Gilad Arnold5502b562013-03-08 13:22:31 -0800430 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800431 raise PayloadError('%s: unexpected signed data length (%d)' %
432 (sig_name, len(signed_data)))
433
Gilad Arnold5502b562013-03-08 13:22:31 -0800434 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800435 raise PayloadError('%s: not containing standard ASN.1 prefix' % sig_name)
436
Gilad Arnold5502b562013-03-08 13:22:31 -0800437 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800438 if signed_hash != actual_hash:
439 raise PayloadError('%s: signed hash (%s) different from actual (%s)' %
Gilad Arnold96405372013-05-04 00:24:58 -0700440 (sig_name, common.FormatSha256(signed_hash),
441 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800442
443 @staticmethod
444 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
445 block_name=None):
446 """Checks that a given length fits given block space.
447
448 This ensures that the number of blocks allocated is appropriate for the
449 length of the data residing in these blocks.
450
451 Args:
452 length: the actual length of the data
453 num_blocks: the number of blocks allocated for it
454 block_size: the size of each block in bytes
455 length_name: name of length (used for error reporting)
456 block_name: name of block (used for error reporting)
457 Raises:
458 PayloadError if the aforementioned invariant is not satisfied.
459
460 """
461 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700462 if length > num_blocks * block_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800463 raise PayloadError(
464 '%s (%d) > num %sblocks (%d) * block_size (%d)' %
465 (length_name, length, block_name or '', num_blocks, block_size))
466
467 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700468 if length <= (num_blocks - 1) * block_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800469 raise PayloadError(
470 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d)' %
471 (length_name, length, block_name or '', num_blocks - 1, block_size))
472
Gilad Arnold382df5c2013-05-03 12:49:28 -0700473 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800474 """Checks the payload manifest.
475
476 Args:
477 report: a report object to add to
Gilad Arnold382df5c2013-05-03 12:49:28 -0700478 rootfs_part_size: size of the rootfs partition in bytes
479 kernel_part_size: size of the kernel partition in bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800480 Returns:
481 A tuple consisting of the partition block size used during the update
482 (integer), the signatures block offset and size.
483 Raises:
484 PayloadError if any of the checks fail.
485
486 """
487 manifest = self.payload.manifest
488 report.AddSection('manifest')
489
490 # Check: block_size must exist and match the expected value.
491 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
492 report, 'manifest')
493 if actual_block_size != self.block_size:
494 raise PayloadError('block_size (%d) not as expected (%d)' %
495 (actual_block_size, self.block_size))
496
497 # Check: signatures_offset <==> signatures_size.
498 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
499 report)
500 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
501 report)
502 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
503 'signatures_offset', 'signatures_size', 'manifest')
504
505 # Check: old_kernel_info <==> old_rootfs_info.
506 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
507 'old_kernel_info', report)
508 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
509 'old_rootfs_info', report)
510 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
511 'old_rootfs_info', 'manifest')
512 if oki_msg: # equivalently, ori_msg
513 # Assert/mark delta payload.
514 if self.payload_type == _TYPE_FULL:
515 raise PayloadError(
516 'apparent full payload contains old_{kernel,rootfs}_info')
517 self.payload_type = _TYPE_DELTA
518
519 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700520 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800521 oki_msg, 'size', oki_report, 'old_kernel_info')
522 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
523 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700524 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800525 ori_msg, 'size', ori_report, 'old_rootfs_info')
526 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
527 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700528
529 # Check: old_{kernel,rootfs} size must fit in respective partition.
530 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
531 raise PayloadError(
532 'old kernel content (%d) exceed partition size (%d)' %
533 (self.old_kernel_fs_size, kernel_part_size))
534 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
535 raise PayloadError(
536 'old rootfs content (%d) exceed partition size (%d)' %
537 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800538 else:
539 # Assert/mark full payload.
540 if self.payload_type == _TYPE_DELTA:
541 raise PayloadError(
542 'apparent delta payload missing old_{kernel,rootfs}_info')
543 self.payload_type = _TYPE_FULL
544
545 # Check: new_kernel_info present; contains {size, hash}.
546 nki_msg, nki_report = self._CheckMandatorySubMsg(
547 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700548 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800549 nki_msg, 'size', nki_report, 'new_kernel_info')
550 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
551 convert=common.FormatSha256)
552
553 # Check: new_rootfs_info present; contains {size, hash}.
554 nri_msg, nri_report = self._CheckMandatorySubMsg(
555 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700556 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800557 nri_msg, 'size', nri_report, 'new_rootfs_info')
558 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
559 convert=common.FormatSha256)
560
Gilad Arnold382df5c2013-05-03 12:49:28 -0700561 # Check: new_{kernel,rootfs} size must fit in respective partition.
562 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
563 raise PayloadError(
564 'new kernel content (%d) exceed partition size (%d)' %
565 (self.new_kernel_fs_size, kernel_part_size))
566 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
567 raise PayloadError(
568 'new rootfs content (%d) exceed partition size (%d)' %
569 (self.new_rootfs_fs_size, rootfs_part_size))
570
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800571 # Check: payload must contain at least one operation.
572 if not(len(manifest.install_operations) or
573 len(manifest.kernel_install_operations)):
574 raise PayloadError('payload contains no operations')
575
576 def _CheckLength(self, length, total_blocks, op_name, length_name):
577 """Checks whether a length matches the space designated in extents.
578
579 Args:
580 length: the total length of the data
581 total_blocks: the total number of blocks in extents
582 op_name: operation name (for error reporting)
583 length_name: length name (for error reporting)
584 Raises:
585 PayloadError is there a problem with the length.
586
587 """
588 # Check: length is non-zero.
589 if length == 0:
590 raise PayloadError('%s: %s is zero' % (op_name, length_name))
591
592 # Check that length matches number of blocks.
593 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
594 '%s: %s' % (op_name, length_name))
595
Gilad Arnold382df5c2013-05-03 12:49:28 -0700596 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800597 allow_pseudo=False, allow_signature=False):
598 """Checks a sequence of extents.
599
600 Args:
601 extents: the sequence of extents to check
Gilad Arnold382df5c2013-05-03 12:49:28 -0700602 usable_size: the usable size of the partition to which the extents apply
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800603 block_counters: an array of counters corresponding to the number of blocks
604 name: the name of the extent block
605 allow_pseudo: whether or not pseudo block numbers are allowed
606 allow_signature: whether or not the extents are used for a signature
607 Returns:
608 The total number of blocks in the extents.
609 Raises:
610 PayloadError if any of the entailed checks fails.
611
612 """
613 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800614 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800615 # Check: mandatory fields.
616 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
617 None, ex_name)
618 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
619 ex_name)
620 end_block = start_block + num_blocks
621
622 # Check: num_blocks > 0.
623 if num_blocks == 0:
624 raise PayloadError('%s: extent length is zero' % ex_name)
625
626 if start_block != common.PSEUDO_EXTENT_MARKER:
627 # Check: make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700628 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800629 raise PayloadError(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700630 '%s: extent (%s) exceeds usable partition size (%d)' %
631 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800632
633 # Record block usage.
634 for i in range(start_block, end_block):
635 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800636 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
637 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
638 # signature operation (in which case there has to be exactly one).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800639 raise PayloadError('%s: unexpected pseudo-extent' % ex_name)
640
641 total_num_blocks += num_blocks
642
643 return total_num_blocks
644
645 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
646 """Specific checks for REPLACE/REPLACE_BZ operations.
647
648 Args:
649 op: the operation object from the manifest
650 data_length: the length of the data blob associated with the operation
651 total_dst_blocks: total number of blocks in dst_extents
652 op_name: operation name for error reporting
653 Raises:
654 PayloadError if any check fails.
655
656 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800657 # Check: does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800658 if op.src_extents:
659 raise PayloadError('%s: contains src_extents' % op_name)
660
Gilad Arnold5502b562013-03-08 13:22:31 -0800661 # Check: contains data.
662 if data_length is None:
663 raise PayloadError('%s: missing data_{offset,length}' % op_name)
664
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800665 if op.type == common.OpType.REPLACE:
666 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
667 self.block_size,
668 op_name + '.data_length', 'dst')
669 else:
670 # Check: data_length must be smaller than the alotted dst blocks.
671 if data_length >= total_dst_blocks * self.block_size:
672 raise PayloadError(
673 '%s: data_length (%d) must be less than allotted dst block '
674 'space (%d * %d)' %
675 (op_name, data_length, total_dst_blocks, self.block_size))
676
677 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
678 total_dst_blocks, op_name):
679 """Specific checks for MOVE operations.
680
681 Args:
682 op: the operation object from the manifest
683 data_offset: the offset of a data blob for the operation
684 total_src_blocks: total number of blocks in src_extents
685 total_dst_blocks: total number of blocks in dst_extents
686 op_name: operation name for error reporting
687 Raises:
688 PayloadError if any check fails.
689
690 """
691 # Check: no data_{offset,length}.
692 if data_offset is not None:
693 raise PayloadError('%s: contains data_{offset,length}' % op_name)
694
695 # Check: total src blocks == total dst blocks.
696 if total_src_blocks != total_dst_blocks:
697 raise PayloadError(
698 '%s: total src blocks (%d) != total dst blocks (%d)' %
699 (op_name, total_src_blocks, total_dst_blocks))
700
701 # Check: for all i, i-th src block index != i-th dst block index.
702 i = 0
703 src_extent_iter = iter(op.src_extents)
704 dst_extent_iter = iter(op.dst_extents)
705 src_extent = dst_extent = None
706 src_idx = src_num = dst_idx = dst_num = 0
707 while i < total_src_blocks:
708 # Get the next source extent, if needed.
709 if not src_extent:
710 try:
711 src_extent = src_extent_iter.next()
712 except StopIteration:
713 raise PayloadError('%s: ran out of src extents (%d/%d)' %
714 (op_name, i, total_src_blocks))
715 src_idx = src_extent.start_block
716 src_num = src_extent.num_blocks
717
718 # Get the next dest extent, if needed.
719 if not dst_extent:
720 try:
721 dst_extent = dst_extent_iter.next()
722 except StopIteration:
723 raise PayloadError('%s: ran out of dst extents (%d/%d)' %
724 (op_name, i, total_dst_blocks))
725 dst_idx = dst_extent.start_block
726 dst_num = dst_extent.num_blocks
727
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700728 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnold5502b562013-03-08 13:22:31 -0800729 raise PayloadError('%s: src/dst block number %d is the same (%d)' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800730 (op_name, i, src_idx))
731
732 advance = min(src_num, dst_num)
733 i += advance
734
735 src_idx += advance
736 src_num -= advance
737 if src_num == 0:
738 src_extent = None
739
740 dst_idx += advance
741 dst_num -= advance
742 if dst_num == 0:
743 dst_extent = None
744
Gilad Arnold5502b562013-03-08 13:22:31 -0800745 # Make sure we've exhausted all src/dst extents.
746 if src_extent:
747 raise PayloadError('%s: excess src blocks' % op_name)
748 if dst_extent:
749 raise PayloadError('%s: excess dst blocks' % op_name)
750
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800751 def _CheckBsdiffOperation(self, data_length, total_dst_blocks, op_name):
752 """Specific checks for BSDIFF operations.
753
754 Args:
755 data_length: the length of the data blob associated with the operation
756 total_dst_blocks: total number of blocks in dst_extents
757 op_name: operation name for error reporting
758 Raises:
759 PayloadError if any check fails.
760
761 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800762 # Check: data_{offset,length} present.
763 if data_length is None:
764 raise PayloadError('%s: missing data_{offset,length}' % op_name)
765
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800766 # Check: data_length is strictly smaller than the alotted dst blocks.
767 if data_length >= total_dst_blocks * self.block_size:
768 raise PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800769 '%s: data_length (%d) must be smaller than allotted dst space '
770 '(%d * %d = %d)' %
771 (op_name, data_length, total_dst_blocks, self.block_size,
772 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800773
774 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold382df5c2013-05-03 12:49:28 -0700775 new_block_counters, old_fs_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700776 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800777 """Checks a single update operation.
778
779 Args:
780 op: the operation object
781 op_name: operation name string for error reporting
782 is_last: whether this is the last operation in the sequence
783 old_block_counters: arrays of block read counters
784 new_block_counters: arrays of block write counters
Gilad Arnold382df5c2013-05-03 12:49:28 -0700785 old_fs_size: the old filesystem size in bytes
786 new_usable_size: the overall usable size of the new partition in bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800787 prev_data_offset: offset of last used data bytes
788 allow_signature: whether this may be a signature operation
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800789 blob_hash_counts: counters for hashed/unhashed blobs
790 Returns:
791 The amount of data blob associated with the operation.
792 Raises:
793 PayloadError if any check has failed.
794
795 """
796 # Check extents.
797 total_src_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700798 op.src_extents, old_fs_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800799 op_name + '.src_extents', allow_pseudo=True)
800 allow_signature_in_extents = (allow_signature and is_last and
801 op.type == common.OpType.REPLACE)
802 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700803 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700804 op_name + '.dst_extents',
805 allow_pseudo=(not self.check_dst_pseudo_extents),
806 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800807
808 # Check: data_offset present <==> data_length present.
809 data_offset = self._CheckOptionalField(op, 'data_offset', None)
810 data_length = self._CheckOptionalField(op, 'data_length', None)
811 self._CheckPresentIff(data_offset, data_length, 'data_offset',
812 'data_length', op_name)
813
814 # Check: at least one dst_extent.
815 if not op.dst_extents:
816 raise PayloadError('%s: dst_extents is empty' % op_name)
817
818 # Check {src,dst}_length, if present.
819 if op.HasField('src_length'):
820 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
821 if op.HasField('dst_length'):
822 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
823
824 if op.HasField('data_sha256_hash'):
825 blob_hash_counts['hashed'] += 1
826
827 # Check: operation carries data.
828 if data_offset is None:
829 raise PayloadError(
830 '%s: data_sha256_hash present but no data_{offset,length}' %
831 op_name)
832
833 # Check: hash verifies correctly.
834 # pylint: disable=E1101
835 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
836 data_length))
837 if op.data_sha256_hash != actual_hash.digest():
838 raise PayloadError(
839 '%s: data_sha256_hash (%s) does not match actual hash (%s)' %
Gilad Arnold96405372013-05-04 00:24:58 -0700840 (op_name, common.FormatSha256(op.data_sha256_hash),
841 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800842 elif data_offset is not None:
843 if allow_signature_in_extents:
844 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700845 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800846 blob_hash_counts['unhashed'] += 1
847 else:
848 raise PayloadError('%s: unhashed operation not allowed' % op_name)
849
850 if data_offset is not None:
851 # Check: contiguous use of data section.
852 if data_offset != prev_data_offset:
853 raise PayloadError(
854 '%s: data offset (%d) not matching amount used so far (%d)' %
855 (op_name, data_offset, prev_data_offset))
856
857 # Type-specific checks.
858 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
859 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
860 elif self.payload_type == _TYPE_FULL:
861 raise PayloadError('%s: non-REPLACE operation in a full payload' %
862 op_name)
863 elif op.type == common.OpType.MOVE:
864 self._CheckMoveOperation(op, data_offset, total_src_blocks,
865 total_dst_blocks, op_name)
866 elif op.type == common.OpType.BSDIFF:
867 self._CheckBsdiffOperation(data_length, total_dst_blocks, op_name)
868 else:
869 assert False, 'cannot get here'
870
871 return data_length if data_length is not None else 0
872
Gilad Arnold382df5c2013-05-03 12:49:28 -0700873 def _SizeToNumBlocks(self, size):
874 """Returns the number of blocks needed to contain a given byte size."""
875 return (size + self.block_size - 1) / self.block_size
876
877 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800878 """Returns a freshly initialized array of block counters.
879
880 Args:
Gilad Arnold382df5c2013-05-03 12:49:28 -0700881 total_size: the total block size in bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800882 Returns:
883 An array of unsigned char elements initialized to zero, one for each of
884 the blocks necessary for containing the partition.
885
886 """
Gilad Arnold382df5c2013-05-03 12:49:28 -0700887 return array.array('B', [0] * self._SizeToNumBlocks(total_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800888
Gilad Arnold382df5c2013-05-03 12:49:28 -0700889 def _CheckOperations(self, operations, report, base_name, old_fs_size,
890 new_fs_size, new_usable_size, prev_data_offset,
891 allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800892 """Checks a sequence of update operations.
893
894 Args:
895 operations: the sequence of operations to check
896 report: the report object to add to
897 base_name: the name of the operation block
Gilad Arnold382df5c2013-05-03 12:49:28 -0700898 old_fs_size: the old filesystem size in bytes
899 new_fs_size: the new filesystem size in bytes
900 new_usable_size: the olverall usable size of the new partition in bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800901 prev_data_offset: offset of last used data bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800902 allow_signature: whether this sequence may contain signature operations
903 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -0800904 The total data blob size used.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800905 Raises:
906 PayloadError if any of the checks fails.
907
908 """
909 # The total size of data blobs used by operations scanned thus far.
910 total_data_used = 0
911 # Counts of specific operation types.
912 op_counts = {
913 common.OpType.REPLACE: 0,
914 common.OpType.REPLACE_BZ: 0,
915 common.OpType.MOVE: 0,
916 common.OpType.BSDIFF: 0,
917 }
918 # Total blob sizes for each operation type.
919 op_blob_totals = {
920 common.OpType.REPLACE: 0,
921 common.OpType.REPLACE_BZ: 0,
922 # MOVE operations don't have blobs
923 common.OpType.BSDIFF: 0,
924 }
925 # Counts of hashed vs unhashed operations.
926 blob_hash_counts = {
927 'hashed': 0,
928 'unhashed': 0,
929 }
930 if allow_signature:
931 blob_hash_counts['signature'] = 0
932
933 # Allocate old and new block counters.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700934 old_block_counters = (self._AllocBlockCounters(old_fs_size)
935 if old_fs_size else None)
936 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800937
938 # Process and verify each operation.
939 op_num = 0
940 for op, op_name in common.OperationIter(operations, base_name):
941 op_num += 1
942
943 # Check: type is valid.
944 if op.type not in op_counts.keys():
945 raise PayloadError('%s: invalid type (%d)' % (op_name, op.type))
946 op_counts[op.type] += 1
947
948 is_last = op_num == len(operations)
949 curr_data_used = self._CheckOperation(
950 op, op_name, is_last, old_block_counters, new_block_counters,
Gilad Arnold382df5c2013-05-03 12:49:28 -0700951 old_fs_size, new_usable_size, prev_data_offset + total_data_used,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700952 allow_signature, blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800953 if curr_data_used:
954 op_blob_totals[op.type] += curr_data_used
955 total_data_used += curr_data_used
956
957 # Report totals and breakdown statistics.
958 report.AddField('total operations', op_num)
959 report.AddField(
960 None,
961 histogram.Histogram.FromCountDict(op_counts,
962 key_names=common.OpType.NAMES),
963 indent=1)
964 report.AddField('total blobs', sum(blob_hash_counts.values()))
965 report.AddField(None,
966 histogram.Histogram.FromCountDict(blob_hash_counts),
967 indent=1)
968 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
969 report.AddField(
970 None,
971 histogram.Histogram.FromCountDict(op_blob_totals,
972 formatter=_AddHumanReadableSize,
973 key_names=common.OpType.NAMES),
974 indent=1)
975
976 # Report read/write histograms.
977 if old_block_counters:
978 report.AddField('block read hist',
979 histogram.Histogram.FromKeyList(old_block_counters),
980 linebreak=True, indent=1)
981
Gilad Arnold382df5c2013-05-03 12:49:28 -0700982 new_write_hist = histogram.Histogram.FromKeyList(
983 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
984 report.AddField('block write hist', new_write_hist, linebreak=True,
985 indent=1)
986
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800987 # Check: full update must write each dst block once.
988 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
989 raise PayloadError(
990 '%s: not all blocks written exactly once during full update' %
991 base_name)
992
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800993 return total_data_used
994
995 def _CheckSignatures(self, report, pubkey_file_name):
996 """Checks a payload's signature block."""
997 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
998 sigs = update_metadata_pb2.Signatures()
999 sigs.ParseFromString(sigs_raw)
1000 report.AddSection('signatures')
1001
1002 # Check: at least one signature present.
1003 # pylint: disable=E1101
1004 if not sigs.signatures:
1005 raise PayloadError('signature block is empty')
1006
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001007 last_ops_section = (self.payload.manifest.kernel_install_operations or
1008 self.payload.manifest.install_operations)
1009 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001010 # Check: signatures_{offset,size} must match the last (fake) operation.
1011 if not (fake_sig_op.type == common.OpType.REPLACE and
1012 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001013 self.sigs_size == fake_sig_op.data_length):
1014 raise PayloadError(
1015 'signatures_{offset,size} (%d+%d) does not match last operation '
1016 '(%d+%d)' %
1017 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1018 fake_sig_op.data_length))
1019
1020 # Compute the checksum of all data up to signature blob.
1021 # TODO(garnold) we're re-reading the whole data section into a string
1022 # just to compute the checksum; instead, we could do it incrementally as
1023 # we read the blobs one-by-one, under the assumption that we're reading
1024 # them in order (which currently holds). This should be reconsidered.
1025 payload_hasher = self.payload.manifest_hasher.copy()
1026 common.Read(self.payload.payload_file, self.sigs_offset,
1027 offset=self.payload.data_offset, hasher=payload_hasher)
1028
1029 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1030 sig_report = report.AddSubReport(sig_name)
1031
1032 # Check: signature contains mandatory fields.
1033 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1034 self._CheckMandatoryField(sig, 'data', None, sig_name)
1035 sig_report.AddField('data len', len(sig.data))
1036
1037 # Check: signatures pertains to actual payload hash.
1038 if sig.version == 1:
1039 self._CheckSha256Signature(sig.data, pubkey_file_name,
1040 payload_hasher.digest(), sig_name)
1041 else:
1042 raise PayloadError('unknown signature version (%d)' % sig.version)
1043
1044 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001045 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001046 """Checker entry point, invoking all checks.
1047
1048 Args:
1049 pubkey_file_name: public key used for signature verification
1050 metadata_sig_file: metadata signature, if verification is desired
Gilad Arnold382df5c2013-05-03 12:49:28 -07001051 rootfs_part_size: the size of rootfs partitions in bytes (default: use
1052 reported filesystem size)
1053 kernel_part_size: the size of kernel partitions in bytes (default: use
1054 reported filesystem size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001055 report_out_file: file object to dump the report to
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001056 Raises:
1057 PayloadError if payload verification failed.
1058
1059 """
1060 report = _PayloadReport()
1061
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001062 # Get payload file size.
1063 self.payload.payload_file.seek(0, 2)
1064 payload_file_size = self.payload.payload_file.tell()
1065 self.payload.ResetFile()
1066
1067 try:
1068 # Check metadata signature (if provided).
1069 if metadata_sig_file:
1070 if not pubkey_file_name:
1071 raise PayloadError(
1072 'no public key provided, cannot verify metadata signature')
1073 metadata_sig = base64.b64decode(metadata_sig_file.read())
1074 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1075 self.payload.manifest_hasher.digest(),
1076 'metadata signature')
1077
1078 # Part 1: check the file header.
1079 report.AddSection('header')
1080 # Check: payload version is valid.
1081 if self.payload.header.version != 1:
1082 raise PayloadError('unknown payload version (%d)' %
1083 self.payload.header.version)
1084 report.AddField('version', self.payload.header.version)
1085 report.AddField('manifest len', self.payload.header.manifest_len)
1086
1087 # Part 2: check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001088 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001089 assert self.payload_type, 'payload type should be known by now'
1090
1091 # Part 3: examine rootfs operations.
1092 report.AddSection('rootfs operations')
1093 total_blob_size = self._CheckOperations(
1094 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001095 'install_operations', self.old_rootfs_fs_size,
1096 self.new_rootfs_fs_size,
1097 rootfs_part_size if rootfs_part_size else self.new_rootfs_fs_size,
1098 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001099
1100 # Part 4: examine kernel operations.
1101 report.AddSection('kernel operations')
1102 total_blob_size += self._CheckOperations(
1103 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001104 'kernel_install_operations', self.old_kernel_fs_size,
1105 self.new_kernel_fs_size,
1106 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1107 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001108
1109 # Check: operations data reach the end of the payload file.
1110 used_payload_size = self.payload.data_offset + total_blob_size
1111 if used_payload_size != payload_file_size:
1112 raise PayloadError(
1113 'used payload size (%d) different from actual file size (%d)' %
1114 (used_payload_size, payload_file_size))
1115
1116 # Part 5: handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001117 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001118 if not pubkey_file_name:
1119 raise PayloadError(
1120 'no public key provided, cannot verify payload signature')
1121 self._CheckSignatures(report, pubkey_file_name)
1122
1123 # Part 6: summary.
1124 report.AddSection('summary')
1125 report.AddField('update type', self.payload_type)
1126
1127 report.Finalize()
1128 finally:
1129 if report_out_file:
1130 report.Dump(report_out_file)