blob: 47461af78066dd30f62b98f92555d6cc6022b9f0 [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(...)
Gilad Arnold553b0ec2013-01-26 01:00:39 -080013"""
14
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080015from __future__ import print_function
16
Gilad Arnold553b0ec2013-01-26 01:00:39 -080017import array
18import base64
19import hashlib
Gilad Arnoldcb638912013-06-24 04:57:11 -070020import itertools
Gilad Arnold9b90c932013-05-22 17:12:56 -070021import os
Gilad Arnold553b0ec2013-01-26 01:00:39 -080022import subprocess
23
24import common
Gilad Arnoldcb638912013-06-24 04:57:11 -070025import error
Gilad Arnold553b0ec2013-01-26 01:00:39 -080026import format_utils
27import histogram
28import update_metadata_pb2
29
30
31#
Gilad Arnold9b90c932013-05-22 17:12:56 -070032# Constants.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080033#
Gilad Arnoldcb638912013-06-24 04:57:11 -070034
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070035_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents'
36_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
37_CHECK_PAYLOAD_SIG = 'payload-sig'
38CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070039 _CHECK_DST_PSEUDO_EXTENTS,
40 _CHECK_MOVE_SAME_SRC_DST_BLOCK,
41 _CHECK_PAYLOAD_SIG,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070042)
43
Gilad Arnold553b0ec2013-01-26 01:00:39 -080044_TYPE_FULL = 'full'
45_TYPE_DELTA = 'delta'
46
47_DEFAULT_BLOCK_SIZE = 4096
48
Gilad Arnold9b90c932013-05-22 17:12:56 -070049_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem'
50_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
51 _DEFAULT_PUBKEY_BASE_NAME)
52
Gilad Arnold0d575cd2015-07-13 17:29:21 -070053# Supported minor version map to payload types allowed to be using them.
54_SUPPORTED_MINOR_VERSIONS = {
55 0: (_TYPE_FULL,),
56 1: (_TYPE_DELTA,),
57 2: (_TYPE_DELTA,),
58}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080059
Gilad Arnold06eea332015-07-13 18:06:33 -070060_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
61
Gilad Arnold553b0ec2013-01-26 01:00:39 -080062#
63# Helper functions.
64#
Gilad Arnoldcb638912013-06-24 04:57:11 -070065
Gilad Arnold553b0ec2013-01-26 01:00:39 -080066def _IsPowerOfTwo(val):
67 """Returns True iff val is a power of two."""
68 return val > 0 and (val & (val - 1)) == 0
69
70
71def _AddFormat(format_func, value):
72 """Adds a custom formatted representation to ordinary string representation.
73
74 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070075 format_func: A value formatter.
76 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080077
Gilad Arnold553b0ec2013-01-26 01:00:39 -080078 Returns:
79 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080080 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070081 ret = str(value)
82 formatted_str = format_func(value)
83 if formatted_str:
84 ret += ' (%s)' % formatted_str
85 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -080086
87
88def _AddHumanReadableSize(size):
89 """Adds a human readable representation to a byte size value."""
90 return _AddFormat(format_utils.BytesToHumanReadable, size)
91
92
93#
94# Payload report generator.
95#
Gilad Arnoldcb638912013-06-24 04:57:11 -070096
Gilad Arnold553b0ec2013-01-26 01:00:39 -080097class _PayloadReport(object):
98 """A payload report generator.
99
100 A report is essentially a sequence of nodes, which represent data points. It
101 is initialized to have a "global", untitled section. A node may be a
102 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800103 """
104
Gilad Arnoldcb638912013-06-24 04:57:11 -0700105 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800106 class Node(object):
107 """A report node interface."""
108
109 @staticmethod
110 def _Indent(indent, line):
111 """Indents a line by a given indentation amount.
112
113 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700114 indent: The indentation amount.
115 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800116
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800117 Returns:
118 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800119 """
120 return '%*s%s' % (indent, '', line)
121
122 def GenerateLines(self, base_indent, sub_indent, curr_section):
123 """Generates the report lines for this node.
124
125 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700126 base_indent: Base indentation for each line.
127 sub_indent: Additional indentation for sub-nodes.
128 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800129
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800130 Returns:
131 A pair consisting of a list of properly indented report lines and a new
132 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800133 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700134 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800135
136 class FieldNode(Node):
137 """A field report node, representing a (name, value) pair."""
138
139 def __init__(self, name, value, linebreak, indent):
140 super(_PayloadReport.FieldNode, self).__init__()
141 self.name = name
142 self.value = value
143 self.linebreak = linebreak
144 self.indent = indent
145
146 def GenerateLines(self, base_indent, sub_indent, curr_section):
147 """Generates a properly formatted 'name : value' entry."""
148 report_output = ''
149 if self.name:
150 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
151 value_lines = str(self.value).splitlines()
152 if self.linebreak and self.name:
153 report_output += '\n' + '\n'.join(
154 ['%*s%s' % (self.indent, '', line) for line in value_lines])
155 else:
156 if self.name:
157 report_output += ' '
158 report_output += '%*s' % (self.indent, '')
159 cont_line_indent = len(report_output)
160 indented_value_lines = [value_lines[0]]
161 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
162 for line in value_lines[1:]])
163 report_output += '\n'.join(indented_value_lines)
164
165 report_lines = [self._Indent(base_indent, line + '\n')
166 for line in report_output.split('\n')]
167 return report_lines, curr_section
168
169 class SubReportNode(Node):
170 """A sub-report node, representing a nested report."""
171
172 def __init__(self, title, report):
173 super(_PayloadReport.SubReportNode, self).__init__()
174 self.title = title
175 self.report = report
176
177 def GenerateLines(self, base_indent, sub_indent, curr_section):
178 """Recurse with indentation."""
179 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
180 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
181 sub_indent))
182 return report_lines, curr_section
183
184 class SectionNode(Node):
185 """A section header node."""
186
187 def __init__(self, title=None):
188 super(_PayloadReport.SectionNode, self).__init__()
189 self.title = title
190 self.max_field_name_len = 0
191
192 def GenerateLines(self, base_indent, sub_indent, curr_section):
193 """Dump a title line, return self as the (new) current section."""
194 report_lines = []
195 if self.title:
196 report_lines.append(self._Indent(base_indent,
197 '=== %s ===\n' % self.title))
198 return report_lines, self
199
200 def __init__(self):
201 self.report = []
202 self.last_section = self.global_section = self.SectionNode()
203 self.is_finalized = False
204
205 def GenerateLines(self, base_indent, sub_indent):
206 """Generates the lines in the report, properly indented.
207
208 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700209 base_indent: The indentation used for root-level report lines.
210 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800211
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800212 Returns:
213 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800214 """
215 report_lines = []
216 curr_section = self.global_section
217 for node in self.report:
218 node_report_lines, curr_section = node.GenerateLines(
219 base_indent, sub_indent, curr_section)
220 report_lines.extend(node_report_lines)
221
222 return report_lines
223
224 def Dump(self, out_file, base_indent=0, sub_indent=2):
225 """Dumps the report to a file.
226
227 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700228 out_file: File object to output the content to.
229 base_indent: Base indentation for report lines.
230 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800231 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800232 report_lines = self.GenerateLines(base_indent, sub_indent)
233 if report_lines and not self.is_finalized:
234 report_lines.append('(incomplete report)\n')
235
236 for line in report_lines:
237 out_file.write(line)
238
239 def AddField(self, name, value, linebreak=False, indent=0):
240 """Adds a field/value pair to the payload report.
241
242 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700243 name: The field's name.
244 value: The field's value.
245 linebreak: Whether the value should be printed on a new line.
246 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800247 """
248 assert not self.is_finalized
249 if name and self.last_section.max_field_name_len < len(name):
250 self.last_section.max_field_name_len = len(name)
251 self.report.append(self.FieldNode(name, value, linebreak, indent))
252
253 def AddSubReport(self, title):
254 """Adds and returns a sub-report with a title."""
255 assert not self.is_finalized
256 sub_report = self.SubReportNode(title, type(self)())
257 self.report.append(sub_report)
258 return sub_report.report
259
260 def AddSection(self, title):
261 """Adds a new section title."""
262 assert not self.is_finalized
263 self.last_section = self.SectionNode(title)
264 self.report.append(self.last_section)
265
266 def Finalize(self):
267 """Seals the report, marking it as complete."""
268 self.is_finalized = True
269
270
271#
272# Payload verification.
273#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700274
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800275class PayloadChecker(object):
276 """Checking the integrity of an update payload.
277
278 This is a short-lived object whose purpose is to isolate the logic used for
279 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800280 """
281
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700282 def __init__(self, payload, assert_type=None, block_size=0,
283 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700284 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700285
286 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700287 payload: The payload object to check.
288 assert_type: Assert that payload is either 'full' or 'delta' (optional).
289 block_size: Expected filesystem / payload block size (optional).
290 allow_unhashed: Allow operations with unhashed data blobs.
291 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700292 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700293 if not payload.is_init:
294 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700295
296 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800297 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700298 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
299 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700300 raise error.PayloadError(
301 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700302 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700303 raise error.PayloadError('Invalid assert_type value (%r).' %
304 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700305 self.payload_type = assert_type
306 self.allow_unhashed = allow_unhashed
307
308 # Disable specific tests.
309 self.check_dst_pseudo_extents = (
310 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
311 self.check_move_same_src_dst_block = (
312 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
313 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800314
315 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800316 self.sigs_offset = 0
317 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700318 self.old_rootfs_fs_size = 0
319 self.old_kernel_fs_size = 0
320 self.new_rootfs_fs_size = 0
321 self.new_kernel_fs_size = 0
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700322 self.minor_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800323
324 @staticmethod
325 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
326 msg_name=None, linebreak=False, indent=0):
327 """Adds an element from a protobuf message to the payload report.
328
329 Checks to see whether a message contains a given element, and if so adds
330 the element value to the provided report. A missing mandatory element
331 causes an exception to be raised.
332
333 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700334 msg: The message containing the element.
335 name: The name of the element.
336 report: A report object to add the element name/value to.
337 is_mandatory: Whether or not this element must be present.
338 is_submsg: Whether this element is itself a message.
339 convert: A function for converting the element value for reporting.
340 msg_name: The name of the message object (for error reporting).
341 linebreak: Whether the value report should induce a line break.
342 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800343
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800344 Returns:
345 A pair consisting of the element value and the generated sub-report for
346 it (if the element is a sub-message, None otherwise). If the element is
347 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800348
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800349 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700350 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800351 """
352 if not msg.HasField(name):
353 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700354 raise error.PayloadError('%smissing mandatory %s %r.' %
355 (msg_name + ' ' if msg_name else '',
356 'sub-message' if is_submsg else 'field',
357 name))
358 return None, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800359
360 value = getattr(msg, name)
361 if is_submsg:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700362 return value, report and report.AddSubReport(name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800363 else:
364 if report:
365 report.AddField(name, convert(value), linebreak=linebreak,
366 indent=indent)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700367 return value, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800368
369 @staticmethod
370 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
371 linebreak=False, indent=0):
372 """Adds a mandatory field; returning first component from _CheckElem."""
373 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
374 convert=convert, msg_name=msg_name,
375 linebreak=linebreak, indent=indent)[0]
376
377 @staticmethod
378 def _CheckOptionalField(msg, field_name, report, convert=str,
379 linebreak=False, indent=0):
380 """Adds an optional field; returning first component from _CheckElem."""
381 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
382 convert=convert, linebreak=linebreak,
383 indent=indent)[0]
384
385 @staticmethod
386 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
387 """Adds a mandatory sub-message; wrapper for _CheckElem."""
388 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
389 msg_name)
390
391 @staticmethod
392 def _CheckOptionalSubMsg(msg, submsg_name, report):
393 """Adds an optional sub-message; wrapper for _CheckElem."""
394 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
395
396 @staticmethod
397 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
398 """Checks that val1 is None iff val2 is None.
399
400 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700401 val1: first value to be compared.
402 val2: second value to be compared.
403 name1: name of object holding the first value.
404 name2: name of object holding the second value.
405 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800406
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800407 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700408 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800409 """
410 if None in (val1, val2) and val1 is not val2:
411 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700412 raise error.PayloadError('%r present without %r%s.' %
413 (present, missing,
414 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800415
416 @staticmethod
417 def _Run(cmd, send_data=None):
418 """Runs a subprocess, returns its output.
419
420 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700421 cmd: Sequence of command-line argument for invoking the subprocess.
422 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800423
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800424 Returns:
425 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800426 """
427 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
428 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700429 try:
430 result = run_process.communicate(input=send_data)
431 finally:
432 exit_code = run_process.wait()
433
434 if exit_code:
435 raise RuntimeError('Subprocess %r failed with code %r.' %
436 (cmd, exit_code))
437
438 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800439
440 @staticmethod
441 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
442 """Verifies an actual hash against a signed one.
443
444 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700445 sig_data: The raw signature data.
446 pubkey_file_name: Public key used for verifying signature.
447 actual_hash: The actual hash digest.
448 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800449
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800450 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700451 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800452 """
453 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700454 raise error.PayloadError(
455 '%s: signature size (%d) not as expected (256).' %
456 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800457 signed_data, _ = PayloadChecker._Run(
458 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
459 send_data=sig_data)
460
Gilad Arnold5502b562013-03-08 13:22:31 -0800461 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700462 raise error.PayloadError('%s: unexpected signed data length (%d).' %
463 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800464
Gilad Arnold5502b562013-03-08 13:22:31 -0800465 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700466 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
467 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800468
Gilad Arnold5502b562013-03-08 13:22:31 -0800469 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800470 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700471 raise error.PayloadError(
472 '%s: signed hash (%s) different from actual (%s).' %
473 (sig_name, common.FormatSha256(signed_hash),
474 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800475
476 @staticmethod
477 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
478 block_name=None):
479 """Checks that a given length fits given block space.
480
481 This ensures that the number of blocks allocated is appropriate for the
482 length of the data residing in these blocks.
483
484 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700485 length: The actual length of the data.
486 num_blocks: The number of blocks allocated for it.
487 block_size: The size of each block in bytes.
488 length_name: Name of length (used for error reporting).
489 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800490
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800491 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700492 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800493 """
494 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700495 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700496 raise error.PayloadError(
497 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800498 (length_name, length, block_name or '', num_blocks, block_size))
499
500 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700501 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700502 raise error.PayloadError(
503 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800504 (length_name, length, block_name or '', num_blocks - 1, block_size))
505
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700506 def _CheckManifestMinorVersion(self, report):
507 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800508
509 Args:
510 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800511
512 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700513 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800514 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700515 self.minor_version = self._CheckOptionalField(self.payload.manifest,
516 'minor_version', report)
517 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
518 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800519 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700520 'Minor version %d not compatible with payload type %s.' %
521 (self.minor_version, self.payload_type))
522 elif self.minor_version is None:
523 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800524 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700525 raise error.PayloadError('Unsupported minor version: %d' %
526 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800527
Gilad Arnold382df5c2013-05-03 12:49:28 -0700528 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800529 """Checks the payload manifest.
530
531 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700532 report: A report object to add to.
533 rootfs_part_size: Size of the rootfs partition in bytes.
534 kernel_part_size: Size of the kernel partition in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800535
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800536 Returns:
537 A tuple consisting of the partition block size used during the update
538 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800539
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800540 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700541 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800542 """
543 manifest = self.payload.manifest
544 report.AddSection('manifest')
545
546 # Check: block_size must exist and match the expected value.
547 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
548 report, 'manifest')
549 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700550 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
551 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800552
553 # Check: signatures_offset <==> signatures_size.
554 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
555 report)
556 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
557 report)
558 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
559 'signatures_offset', 'signatures_size', 'manifest')
560
561 # Check: old_kernel_info <==> old_rootfs_info.
562 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
563 'old_kernel_info', report)
564 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
565 'old_rootfs_info', report)
566 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
567 'old_rootfs_info', 'manifest')
568 if oki_msg: # equivalently, ori_msg
569 # Assert/mark delta payload.
570 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700571 raise error.PayloadError(
572 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800573 self.payload_type = _TYPE_DELTA
574
575 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700576 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800577 oki_msg, 'size', oki_report, 'old_kernel_info')
578 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
579 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700580 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800581 ori_msg, 'size', ori_report, 'old_rootfs_info')
582 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
583 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700584
585 # Check: old_{kernel,rootfs} size must fit in respective partition.
586 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700587 raise error.PayloadError(
588 'Old kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700589 (self.old_kernel_fs_size, kernel_part_size))
590 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700591 raise error.PayloadError(
592 'Old rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700593 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800594 else:
595 # Assert/mark full payload.
596 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700597 raise error.PayloadError(
598 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800599 self.payload_type = _TYPE_FULL
600
601 # Check: new_kernel_info present; contains {size, hash}.
602 nki_msg, nki_report = self._CheckMandatorySubMsg(
603 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700604 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800605 nki_msg, 'size', nki_report, 'new_kernel_info')
606 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
607 convert=common.FormatSha256)
608
609 # Check: new_rootfs_info present; contains {size, hash}.
610 nri_msg, nri_report = self._CheckMandatorySubMsg(
611 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700612 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800613 nri_msg, 'size', nri_report, 'new_rootfs_info')
614 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
615 convert=common.FormatSha256)
616
Gilad Arnold382df5c2013-05-03 12:49:28 -0700617 # Check: new_{kernel,rootfs} size must fit in respective partition.
618 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700619 raise error.PayloadError(
620 'New kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700621 (self.new_kernel_fs_size, kernel_part_size))
622 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700623 raise error.PayloadError(
624 'New rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700625 (self.new_rootfs_fs_size, rootfs_part_size))
626
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800627 # Check: minor_version makes sense for the payload type. This check should
628 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700629 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800630
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800631 def _CheckLength(self, length, total_blocks, op_name, length_name):
632 """Checks whether a length matches the space designated in extents.
633
634 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700635 length: The total length of the data.
636 total_blocks: The total number of blocks in extents.
637 op_name: Operation name (for error reporting).
638 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800639
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800640 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700641 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800642 """
643 # Check: length is non-zero.
644 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700645 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800646
647 # Check that length matches number of blocks.
648 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
649 '%s: %s' % (op_name, length_name))
650
Gilad Arnold382df5c2013-05-03 12:49:28 -0700651 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800652 allow_pseudo=False, allow_signature=False):
653 """Checks a sequence of extents.
654
655 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700656 extents: The sequence of extents to check.
657 usable_size: The usable size of the partition to which the extents apply.
658 block_counters: Array of counters corresponding to the number of blocks.
659 name: The name of the extent block.
660 allow_pseudo: Whether or not pseudo block numbers are allowed.
661 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800662
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800663 Returns:
664 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800665
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800666 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700667 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800668 """
669 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800670 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700671 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800672 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
673 None, ex_name)
674 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
675 ex_name)
676 end_block = start_block + num_blocks
677
678 # Check: num_blocks > 0.
679 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700680 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800681
682 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700683 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700684 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700685 raise error.PayloadError(
686 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700687 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800688
689 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700690 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800691 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800692 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
693 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
694 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700695 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800696
697 total_num_blocks += num_blocks
698
699 return total_num_blocks
700
701 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
702 """Specific checks for REPLACE/REPLACE_BZ operations.
703
704 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700705 op: The operation object from the manifest.
706 data_length: The length of the data blob associated with the operation.
707 total_dst_blocks: Total number of blocks in dst_extents.
708 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800709
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800710 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700711 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800712 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700713 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800714 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700715 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800716
Gilad Arnoldcb638912013-06-24 04:57:11 -0700717 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800718 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700719 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800720
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800721 if op.type == common.OpType.REPLACE:
722 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
723 self.block_size,
724 op_name + '.data_length', 'dst')
725 else:
726 # Check: data_length must be smaller than the alotted dst blocks.
727 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700728 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800729 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700730 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800731 (op_name, data_length, total_dst_blocks, self.block_size))
732
733 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
734 total_dst_blocks, op_name):
735 """Specific checks for MOVE operations.
736
737 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700738 op: The operation object from the manifest.
739 data_offset: The offset of a data blob for the operation.
740 total_src_blocks: Total number of blocks in src_extents.
741 total_dst_blocks: Total number of blocks in dst_extents.
742 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800743
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800744 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700745 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800746 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700747 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800748 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700749 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800750
Gilad Arnoldcb638912013-06-24 04:57:11 -0700751 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800752 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700753 raise error.PayloadError(
754 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800755 (op_name, total_src_blocks, total_dst_blocks))
756
Gilad Arnoldcb638912013-06-24 04:57:11 -0700757 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800758 i = 0
759 src_extent_iter = iter(op.src_extents)
760 dst_extent_iter = iter(op.dst_extents)
761 src_extent = dst_extent = None
762 src_idx = src_num = dst_idx = dst_num = 0
763 while i < total_src_blocks:
764 # Get the next source extent, if needed.
765 if not src_extent:
766 try:
767 src_extent = src_extent_iter.next()
768 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700769 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
770 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800771 src_idx = src_extent.start_block
772 src_num = src_extent.num_blocks
773
774 # Get the next dest extent, if needed.
775 if not dst_extent:
776 try:
777 dst_extent = dst_extent_iter.next()
778 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700779 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
780 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800781 dst_idx = dst_extent.start_block
782 dst_num = dst_extent.num_blocks
783
Allie Woodb065e132015-04-24 10:20:27 -0700784 # Check: start block is not 0. See crbug/480751; there are still versions
785 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
786 # so we need to fail payloads that try to MOVE to/from block 0.
787 if src_idx == 0 or dst_idx == 0:
788 raise error.PayloadError(
789 '%s: MOVE operation cannot have extent with start block 0' %
790 op_name)
791
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700792 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700793 raise error.PayloadError(
794 '%s: src/dst block number %d is the same (%d).' %
795 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800796
797 advance = min(src_num, dst_num)
798 i += advance
799
800 src_idx += advance
801 src_num -= advance
802 if src_num == 0:
803 src_extent = None
804
805 dst_idx += advance
806 dst_num -= advance
807 if dst_num == 0:
808 dst_extent = None
809
Gilad Arnold5502b562013-03-08 13:22:31 -0800810 # Make sure we've exhausted all src/dst extents.
811 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700812 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800813 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700814 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800815
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800816 def _CheckBsdiffOperation(self, data_length, total_dst_blocks, op_name):
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800817 """Specific checks for BSDIFF and SOURCE_BSDIFF operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800818
819 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700820 data_length: The length of the data blob associated with the operation.
821 total_dst_blocks: Total number of blocks in dst_extents.
822 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800823
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800824 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700825 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800826 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800827 # Check: data_{offset,length} present.
828 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700829 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800830
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800831 # Check: data_length is strictly smaller than the alotted dst blocks.
832 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700833 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800834 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700835 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800836 (op_name, data_length, total_dst_blocks, self.block_size,
837 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800838
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800839 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
840 total_dst_blocks, op_name):
841 """Specific checks for SOURCE_COPY.
842
843 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800844 data_offset: The offset of a data blob for the operation.
845 total_src_blocks: Total number of blocks in src_extents.
846 total_dst_blocks: Total number of blocks in dst_extents.
847 op_name: Operation name for error reporting.
848
849 Raises:
850 error.PayloadError if any check fails.
851 """
852 # Check: No data_{offset,length}.
853 if data_offset is not None:
854 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
855
856 # Check: total_src_blocks == total_dst_blocks.
857 if total_src_blocks != total_dst_blocks:
858 raise error.PayloadError(
859 '%s: total src blocks (%d) != total dst blocks (%d).' %
860 (op_name, total_src_blocks, total_dst_blocks))
861
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800862 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700863 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700864 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800865 """Checks a single update operation.
866
867 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700868 op: The operation object.
869 op_name: Operation name string for error reporting.
870 is_last: Whether this is the last operation in the sequence.
871 old_block_counters: Arrays of block read counters.
872 new_block_counters: Arrays of block write counters.
873 old_usable_size: The overall usable size for src data in bytes.
874 new_usable_size: The overall usable size for dst data in bytes.
875 prev_data_offset: Offset of last used data bytes.
876 allow_signature: Whether this may be a signature operation.
877 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800878
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800879 Returns:
880 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800881
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800882 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700883 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800884 """
885 # Check extents.
886 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700887 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800888 op_name + '.src_extents', allow_pseudo=True)
889 allow_signature_in_extents = (allow_signature and is_last and
890 op.type == common.OpType.REPLACE)
891 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700892 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700893 op_name + '.dst_extents',
894 allow_pseudo=(not self.check_dst_pseudo_extents),
895 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800896
897 # Check: data_offset present <==> data_length present.
898 data_offset = self._CheckOptionalField(op, 'data_offset', None)
899 data_length = self._CheckOptionalField(op, 'data_length', None)
900 self._CheckPresentIff(data_offset, data_length, 'data_offset',
901 'data_length', op_name)
902
Gilad Arnoldcb638912013-06-24 04:57:11 -0700903 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800904 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700905 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800906
907 # Check {src,dst}_length, if present.
908 if op.HasField('src_length'):
909 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
910 if op.HasField('dst_length'):
911 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
912
913 if op.HasField('data_sha256_hash'):
914 blob_hash_counts['hashed'] += 1
915
Gilad Arnoldcb638912013-06-24 04:57:11 -0700916 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800917 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700918 raise error.PayloadError(
919 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800920 op_name)
921
Gilad Arnoldcb638912013-06-24 04:57:11 -0700922 # Check: Hash verifies correctly.
923 # pylint cannot find the method in hashlib, for some reason.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800924 # pylint: disable=E1101
925 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
926 data_length))
927 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700928 raise error.PayloadError(
929 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700930 (op_name, common.FormatSha256(op.data_sha256_hash),
931 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800932 elif data_offset is not None:
933 if allow_signature_in_extents:
934 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700935 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800936 blob_hash_counts['unhashed'] += 1
937 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700938 raise error.PayloadError('%s: unhashed operation not allowed.' %
939 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800940
941 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700942 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800943 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700944 raise error.PayloadError(
945 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800946 (op_name, data_offset, prev_data_offset))
947
948 # Type-specific checks.
949 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
950 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700951 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800952 self._CheckMoveOperation(op, data_offset, total_src_blocks,
953 total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700954 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800955 self._CheckBsdiffOperation(data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700956 elif op.type == common.OpType.SOURCE_COPY and self.minor_version == 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800957 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
958 total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700959 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version == 2:
Allie Wood7cf9f132015-02-26 14:28:19 -0800960 self._CheckBsdiffOperation(data_length, total_dst_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800961 else:
Allie Wood7cf9f132015-02-26 14:28:19 -0800962 raise error.PayloadError(
963 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700964 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800965 return data_length if data_length is not None else 0
966
Gilad Arnold382df5c2013-05-03 12:49:28 -0700967 def _SizeToNumBlocks(self, size):
968 """Returns the number of blocks needed to contain a given byte size."""
969 return (size + self.block_size - 1) / self.block_size
970
971 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800972 """Returns a freshly initialized array of block counters.
973
Gilad Arnoldcb638912013-06-24 04:57:11 -0700974 Note that the generated array is not portable as is due to byte-ordering
975 issues, hence it should not be serialized.
976
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800977 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700978 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800979
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800980 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -0700981 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800982 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800983 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700984 return array.array('H',
985 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800986
Gilad Arnold382df5c2013-05-03 12:49:28 -0700987 def _CheckOperations(self, operations, report, base_name, old_fs_size,
988 new_fs_size, new_usable_size, prev_data_offset,
989 allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800990 """Checks a sequence of update operations.
991
992 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700993 operations: The sequence of operations to check.
994 report: The report object to add to.
995 base_name: The name of the operation block.
996 old_fs_size: The old filesystem size in bytes.
997 new_fs_size: The new filesystem size in bytes.
998 new_usable_size: The overall usable size of the new partition in bytes.
999 prev_data_offset: Offset of last used data bytes.
1000 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001001
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001002 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001003 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001004
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001005 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001006 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001007 """
1008 # The total size of data blobs used by operations scanned thus far.
1009 total_data_used = 0
1010 # Counts of specific operation types.
1011 op_counts = {
1012 common.OpType.REPLACE: 0,
1013 common.OpType.REPLACE_BZ: 0,
1014 common.OpType.MOVE: 0,
1015 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001016 common.OpType.SOURCE_COPY: 0,
1017 common.OpType.SOURCE_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001018 }
1019 # Total blob sizes for each operation type.
1020 op_blob_totals = {
1021 common.OpType.REPLACE: 0,
1022 common.OpType.REPLACE_BZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001023 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001024 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001025 # SOURCE_COPY operations don't have blobs.
1026 common.OpType.SOURCE_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001027 }
1028 # Counts of hashed vs unhashed operations.
1029 blob_hash_counts = {
1030 'hashed': 0,
1031 'unhashed': 0,
1032 }
1033 if allow_signature:
1034 blob_hash_counts['signature'] = 0
1035
1036 # Allocate old and new block counters.
Gilad Arnold4f50b412013-05-14 09:19:17 -07001037 old_block_counters = (self._AllocBlockCounters(new_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001038 if old_fs_size else None)
1039 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001040
1041 # Process and verify each operation.
1042 op_num = 0
1043 for op, op_name in common.OperationIter(operations, base_name):
1044 op_num += 1
1045
Gilad Arnoldcb638912013-06-24 04:57:11 -07001046 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001047 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001048 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001049 op_counts[op.type] += 1
1050
1051 is_last = op_num == len(operations)
1052 curr_data_used = self._CheckOperation(
1053 op, op_name, is_last, old_block_counters, new_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001054 new_usable_size if old_fs_size else 0, new_usable_size,
1055 prev_data_offset + total_data_used, allow_signature,
1056 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001057 if curr_data_used:
1058 op_blob_totals[op.type] += curr_data_used
1059 total_data_used += curr_data_used
1060
1061 # Report totals and breakdown statistics.
1062 report.AddField('total operations', op_num)
1063 report.AddField(
1064 None,
1065 histogram.Histogram.FromCountDict(op_counts,
1066 key_names=common.OpType.NAMES),
1067 indent=1)
1068 report.AddField('total blobs', sum(blob_hash_counts.values()))
1069 report.AddField(None,
1070 histogram.Histogram.FromCountDict(blob_hash_counts),
1071 indent=1)
1072 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1073 report.AddField(
1074 None,
1075 histogram.Histogram.FromCountDict(op_blob_totals,
1076 formatter=_AddHumanReadableSize,
1077 key_names=common.OpType.NAMES),
1078 indent=1)
1079
1080 # Report read/write histograms.
1081 if old_block_counters:
1082 report.AddField('block read hist',
1083 histogram.Histogram.FromKeyList(old_block_counters),
1084 linebreak=True, indent=1)
1085
Gilad Arnold382df5c2013-05-03 12:49:28 -07001086 new_write_hist = histogram.Histogram.FromKeyList(
1087 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1088 report.AddField('block write hist', new_write_hist, linebreak=True,
1089 indent=1)
1090
Gilad Arnoldcb638912013-06-24 04:57:11 -07001091 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001092 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001093 raise error.PayloadError(
1094 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001095 base_name)
1096
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001097 return total_data_used
1098
1099 def _CheckSignatures(self, report, pubkey_file_name):
1100 """Checks a payload's signature block."""
1101 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1102 sigs = update_metadata_pb2.Signatures()
1103 sigs.ParseFromString(sigs_raw)
1104 report.AddSection('signatures')
1105
Gilad Arnoldcb638912013-06-24 04:57:11 -07001106 # Check: At least one signature present.
1107 # pylint cannot see through the protobuf object, it seems.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001108 # pylint: disable=E1101
1109 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001110 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001111
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001112 last_ops_section = (self.payload.manifest.kernel_install_operations or
1113 self.payload.manifest.install_operations)
1114 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001115 # Check: signatures_{offset,size} must match the last (fake) operation.
1116 if not (fake_sig_op.type == common.OpType.REPLACE and
1117 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001118 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001119 raise error.PayloadError(
1120 'Signatures_{offset,size} (%d+%d) does not match last operation '
1121 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001122 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1123 fake_sig_op.data_length))
1124
1125 # Compute the checksum of all data up to signature blob.
1126 # TODO(garnold) we're re-reading the whole data section into a string
1127 # just to compute the checksum; instead, we could do it incrementally as
1128 # we read the blobs one-by-one, under the assumption that we're reading
1129 # them in order (which currently holds). This should be reconsidered.
1130 payload_hasher = self.payload.manifest_hasher.copy()
1131 common.Read(self.payload.payload_file, self.sigs_offset,
1132 offset=self.payload.data_offset, hasher=payload_hasher)
1133
1134 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1135 sig_report = report.AddSubReport(sig_name)
1136
Gilad Arnoldcb638912013-06-24 04:57:11 -07001137 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001138 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1139 self._CheckMandatoryField(sig, 'data', None, sig_name)
1140 sig_report.AddField('data len', len(sig.data))
1141
Gilad Arnoldcb638912013-06-24 04:57:11 -07001142 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001143 if sig.version == 1:
1144 self._CheckSha256Signature(sig.data, pubkey_file_name,
1145 payload_hasher.digest(), sig_name)
1146 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001147 raise error.PayloadError('Unknown signature version (%d).' %
1148 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001149
1150 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001151 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001152 """Checker entry point, invoking all checks.
1153
1154 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001155 pubkey_file_name: Public key used for signature verification.
1156 metadata_sig_file: Metadata signature, if verification is desired.
Gilad Arnold06eea332015-07-13 18:06:33 -07001157 rootfs_part_size: The size of rootfs partitions in bytes (default: infer
1158 based on payload type and version).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001159 kernel_part_size: The size of kernel partitions in bytes (default: use
1160 reported filesystem size).
1161 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001162
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001163 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001164 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001165 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001166 if not pubkey_file_name:
1167 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1168
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001169 report = _PayloadReport()
1170
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001171 # Get payload file size.
1172 self.payload.payload_file.seek(0, 2)
1173 payload_file_size = self.payload.payload_file.tell()
1174 self.payload.ResetFile()
1175
1176 try:
1177 # Check metadata signature (if provided).
1178 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001179 metadata_sig = base64.b64decode(metadata_sig_file.read())
1180 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1181 self.payload.manifest_hasher.digest(),
1182 'metadata signature')
1183
Gilad Arnoldcb638912013-06-24 04:57:11 -07001184 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001185 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001186 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001187 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001188 raise error.PayloadError('Unknown payload version (%d).' %
1189 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001190 report.AddField('version', self.payload.header.version)
1191 report.AddField('manifest len', self.payload.header.manifest_len)
1192
Gilad Arnoldcb638912013-06-24 04:57:11 -07001193 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001194 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001195 assert self.payload_type, 'payload type should be known by now'
1196
Gilad Arnold06eea332015-07-13 18:06:33 -07001197 # Infer the usable partition size when validating rootfs operations:
1198 # - If rootfs partition size was provided, use that.
1199 # - Otherwise, if this is an older delta (minor version < 2), stick with
1200 # a known constant size. This is necessary because older deltas may
1201 # exceed the filesystem size when moving data blocks around.
1202 # - Otherwise, use the encoded filesystem size.
1203 new_rootfs_usable_size = self.new_rootfs_fs_size
1204 if rootfs_part_size:
1205 new_rootfs_usable_size = rootfs_part_size
1206 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
1207 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
1208
Gilad Arnoldcb638912013-06-24 04:57:11 -07001209 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001210 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1211 # no explicit size provided *and* the partition size is not embedded in
1212 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001213 report.AddSection('rootfs operations')
1214 total_blob_size = self._CheckOperations(
1215 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001216 'install_operations', self.old_rootfs_fs_size,
Gilad Arnold06eea332015-07-13 18:06:33 -07001217 self.new_rootfs_fs_size, new_rootfs_usable_size, 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001218
Gilad Arnoldcb638912013-06-24 04:57:11 -07001219 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001220 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001221 report.AddSection('kernel operations')
1222 total_blob_size += self._CheckOperations(
1223 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001224 'kernel_install_operations', self.old_kernel_fs_size,
1225 self.new_kernel_fs_size,
1226 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1227 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001228
Gilad Arnoldcb638912013-06-24 04:57:11 -07001229 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001230 used_payload_size = self.payload.data_offset + total_blob_size
1231 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001232 raise error.PayloadError(
1233 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001234 (used_payload_size, payload_file_size))
1235
Gilad Arnoldcb638912013-06-24 04:57:11 -07001236 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001237 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001238 self._CheckSignatures(report, pubkey_file_name)
1239
Gilad Arnoldcb638912013-06-24 04:57:11 -07001240 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001241 report.AddSection('summary')
1242 report.AddField('update type', self.payload_type)
1243
1244 report.Finalize()
1245 finally:
1246 if report_out_file:
1247 report.Dump(report_out_file)