blob: a9edce3f5796d1cee58ab802fd00aaef73937932 [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=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700266 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700267
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 Arnold4f50b412013-05-14 09:19:17 -0700775 new_block_counters, old_usable_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 Arnold4f50b412013-05-14 09:19:17 -0700785 old_usable_size: the overall usable size for src data in bytes
786 new_usable_size: the overall usable size for dst data 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 Arnold4f50b412013-05-14 09:19:17 -0700798 op.src_extents, old_usable_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
Gilad Arnold4f50b412013-05-14 09:19:17 -0700900 new_usable_size: the overall 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 Arnold4f50b412013-05-14 09:19:17 -0700934 old_block_counters = (self._AllocBlockCounters(new_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700935 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 Arnold4f50b412013-05-14 09:19:17 -0700951 new_usable_size if old_fs_size else 0, new_usable_size,
952 prev_data_offset + total_data_used, allow_signature,
953 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800954 if curr_data_used:
955 op_blob_totals[op.type] += curr_data_used
956 total_data_used += curr_data_used
957
958 # Report totals and breakdown statistics.
959 report.AddField('total operations', op_num)
960 report.AddField(
961 None,
962 histogram.Histogram.FromCountDict(op_counts,
963 key_names=common.OpType.NAMES),
964 indent=1)
965 report.AddField('total blobs', sum(blob_hash_counts.values()))
966 report.AddField(None,
967 histogram.Histogram.FromCountDict(blob_hash_counts),
968 indent=1)
969 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
970 report.AddField(
971 None,
972 histogram.Histogram.FromCountDict(op_blob_totals,
973 formatter=_AddHumanReadableSize,
974 key_names=common.OpType.NAMES),
975 indent=1)
976
977 # Report read/write histograms.
978 if old_block_counters:
979 report.AddField('block read hist',
980 histogram.Histogram.FromKeyList(old_block_counters),
981 linebreak=True, indent=1)
982
Gilad Arnold382df5c2013-05-03 12:49:28 -0700983 new_write_hist = histogram.Histogram.FromKeyList(
984 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
985 report.AddField('block write hist', new_write_hist, linebreak=True,
986 indent=1)
987
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800988 # Check: full update must write each dst block once.
989 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
990 raise PayloadError(
991 '%s: not all blocks written exactly once during full update' %
992 base_name)
993
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800994 return total_data_used
995
996 def _CheckSignatures(self, report, pubkey_file_name):
997 """Checks a payload's signature block."""
998 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
999 sigs = update_metadata_pb2.Signatures()
1000 sigs.ParseFromString(sigs_raw)
1001 report.AddSection('signatures')
1002
1003 # Check: at least one signature present.
1004 # pylint: disable=E1101
1005 if not sigs.signatures:
1006 raise PayloadError('signature block is empty')
1007
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001008 last_ops_section = (self.payload.manifest.kernel_install_operations or
1009 self.payload.manifest.install_operations)
1010 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001011 # Check: signatures_{offset,size} must match the last (fake) operation.
1012 if not (fake_sig_op.type == common.OpType.REPLACE and
1013 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001014 self.sigs_size == fake_sig_op.data_length):
1015 raise PayloadError(
1016 'signatures_{offset,size} (%d+%d) does not match last operation '
1017 '(%d+%d)' %
1018 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1019 fake_sig_op.data_length))
1020
1021 # Compute the checksum of all data up to signature blob.
1022 # TODO(garnold) we're re-reading the whole data section into a string
1023 # just to compute the checksum; instead, we could do it incrementally as
1024 # we read the blobs one-by-one, under the assumption that we're reading
1025 # them in order (which currently holds). This should be reconsidered.
1026 payload_hasher = self.payload.manifest_hasher.copy()
1027 common.Read(self.payload.payload_file, self.sigs_offset,
1028 offset=self.payload.data_offset, hasher=payload_hasher)
1029
1030 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1031 sig_report = report.AddSubReport(sig_name)
1032
1033 # Check: signature contains mandatory fields.
1034 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1035 self._CheckMandatoryField(sig, 'data', None, sig_name)
1036 sig_report.AddField('data len', len(sig.data))
1037
1038 # Check: signatures pertains to actual payload hash.
1039 if sig.version == 1:
1040 self._CheckSha256Signature(sig.data, pubkey_file_name,
1041 payload_hasher.digest(), sig_name)
1042 else:
1043 raise PayloadError('unknown signature version (%d)' % sig.version)
1044
1045 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001046 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001047 """Checker entry point, invoking all checks.
1048
1049 Args:
1050 pubkey_file_name: public key used for signature verification
1051 metadata_sig_file: metadata signature, if verification is desired
Gilad Arnold382df5c2013-05-03 12:49:28 -07001052 rootfs_part_size: the size of rootfs partitions in bytes (default: use
1053 reported filesystem size)
1054 kernel_part_size: the size of kernel partitions in bytes (default: use
1055 reported filesystem size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001056 report_out_file: file object to dump the report to
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001057 Raises:
1058 PayloadError if payload verification failed.
1059
1060 """
1061 report = _PayloadReport()
1062
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001063 # Get payload file size.
1064 self.payload.payload_file.seek(0, 2)
1065 payload_file_size = self.payload.payload_file.tell()
1066 self.payload.ResetFile()
1067
1068 try:
1069 # Check metadata signature (if provided).
1070 if metadata_sig_file:
1071 if not pubkey_file_name:
1072 raise PayloadError(
1073 'no public key provided, cannot verify metadata signature')
1074 metadata_sig = base64.b64decode(metadata_sig_file.read())
1075 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1076 self.payload.manifest_hasher.digest(),
1077 'metadata signature')
1078
1079 # Part 1: check the file header.
1080 report.AddSection('header')
1081 # Check: payload version is valid.
1082 if self.payload.header.version != 1:
1083 raise PayloadError('unknown payload version (%d)' %
1084 self.payload.header.version)
1085 report.AddField('version', self.payload.header.version)
1086 report.AddField('manifest len', self.payload.header.manifest_len)
1087
1088 # Part 2: check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001089 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001090 assert self.payload_type, 'payload type should be known by now'
1091
1092 # Part 3: examine rootfs operations.
1093 report.AddSection('rootfs operations')
1094 total_blob_size = self._CheckOperations(
1095 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001096 'install_operations', self.old_rootfs_fs_size,
1097 self.new_rootfs_fs_size,
1098 rootfs_part_size if rootfs_part_size else self.new_rootfs_fs_size,
1099 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001100
1101 # Part 4: examine kernel operations.
1102 report.AddSection('kernel operations')
1103 total_blob_size += self._CheckOperations(
1104 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001105 'kernel_install_operations', self.old_kernel_fs_size,
1106 self.new_kernel_fs_size,
1107 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1108 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001109
1110 # Check: operations data reach the end of the payload file.
1111 used_payload_size = self.payload.data_offset + total_blob_size
1112 if used_payload_size != payload_file_size:
1113 raise PayloadError(
1114 'used payload size (%d) different from actual file size (%d)' %
1115 (used_payload_size, payload_file_size))
1116
1117 # Part 5: handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001118 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001119 if not pubkey_file_name:
1120 raise PayloadError(
1121 'no public key provided, cannot verify payload signature')
1122 self._CheckSignatures(report, pubkey_file_name)
1123
1124 # Part 6: summary.
1125 report.AddSection('summary')
1126 report.AddField('update type', self.payload_type)
1127
1128 report.Finalize()
1129 finally:
1130 if report_out_file:
1131 report.Dump(report_out_file)