blob: 7abf17854f43c7ca83ae4d66d236341937aabed7 [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,),
Sen Jiang912c4df2015-12-10 12:17:13 -080058 3: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070059}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080060
Gilad Arnold06eea332015-07-13 18:06:33 -070061_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
62
Gilad Arnold553b0ec2013-01-26 01:00:39 -080063#
64# Helper functions.
65#
Gilad Arnoldcb638912013-06-24 04:57:11 -070066
Gilad Arnold553b0ec2013-01-26 01:00:39 -080067def _IsPowerOfTwo(val):
68 """Returns True iff val is a power of two."""
69 return val > 0 and (val & (val - 1)) == 0
70
71
72def _AddFormat(format_func, value):
73 """Adds a custom formatted representation to ordinary string representation.
74
75 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070076 format_func: A value formatter.
77 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080078
Gilad Arnold553b0ec2013-01-26 01:00:39 -080079 Returns:
80 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080081 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070082 ret = str(value)
83 formatted_str = format_func(value)
84 if formatted_str:
85 ret += ' (%s)' % formatted_str
86 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -080087
88
89def _AddHumanReadableSize(size):
90 """Adds a human readable representation to a byte size value."""
91 return _AddFormat(format_utils.BytesToHumanReadable, size)
92
93
94#
95# Payload report generator.
96#
Gilad Arnoldcb638912013-06-24 04:57:11 -070097
Gilad Arnold553b0ec2013-01-26 01:00:39 -080098class _PayloadReport(object):
99 """A payload report generator.
100
101 A report is essentially a sequence of nodes, which represent data points. It
102 is initialized to have a "global", untitled section. A node may be a
103 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800104 """
105
Gilad Arnoldcb638912013-06-24 04:57:11 -0700106 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800107 class Node(object):
108 """A report node interface."""
109
110 @staticmethod
111 def _Indent(indent, line):
112 """Indents a line by a given indentation amount.
113
114 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700115 indent: The indentation amount.
116 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800117
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800118 Returns:
119 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800120 """
121 return '%*s%s' % (indent, '', line)
122
123 def GenerateLines(self, base_indent, sub_indent, curr_section):
124 """Generates the report lines for this node.
125
126 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700127 base_indent: Base indentation for each line.
128 sub_indent: Additional indentation for sub-nodes.
129 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800130
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800131 Returns:
132 A pair consisting of a list of properly indented report lines and a new
133 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800134 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700135 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800136
137 class FieldNode(Node):
138 """A field report node, representing a (name, value) pair."""
139
140 def __init__(self, name, value, linebreak, indent):
141 super(_PayloadReport.FieldNode, self).__init__()
142 self.name = name
143 self.value = value
144 self.linebreak = linebreak
145 self.indent = indent
146
147 def GenerateLines(self, base_indent, sub_indent, curr_section):
148 """Generates a properly formatted 'name : value' entry."""
149 report_output = ''
150 if self.name:
151 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
152 value_lines = str(self.value).splitlines()
153 if self.linebreak and self.name:
154 report_output += '\n' + '\n'.join(
155 ['%*s%s' % (self.indent, '', line) for line in value_lines])
156 else:
157 if self.name:
158 report_output += ' '
159 report_output += '%*s' % (self.indent, '')
160 cont_line_indent = len(report_output)
161 indented_value_lines = [value_lines[0]]
162 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
163 for line in value_lines[1:]])
164 report_output += '\n'.join(indented_value_lines)
165
166 report_lines = [self._Indent(base_indent, line + '\n')
167 for line in report_output.split('\n')]
168 return report_lines, curr_section
169
170 class SubReportNode(Node):
171 """A sub-report node, representing a nested report."""
172
173 def __init__(self, title, report):
174 super(_PayloadReport.SubReportNode, self).__init__()
175 self.title = title
176 self.report = report
177
178 def GenerateLines(self, base_indent, sub_indent, curr_section):
179 """Recurse with indentation."""
180 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
181 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
182 sub_indent))
183 return report_lines, curr_section
184
185 class SectionNode(Node):
186 """A section header node."""
187
188 def __init__(self, title=None):
189 super(_PayloadReport.SectionNode, self).__init__()
190 self.title = title
191 self.max_field_name_len = 0
192
193 def GenerateLines(self, base_indent, sub_indent, curr_section):
194 """Dump a title line, return self as the (new) current section."""
195 report_lines = []
196 if self.title:
197 report_lines.append(self._Indent(base_indent,
198 '=== %s ===\n' % self.title))
199 return report_lines, self
200
201 def __init__(self):
202 self.report = []
203 self.last_section = self.global_section = self.SectionNode()
204 self.is_finalized = False
205
206 def GenerateLines(self, base_indent, sub_indent):
207 """Generates the lines in the report, properly indented.
208
209 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700210 base_indent: The indentation used for root-level report lines.
211 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800212
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800213 Returns:
214 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800215 """
216 report_lines = []
217 curr_section = self.global_section
218 for node in self.report:
219 node_report_lines, curr_section = node.GenerateLines(
220 base_indent, sub_indent, curr_section)
221 report_lines.extend(node_report_lines)
222
223 return report_lines
224
225 def Dump(self, out_file, base_indent=0, sub_indent=2):
226 """Dumps the report to a file.
227
228 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700229 out_file: File object to output the content to.
230 base_indent: Base indentation for report lines.
231 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800232 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800233 report_lines = self.GenerateLines(base_indent, sub_indent)
234 if report_lines and not self.is_finalized:
235 report_lines.append('(incomplete report)\n')
236
237 for line in report_lines:
238 out_file.write(line)
239
240 def AddField(self, name, value, linebreak=False, indent=0):
241 """Adds a field/value pair to the payload report.
242
243 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700244 name: The field's name.
245 value: The field's value.
246 linebreak: Whether the value should be printed on a new line.
247 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800248 """
249 assert not self.is_finalized
250 if name and self.last_section.max_field_name_len < len(name):
251 self.last_section.max_field_name_len = len(name)
252 self.report.append(self.FieldNode(name, value, linebreak, indent))
253
254 def AddSubReport(self, title):
255 """Adds and returns a sub-report with a title."""
256 assert not self.is_finalized
257 sub_report = self.SubReportNode(title, type(self)())
258 self.report.append(sub_report)
259 return sub_report.report
260
261 def AddSection(self, title):
262 """Adds a new section title."""
263 assert not self.is_finalized
264 self.last_section = self.SectionNode(title)
265 self.report.append(self.last_section)
266
267 def Finalize(self):
268 """Seals the report, marking it as complete."""
269 self.is_finalized = True
270
271
272#
273# Payload verification.
274#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700275
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800276class PayloadChecker(object):
277 """Checking the integrity of an update payload.
278
279 This is a short-lived object whose purpose is to isolate the logic used for
280 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800281 """
282
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700283 def __init__(self, payload, assert_type=None, block_size=0,
284 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700285 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700286
287 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700288 payload: The payload object to check.
289 assert_type: Assert that payload is either 'full' or 'delta' (optional).
290 block_size: Expected filesystem / payload block size (optional).
291 allow_unhashed: Allow operations with unhashed data blobs.
292 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700293 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700294 if not payload.is_init:
295 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700296
297 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800298 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700299 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
300 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700301 raise error.PayloadError(
302 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700303 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700304 raise error.PayloadError('Invalid assert_type value (%r).' %
305 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700306 self.payload_type = assert_type
307 self.allow_unhashed = allow_unhashed
308
309 # Disable specific tests.
310 self.check_dst_pseudo_extents = (
311 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
312 self.check_move_same_src_dst_block = (
313 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
314 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800315
316 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800317 self.sigs_offset = 0
318 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700319 self.old_rootfs_fs_size = 0
320 self.old_kernel_fs_size = 0
321 self.new_rootfs_fs_size = 0
322 self.new_kernel_fs_size = 0
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700323 self.minor_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800324
325 @staticmethod
326 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
327 msg_name=None, linebreak=False, indent=0):
328 """Adds an element from a protobuf message to the payload report.
329
330 Checks to see whether a message contains a given element, and if so adds
331 the element value to the provided report. A missing mandatory element
332 causes an exception to be raised.
333
334 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700335 msg: The message containing the element.
336 name: The name of the element.
337 report: A report object to add the element name/value to.
338 is_mandatory: Whether or not this element must be present.
339 is_submsg: Whether this element is itself a message.
340 convert: A function for converting the element value for reporting.
341 msg_name: The name of the message object (for error reporting).
342 linebreak: Whether the value report should induce a line break.
343 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800344
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800345 Returns:
346 A pair consisting of the element value and the generated sub-report for
347 it (if the element is a sub-message, None otherwise). If the element is
348 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800349
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800350 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700351 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800352 """
353 if not msg.HasField(name):
354 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700355 raise error.PayloadError('%smissing mandatory %s %r.' %
356 (msg_name + ' ' if msg_name else '',
357 'sub-message' if is_submsg else 'field',
358 name))
359 return None, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800360
361 value = getattr(msg, name)
362 if is_submsg:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700363 return value, report and report.AddSubReport(name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800364 else:
365 if report:
366 report.AddField(name, convert(value), linebreak=linebreak,
367 indent=indent)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700368 return value, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800369
370 @staticmethod
371 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
372 linebreak=False, indent=0):
373 """Adds a mandatory field; returning first component from _CheckElem."""
374 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
375 convert=convert, msg_name=msg_name,
376 linebreak=linebreak, indent=indent)[0]
377
378 @staticmethod
379 def _CheckOptionalField(msg, field_name, report, convert=str,
380 linebreak=False, indent=0):
381 """Adds an optional field; returning first component from _CheckElem."""
382 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
383 convert=convert, linebreak=linebreak,
384 indent=indent)[0]
385
386 @staticmethod
387 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
388 """Adds a mandatory sub-message; wrapper for _CheckElem."""
389 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
390 msg_name)
391
392 @staticmethod
393 def _CheckOptionalSubMsg(msg, submsg_name, report):
394 """Adds an optional sub-message; wrapper for _CheckElem."""
395 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
396
397 @staticmethod
398 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
399 """Checks that val1 is None iff val2 is None.
400
401 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700402 val1: first value to be compared.
403 val2: second value to be compared.
404 name1: name of object holding the first value.
405 name2: name of object holding the second value.
406 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800407
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800408 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700409 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800410 """
411 if None in (val1, val2) and val1 is not val2:
412 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700413 raise error.PayloadError('%r present without %r%s.' %
414 (present, missing,
415 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800416
417 @staticmethod
418 def _Run(cmd, send_data=None):
419 """Runs a subprocess, returns its output.
420
421 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700422 cmd: Sequence of command-line argument for invoking the subprocess.
423 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800424
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800425 Returns:
426 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800427 """
428 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
429 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700430 try:
431 result = run_process.communicate(input=send_data)
432 finally:
433 exit_code = run_process.wait()
434
435 if exit_code:
436 raise RuntimeError('Subprocess %r failed with code %r.' %
437 (cmd, exit_code))
438
439 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800440
441 @staticmethod
442 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
443 """Verifies an actual hash against a signed one.
444
445 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700446 sig_data: The raw signature data.
447 pubkey_file_name: Public key used for verifying signature.
448 actual_hash: The actual hash digest.
449 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800450
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800451 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700452 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800453 """
454 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700455 raise error.PayloadError(
456 '%s: signature size (%d) not as expected (256).' %
457 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800458 signed_data, _ = PayloadChecker._Run(
459 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
460 send_data=sig_data)
461
Gilad Arnold5502b562013-03-08 13:22:31 -0800462 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700463 raise error.PayloadError('%s: unexpected signed data length (%d).' %
464 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800465
Gilad Arnold5502b562013-03-08 13:22:31 -0800466 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700467 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
468 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800469
Gilad Arnold5502b562013-03-08 13:22:31 -0800470 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800471 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700472 raise error.PayloadError(
473 '%s: signed hash (%s) different from actual (%s).' %
474 (sig_name, common.FormatSha256(signed_hash),
475 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800476
477 @staticmethod
478 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
479 block_name=None):
480 """Checks that a given length fits given block space.
481
482 This ensures that the number of blocks allocated is appropriate for the
483 length of the data residing in these blocks.
484
485 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700486 length: The actual length of the data.
487 num_blocks: The number of blocks allocated for it.
488 block_size: The size of each block in bytes.
489 length_name: Name of length (used for error reporting).
490 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800491
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800492 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700493 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800494 """
495 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700496 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700497 raise error.PayloadError(
498 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800499 (length_name, length, block_name or '', num_blocks, block_size))
500
501 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700502 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700503 raise error.PayloadError(
504 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800505 (length_name, length, block_name or '', num_blocks - 1, block_size))
506
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700507 def _CheckManifestMinorVersion(self, report):
508 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800509
510 Args:
511 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800512
513 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700514 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800515 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700516 self.minor_version = self._CheckOptionalField(self.payload.manifest,
517 'minor_version', report)
518 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
519 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800520 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700521 'Minor version %d not compatible with payload type %s.' %
522 (self.minor_version, self.payload_type))
523 elif self.minor_version is None:
524 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800525 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700526 raise error.PayloadError('Unsupported minor version: %d' %
527 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800528
Gilad Arnold382df5c2013-05-03 12:49:28 -0700529 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800530 """Checks the payload manifest.
531
532 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700533 report: A report object to add to.
534 rootfs_part_size: Size of the rootfs partition in bytes.
535 kernel_part_size: Size of the kernel partition in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800536
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800537 Returns:
538 A tuple consisting of the partition block size used during the update
539 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800540
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800541 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700542 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800543 """
544 manifest = self.payload.manifest
545 report.AddSection('manifest')
546
547 # Check: block_size must exist and match the expected value.
548 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
549 report, 'manifest')
550 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700551 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
552 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800553
554 # Check: signatures_offset <==> signatures_size.
555 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
556 report)
557 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
558 report)
559 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
560 'signatures_offset', 'signatures_size', 'manifest')
561
562 # Check: old_kernel_info <==> old_rootfs_info.
563 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
564 'old_kernel_info', report)
565 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
566 'old_rootfs_info', report)
567 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
568 'old_rootfs_info', 'manifest')
569 if oki_msg: # equivalently, ori_msg
570 # Assert/mark delta payload.
571 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700572 raise error.PayloadError(
573 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800574 self.payload_type = _TYPE_DELTA
575
576 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700577 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800578 oki_msg, 'size', oki_report, 'old_kernel_info')
579 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
580 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700581 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800582 ori_msg, 'size', ori_report, 'old_rootfs_info')
583 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
584 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700585
586 # Check: old_{kernel,rootfs} size must fit in respective partition.
587 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700588 raise error.PayloadError(
589 'Old kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700590 (self.old_kernel_fs_size, kernel_part_size))
591 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700592 raise error.PayloadError(
593 'Old rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700594 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800595 else:
596 # Assert/mark full payload.
597 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700598 raise error.PayloadError(
599 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800600 self.payload_type = _TYPE_FULL
601
602 # Check: new_kernel_info present; contains {size, hash}.
603 nki_msg, nki_report = self._CheckMandatorySubMsg(
604 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700605 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800606 nki_msg, 'size', nki_report, 'new_kernel_info')
607 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
608 convert=common.FormatSha256)
609
610 # Check: new_rootfs_info present; contains {size, hash}.
611 nri_msg, nri_report = self._CheckMandatorySubMsg(
612 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700613 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800614 nri_msg, 'size', nri_report, 'new_rootfs_info')
615 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
616 convert=common.FormatSha256)
617
Gilad Arnold382df5c2013-05-03 12:49:28 -0700618 # Check: new_{kernel,rootfs} size must fit in respective partition.
619 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700620 raise error.PayloadError(
621 'New kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700622 (self.new_kernel_fs_size, kernel_part_size))
623 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700624 raise error.PayloadError(
625 'New rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700626 (self.new_rootfs_fs_size, rootfs_part_size))
627
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800628 # Check: minor_version makes sense for the payload type. This check should
629 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700630 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800631
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800632 def _CheckLength(self, length, total_blocks, op_name, length_name):
633 """Checks whether a length matches the space designated in extents.
634
635 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700636 length: The total length of the data.
637 total_blocks: The total number of blocks in extents.
638 op_name: Operation name (for error reporting).
639 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800640
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800641 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700642 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800643 """
644 # Check: length is non-zero.
645 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700646 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800647
648 # Check that length matches number of blocks.
649 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
650 '%s: %s' % (op_name, length_name))
651
Gilad Arnold382df5c2013-05-03 12:49:28 -0700652 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800653 allow_pseudo=False, allow_signature=False):
654 """Checks a sequence of extents.
655
656 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700657 extents: The sequence of extents to check.
658 usable_size: The usable size of the partition to which the extents apply.
659 block_counters: Array of counters corresponding to the number of blocks.
660 name: The name of the extent block.
661 allow_pseudo: Whether or not pseudo block numbers are allowed.
662 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800663
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800664 Returns:
665 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800666
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800667 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700668 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800669 """
670 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800671 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700672 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800673 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
674 None, ex_name)
675 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
676 ex_name)
677 end_block = start_block + num_blocks
678
679 # Check: num_blocks > 0.
680 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700681 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800682
683 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700684 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700685 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700686 raise error.PayloadError(
687 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700688 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800689
690 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700691 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800692 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800693 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
694 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
695 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700696 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800697
698 total_num_blocks += num_blocks
699
700 return total_num_blocks
701
702 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
703 """Specific checks for REPLACE/REPLACE_BZ operations.
704
705 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700706 op: The operation object from the manifest.
707 data_length: The length of the data blob associated with the operation.
708 total_dst_blocks: Total number of blocks in dst_extents.
709 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800710
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800711 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700712 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800713 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700714 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800715 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700716 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800717
Gilad Arnoldcb638912013-06-24 04:57:11 -0700718 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800719 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700720 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800721
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800722 if op.type == common.OpType.REPLACE:
723 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
724 self.block_size,
725 op_name + '.data_length', 'dst')
726 else:
727 # Check: data_length must be smaller than the alotted dst blocks.
728 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700729 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800730 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700731 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800732 (op_name, data_length, total_dst_blocks, self.block_size))
733
734 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
735 total_dst_blocks, op_name):
736 """Specific checks for MOVE operations.
737
738 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700739 op: The operation object from the manifest.
740 data_offset: The offset of a data blob for the operation.
741 total_src_blocks: Total number of blocks in src_extents.
742 total_dst_blocks: Total number of blocks in dst_extents.
743 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800744
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800745 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700746 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800747 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700748 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800749 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700750 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800751
Gilad Arnoldcb638912013-06-24 04:57:11 -0700752 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800753 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700754 raise error.PayloadError(
755 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800756 (op_name, total_src_blocks, total_dst_blocks))
757
Gilad Arnoldcb638912013-06-24 04:57:11 -0700758 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800759 i = 0
760 src_extent_iter = iter(op.src_extents)
761 dst_extent_iter = iter(op.dst_extents)
762 src_extent = dst_extent = None
763 src_idx = src_num = dst_idx = dst_num = 0
764 while i < total_src_blocks:
765 # Get the next source extent, if needed.
766 if not src_extent:
767 try:
768 src_extent = src_extent_iter.next()
769 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700770 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
771 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800772 src_idx = src_extent.start_block
773 src_num = src_extent.num_blocks
774
775 # Get the next dest extent, if needed.
776 if not dst_extent:
777 try:
778 dst_extent = dst_extent_iter.next()
779 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700780 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
781 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800782 dst_idx = dst_extent.start_block
783 dst_num = dst_extent.num_blocks
784
Allie Woodb065e132015-04-24 10:20:27 -0700785 # Check: start block is not 0. See crbug/480751; there are still versions
786 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
787 # so we need to fail payloads that try to MOVE to/from block 0.
788 if src_idx == 0 or dst_idx == 0:
789 raise error.PayloadError(
790 '%s: MOVE operation cannot have extent with start block 0' %
791 op_name)
792
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700793 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700794 raise error.PayloadError(
795 '%s: src/dst block number %d is the same (%d).' %
796 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800797
798 advance = min(src_num, dst_num)
799 i += advance
800
801 src_idx += advance
802 src_num -= advance
803 if src_num == 0:
804 src_extent = None
805
806 dst_idx += advance
807 dst_num -= advance
808 if dst_num == 0:
809 dst_extent = None
810
Gilad Arnold5502b562013-03-08 13:22:31 -0800811 # Make sure we've exhausted all src/dst extents.
812 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700813 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800814 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700815 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800816
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800817 def _CheckBsdiffOperation(self, data_length, total_dst_blocks, op_name):
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800818 """Specific checks for BSDIFF and SOURCE_BSDIFF operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800819
820 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700821 data_length: The length of the data blob associated with the operation.
822 total_dst_blocks: Total number of blocks in dst_extents.
823 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800824
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800825 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700826 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800827 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800828 # Check: data_{offset,length} present.
829 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700830 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800831
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800832 # Check: data_length is strictly smaller than the alotted dst blocks.
833 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700834 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800835 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700836 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800837 (op_name, data_length, total_dst_blocks, self.block_size,
838 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800839
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800840 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
841 total_dst_blocks, op_name):
842 """Specific checks for SOURCE_COPY.
843
844 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800845 data_offset: The offset of a data blob for the operation.
846 total_src_blocks: Total number of blocks in src_extents.
847 total_dst_blocks: Total number of blocks in dst_extents.
848 op_name: Operation name for error reporting.
849
850 Raises:
851 error.PayloadError if any check fails.
852 """
853 # Check: No data_{offset,length}.
854 if data_offset is not None:
855 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
856
857 # Check: total_src_blocks == total_dst_blocks.
858 if total_src_blocks != total_dst_blocks:
859 raise error.PayloadError(
860 '%s: total src blocks (%d) != total dst blocks (%d).' %
861 (op_name, total_src_blocks, total_dst_blocks))
862
Sen Jiangd6122bb2015-12-11 10:27:04 -0800863 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800864 """Specific checks for SOURCE_* operations.
865
866 Args:
867 op: The operation object from the manifest.
868 total_src_blocks: Total number of blocks in src_extents.
869 op_name: Operation name for error reporting.
870
871 Raises:
872 error.PayloadError if any check fails.
873 """
874 # Check: total_src_blocks != 0.
875 if total_src_blocks == 0:
876 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
877
878 # Check: src_sha256_hash present in minor version 3.
879 if self.minor_version == 3 and op.src_sha256_hash is None:
880 raise error.PayloadError('%s: source hash missing.' % op_name)
881
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800882 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700883 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700884 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800885 """Checks a single update operation.
886
887 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700888 op: The operation object.
889 op_name: Operation name string for error reporting.
890 is_last: Whether this is the last operation in the sequence.
891 old_block_counters: Arrays of block read counters.
892 new_block_counters: Arrays of block write counters.
893 old_usable_size: The overall usable size for src data in bytes.
894 new_usable_size: The overall usable size for dst data in bytes.
895 prev_data_offset: Offset of last used data bytes.
896 allow_signature: Whether this may be a signature operation.
897 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800898
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800899 Returns:
900 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800901
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800902 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700903 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800904 """
905 # Check extents.
906 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700907 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800908 op_name + '.src_extents', allow_pseudo=True)
909 allow_signature_in_extents = (allow_signature and is_last and
910 op.type == common.OpType.REPLACE)
911 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700912 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700913 op_name + '.dst_extents',
914 allow_pseudo=(not self.check_dst_pseudo_extents),
915 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800916
917 # Check: data_offset present <==> data_length present.
918 data_offset = self._CheckOptionalField(op, 'data_offset', None)
919 data_length = self._CheckOptionalField(op, 'data_length', None)
920 self._CheckPresentIff(data_offset, data_length, 'data_offset',
921 'data_length', op_name)
922
Gilad Arnoldcb638912013-06-24 04:57:11 -0700923 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800924 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700925 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800926
927 # Check {src,dst}_length, if present.
928 if op.HasField('src_length'):
929 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
930 if op.HasField('dst_length'):
931 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
932
933 if op.HasField('data_sha256_hash'):
934 blob_hash_counts['hashed'] += 1
935
Gilad Arnoldcb638912013-06-24 04:57:11 -0700936 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800937 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700938 raise error.PayloadError(
939 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800940 op_name)
941
Gilad Arnoldcb638912013-06-24 04:57:11 -0700942 # Check: Hash verifies correctly.
943 # pylint cannot find the method in hashlib, for some reason.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800944 # pylint: disable=E1101
945 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
946 data_length))
947 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700948 raise error.PayloadError(
949 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700950 (op_name, common.FormatSha256(op.data_sha256_hash),
951 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800952 elif data_offset is not None:
953 if allow_signature_in_extents:
954 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700955 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800956 blob_hash_counts['unhashed'] += 1
957 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700958 raise error.PayloadError('%s: unhashed operation not allowed.' %
959 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800960
961 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700962 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800963 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700964 raise error.PayloadError(
965 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800966 (op_name, data_offset, prev_data_offset))
967
968 # Type-specific checks.
969 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
970 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700971 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800972 self._CheckMoveOperation(op, data_offset, total_src_blocks,
973 total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700974 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800975 self._CheckBsdiffOperation(data_length, total_dst_blocks, op_name)
Sen Jiang912c4df2015-12-10 12:17:13 -0800976 elif op.type == common.OpType.SOURCE_COPY and self.minor_version in (2, 3):
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800977 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
978 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800979 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang912c4df2015-12-10 12:17:13 -0800980 elif (op.type == common.OpType.SOURCE_BSDIFF and
981 self.minor_version in (2, 3)):
Allie Wood7cf9f132015-02-26 14:28:19 -0800982 self._CheckBsdiffOperation(data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800983 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800984 else:
Allie Wood7cf9f132015-02-26 14:28:19 -0800985 raise error.PayloadError(
986 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700987 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800988 return data_length if data_length is not None else 0
989
Gilad Arnold382df5c2013-05-03 12:49:28 -0700990 def _SizeToNumBlocks(self, size):
991 """Returns the number of blocks needed to contain a given byte size."""
992 return (size + self.block_size - 1) / self.block_size
993
994 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800995 """Returns a freshly initialized array of block counters.
996
Gilad Arnoldcb638912013-06-24 04:57:11 -0700997 Note that the generated array is not portable as is due to byte-ordering
998 issues, hence it should not be serialized.
999
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001000 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001001 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001002
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001003 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001004 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001005 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001006 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001007 return array.array('H',
1008 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001009
Gilad Arnold382df5c2013-05-03 12:49:28 -07001010 def _CheckOperations(self, operations, report, base_name, old_fs_size,
1011 new_fs_size, new_usable_size, prev_data_offset,
1012 allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001013 """Checks a sequence of update operations.
1014
1015 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001016 operations: The sequence of operations to check.
1017 report: The report object to add to.
1018 base_name: The name of the operation block.
1019 old_fs_size: The old filesystem size in bytes.
1020 new_fs_size: The new filesystem size in bytes.
1021 new_usable_size: The overall usable size of the new partition in bytes.
1022 prev_data_offset: Offset of last used data bytes.
1023 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001024
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001025 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001026 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001027
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001028 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001029 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001030 """
1031 # The total size of data blobs used by operations scanned thus far.
1032 total_data_used = 0
1033 # Counts of specific operation types.
1034 op_counts = {
1035 common.OpType.REPLACE: 0,
1036 common.OpType.REPLACE_BZ: 0,
1037 common.OpType.MOVE: 0,
1038 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001039 common.OpType.SOURCE_COPY: 0,
1040 common.OpType.SOURCE_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001041 }
1042 # Total blob sizes for each operation type.
1043 op_blob_totals = {
1044 common.OpType.REPLACE: 0,
1045 common.OpType.REPLACE_BZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001046 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001047 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001048 # SOURCE_COPY operations don't have blobs.
1049 common.OpType.SOURCE_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001050 }
1051 # Counts of hashed vs unhashed operations.
1052 blob_hash_counts = {
1053 'hashed': 0,
1054 'unhashed': 0,
1055 }
1056 if allow_signature:
1057 blob_hash_counts['signature'] = 0
1058
1059 # Allocate old and new block counters.
Gilad Arnold4f50b412013-05-14 09:19:17 -07001060 old_block_counters = (self._AllocBlockCounters(new_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001061 if old_fs_size else None)
1062 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001063
1064 # Process and verify each operation.
1065 op_num = 0
1066 for op, op_name in common.OperationIter(operations, base_name):
1067 op_num += 1
1068
Gilad Arnoldcb638912013-06-24 04:57:11 -07001069 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001070 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001071 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001072 op_counts[op.type] += 1
1073
1074 is_last = op_num == len(operations)
1075 curr_data_used = self._CheckOperation(
1076 op, op_name, is_last, old_block_counters, new_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001077 new_usable_size if old_fs_size else 0, new_usable_size,
1078 prev_data_offset + total_data_used, allow_signature,
1079 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001080 if curr_data_used:
1081 op_blob_totals[op.type] += curr_data_used
1082 total_data_used += curr_data_used
1083
1084 # Report totals and breakdown statistics.
1085 report.AddField('total operations', op_num)
1086 report.AddField(
1087 None,
1088 histogram.Histogram.FromCountDict(op_counts,
1089 key_names=common.OpType.NAMES),
1090 indent=1)
1091 report.AddField('total blobs', sum(blob_hash_counts.values()))
1092 report.AddField(None,
1093 histogram.Histogram.FromCountDict(blob_hash_counts),
1094 indent=1)
1095 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1096 report.AddField(
1097 None,
1098 histogram.Histogram.FromCountDict(op_blob_totals,
1099 formatter=_AddHumanReadableSize,
1100 key_names=common.OpType.NAMES),
1101 indent=1)
1102
1103 # Report read/write histograms.
1104 if old_block_counters:
1105 report.AddField('block read hist',
1106 histogram.Histogram.FromKeyList(old_block_counters),
1107 linebreak=True, indent=1)
1108
Gilad Arnold382df5c2013-05-03 12:49:28 -07001109 new_write_hist = histogram.Histogram.FromKeyList(
1110 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1111 report.AddField('block write hist', new_write_hist, linebreak=True,
1112 indent=1)
1113
Gilad Arnoldcb638912013-06-24 04:57:11 -07001114 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001115 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001116 raise error.PayloadError(
1117 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001118 base_name)
1119
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001120 return total_data_used
1121
1122 def _CheckSignatures(self, report, pubkey_file_name):
1123 """Checks a payload's signature block."""
1124 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1125 sigs = update_metadata_pb2.Signatures()
1126 sigs.ParseFromString(sigs_raw)
1127 report.AddSection('signatures')
1128
Gilad Arnoldcb638912013-06-24 04:57:11 -07001129 # Check: At least one signature present.
1130 # pylint cannot see through the protobuf object, it seems.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001131 # pylint: disable=E1101
1132 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001133 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001134
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001135 last_ops_section = (self.payload.manifest.kernel_install_operations or
1136 self.payload.manifest.install_operations)
1137 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001138 # Check: signatures_{offset,size} must match the last (fake) operation.
1139 if not (fake_sig_op.type == common.OpType.REPLACE and
1140 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001141 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001142 raise error.PayloadError(
1143 'Signatures_{offset,size} (%d+%d) does not match last operation '
1144 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001145 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1146 fake_sig_op.data_length))
1147
1148 # Compute the checksum of all data up to signature blob.
1149 # TODO(garnold) we're re-reading the whole data section into a string
1150 # just to compute the checksum; instead, we could do it incrementally as
1151 # we read the blobs one-by-one, under the assumption that we're reading
1152 # them in order (which currently holds). This should be reconsidered.
1153 payload_hasher = self.payload.manifest_hasher.copy()
1154 common.Read(self.payload.payload_file, self.sigs_offset,
1155 offset=self.payload.data_offset, hasher=payload_hasher)
1156
1157 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1158 sig_report = report.AddSubReport(sig_name)
1159
Gilad Arnoldcb638912013-06-24 04:57:11 -07001160 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001161 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1162 self._CheckMandatoryField(sig, 'data', None, sig_name)
1163 sig_report.AddField('data len', len(sig.data))
1164
Gilad Arnoldcb638912013-06-24 04:57:11 -07001165 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001166 if sig.version == 1:
1167 self._CheckSha256Signature(sig.data, pubkey_file_name,
1168 payload_hasher.digest(), sig_name)
1169 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001170 raise error.PayloadError('Unknown signature version (%d).' %
1171 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001172
1173 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001174 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001175 """Checker entry point, invoking all checks.
1176
1177 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001178 pubkey_file_name: Public key used for signature verification.
1179 metadata_sig_file: Metadata signature, if verification is desired.
Gilad Arnold06eea332015-07-13 18:06:33 -07001180 rootfs_part_size: The size of rootfs partitions in bytes (default: infer
1181 based on payload type and version).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001182 kernel_part_size: The size of kernel partitions in bytes (default: use
1183 reported filesystem size).
1184 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001185
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001186 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001187 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001188 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001189 if not pubkey_file_name:
1190 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1191
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001192 report = _PayloadReport()
1193
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001194 # Get payload file size.
1195 self.payload.payload_file.seek(0, 2)
1196 payload_file_size = self.payload.payload_file.tell()
1197 self.payload.ResetFile()
1198
1199 try:
1200 # Check metadata signature (if provided).
1201 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001202 metadata_sig = base64.b64decode(metadata_sig_file.read())
1203 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1204 self.payload.manifest_hasher.digest(),
1205 'metadata signature')
1206
Gilad Arnoldcb638912013-06-24 04:57:11 -07001207 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001208 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001209 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001210 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001211 raise error.PayloadError('Unknown payload version (%d).' %
1212 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001213 report.AddField('version', self.payload.header.version)
1214 report.AddField('manifest len', self.payload.header.manifest_len)
1215
Gilad Arnoldcb638912013-06-24 04:57:11 -07001216 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001217 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001218 assert self.payload_type, 'payload type should be known by now'
1219
Gilad Arnold06eea332015-07-13 18:06:33 -07001220 # Infer the usable partition size when validating rootfs operations:
1221 # - If rootfs partition size was provided, use that.
1222 # - Otherwise, if this is an older delta (minor version < 2), stick with
1223 # a known constant size. This is necessary because older deltas may
1224 # exceed the filesystem size when moving data blocks around.
1225 # - Otherwise, use the encoded filesystem size.
1226 new_rootfs_usable_size = self.new_rootfs_fs_size
1227 if rootfs_part_size:
1228 new_rootfs_usable_size = rootfs_part_size
1229 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
1230 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
1231
Gilad Arnoldcb638912013-06-24 04:57:11 -07001232 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001233 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1234 # no explicit size provided *and* the partition size is not embedded in
1235 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001236 report.AddSection('rootfs operations')
1237 total_blob_size = self._CheckOperations(
1238 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001239 'install_operations', self.old_rootfs_fs_size,
Gilad Arnold06eea332015-07-13 18:06:33 -07001240 self.new_rootfs_fs_size, new_rootfs_usable_size, 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001241
Gilad Arnoldcb638912013-06-24 04:57:11 -07001242 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001243 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001244 report.AddSection('kernel operations')
1245 total_blob_size += self._CheckOperations(
1246 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001247 'kernel_install_operations', self.old_kernel_fs_size,
1248 self.new_kernel_fs_size,
1249 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1250 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001251
Gilad Arnoldcb638912013-06-24 04:57:11 -07001252 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001253 used_payload_size = self.payload.data_offset + total_blob_size
1254 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001255 raise error.PayloadError(
1256 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001257 (used_payload_size, payload_file_size))
1258
Gilad Arnoldcb638912013-06-24 04:57:11 -07001259 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001260 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001261 self._CheckSignatures(report, pubkey_file_name)
1262
Gilad Arnoldcb638912013-06-24 04:57:11 -07001263 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001264 report.AddSection('summary')
1265 report.AddField('update type', self.payload_type)
1266
1267 report.Finalize()
1268 finally:
1269 if report_out_file:
1270 report.Dump(report_out_file)