blob: 31443959e3c26e9a01f5d49323c6731e493e395b [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,),
Sen Jiang92161a72016-06-28 16:09:38 -070059 4: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070060}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080061
Gilad Arnold06eea332015-07-13 18:06:33 -070062_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
63
Gilad Arnold553b0ec2013-01-26 01:00:39 -080064#
65# Helper functions.
66#
Gilad Arnoldcb638912013-06-24 04:57:11 -070067
Gilad Arnold553b0ec2013-01-26 01:00:39 -080068def _IsPowerOfTwo(val):
69 """Returns True iff val is a power of two."""
70 return val > 0 and (val & (val - 1)) == 0
71
72
73def _AddFormat(format_func, value):
74 """Adds a custom formatted representation to ordinary string representation.
75
76 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070077 format_func: A value formatter.
78 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080079
Gilad Arnold553b0ec2013-01-26 01:00:39 -080080 Returns:
81 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080082 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070083 ret = str(value)
84 formatted_str = format_func(value)
85 if formatted_str:
86 ret += ' (%s)' % formatted_str
87 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -080088
89
90def _AddHumanReadableSize(size):
91 """Adds a human readable representation to a byte size value."""
92 return _AddFormat(format_utils.BytesToHumanReadable, size)
93
94
95#
96# Payload report generator.
97#
Gilad Arnoldcb638912013-06-24 04:57:11 -070098
Gilad Arnold553b0ec2013-01-26 01:00:39 -080099class _PayloadReport(object):
100 """A payload report generator.
101
102 A report is essentially a sequence of nodes, which represent data points. It
103 is initialized to have a "global", untitled section. A node may be a
104 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800105 """
106
Gilad Arnoldcb638912013-06-24 04:57:11 -0700107 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800108 class Node(object):
109 """A report node interface."""
110
111 @staticmethod
112 def _Indent(indent, line):
113 """Indents a line by a given indentation amount.
114
115 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700116 indent: The indentation amount.
117 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800118
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800119 Returns:
120 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800121 """
122 return '%*s%s' % (indent, '', line)
123
124 def GenerateLines(self, base_indent, sub_indent, curr_section):
125 """Generates the report lines for this node.
126
127 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700128 base_indent: Base indentation for each line.
129 sub_indent: Additional indentation for sub-nodes.
130 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800131
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800132 Returns:
133 A pair consisting of a list of properly indented report lines and a new
134 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800135 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700136 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800137
138 class FieldNode(Node):
139 """A field report node, representing a (name, value) pair."""
140
141 def __init__(self, name, value, linebreak, indent):
142 super(_PayloadReport.FieldNode, self).__init__()
143 self.name = name
144 self.value = value
145 self.linebreak = linebreak
146 self.indent = indent
147
148 def GenerateLines(self, base_indent, sub_indent, curr_section):
149 """Generates a properly formatted 'name : value' entry."""
150 report_output = ''
151 if self.name:
152 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
153 value_lines = str(self.value).splitlines()
154 if self.linebreak and self.name:
155 report_output += '\n' + '\n'.join(
156 ['%*s%s' % (self.indent, '', line) for line in value_lines])
157 else:
158 if self.name:
159 report_output += ' '
160 report_output += '%*s' % (self.indent, '')
161 cont_line_indent = len(report_output)
162 indented_value_lines = [value_lines[0]]
163 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
164 for line in value_lines[1:]])
165 report_output += '\n'.join(indented_value_lines)
166
167 report_lines = [self._Indent(base_indent, line + '\n')
168 for line in report_output.split('\n')]
169 return report_lines, curr_section
170
171 class SubReportNode(Node):
172 """A sub-report node, representing a nested report."""
173
174 def __init__(self, title, report):
175 super(_PayloadReport.SubReportNode, self).__init__()
176 self.title = title
177 self.report = report
178
179 def GenerateLines(self, base_indent, sub_indent, curr_section):
180 """Recurse with indentation."""
181 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
182 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
183 sub_indent))
184 return report_lines, curr_section
185
186 class SectionNode(Node):
187 """A section header node."""
188
189 def __init__(self, title=None):
190 super(_PayloadReport.SectionNode, self).__init__()
191 self.title = title
192 self.max_field_name_len = 0
193
194 def GenerateLines(self, base_indent, sub_indent, curr_section):
195 """Dump a title line, return self as the (new) current section."""
196 report_lines = []
197 if self.title:
198 report_lines.append(self._Indent(base_indent,
199 '=== %s ===\n' % self.title))
200 return report_lines, self
201
202 def __init__(self):
203 self.report = []
204 self.last_section = self.global_section = self.SectionNode()
205 self.is_finalized = False
206
207 def GenerateLines(self, base_indent, sub_indent):
208 """Generates the lines in the report, properly indented.
209
210 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700211 base_indent: The indentation used for root-level report lines.
212 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800213
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800214 Returns:
215 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800216 """
217 report_lines = []
218 curr_section = self.global_section
219 for node in self.report:
220 node_report_lines, curr_section = node.GenerateLines(
221 base_indent, sub_indent, curr_section)
222 report_lines.extend(node_report_lines)
223
224 return report_lines
225
226 def Dump(self, out_file, base_indent=0, sub_indent=2):
227 """Dumps the report to a file.
228
229 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700230 out_file: File object to output the content to.
231 base_indent: Base indentation for report lines.
232 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800233 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800234 report_lines = self.GenerateLines(base_indent, sub_indent)
235 if report_lines and not self.is_finalized:
236 report_lines.append('(incomplete report)\n')
237
238 for line in report_lines:
239 out_file.write(line)
240
241 def AddField(self, name, value, linebreak=False, indent=0):
242 """Adds a field/value pair to the payload report.
243
244 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700245 name: The field's name.
246 value: The field's value.
247 linebreak: Whether the value should be printed on a new line.
248 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800249 """
250 assert not self.is_finalized
251 if name and self.last_section.max_field_name_len < len(name):
252 self.last_section.max_field_name_len = len(name)
253 self.report.append(self.FieldNode(name, value, linebreak, indent))
254
255 def AddSubReport(self, title):
256 """Adds and returns a sub-report with a title."""
257 assert not self.is_finalized
258 sub_report = self.SubReportNode(title, type(self)())
259 self.report.append(sub_report)
260 return sub_report.report
261
262 def AddSection(self, title):
263 """Adds a new section title."""
264 assert not self.is_finalized
265 self.last_section = self.SectionNode(title)
266 self.report.append(self.last_section)
267
268 def Finalize(self):
269 """Seals the report, marking it as complete."""
270 self.is_finalized = True
271
272
273#
274# Payload verification.
275#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700276
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800277class PayloadChecker(object):
278 """Checking the integrity of an update payload.
279
280 This is a short-lived object whose purpose is to isolate the logic used for
281 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800282 """
283
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700284 def __init__(self, payload, assert_type=None, block_size=0,
285 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700286 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700287
288 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700289 payload: The payload object to check.
290 assert_type: Assert that payload is either 'full' or 'delta' (optional).
291 block_size: Expected filesystem / payload block size (optional).
292 allow_unhashed: Allow operations with unhashed data blobs.
293 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700294 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700295 if not payload.is_init:
296 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700297
298 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800299 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700300 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
301 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700302 raise error.PayloadError(
303 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700304 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700305 raise error.PayloadError('Invalid assert_type value (%r).' %
306 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700307 self.payload_type = assert_type
308 self.allow_unhashed = allow_unhashed
309
310 # Disable specific tests.
311 self.check_dst_pseudo_extents = (
312 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
313 self.check_move_same_src_dst_block = (
314 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
315 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800316
317 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800318 self.sigs_offset = 0
319 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700320 self.old_rootfs_fs_size = 0
321 self.old_kernel_fs_size = 0
322 self.new_rootfs_fs_size = 0
323 self.new_kernel_fs_size = 0
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700324 self.minor_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800325
326 @staticmethod
327 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
328 msg_name=None, linebreak=False, indent=0):
329 """Adds an element from a protobuf message to the payload report.
330
331 Checks to see whether a message contains a given element, and if so adds
332 the element value to the provided report. A missing mandatory element
333 causes an exception to be raised.
334
335 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700336 msg: The message containing the element.
337 name: The name of the element.
338 report: A report object to add the element name/value to.
339 is_mandatory: Whether or not this element must be present.
340 is_submsg: Whether this element is itself a message.
341 convert: A function for converting the element value for reporting.
342 msg_name: The name of the message object (for error reporting).
343 linebreak: Whether the value report should induce a line break.
344 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800345
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800346 Returns:
347 A pair consisting of the element value and the generated sub-report for
348 it (if the element is a sub-message, None otherwise). If the element is
349 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800350
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800351 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700352 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800353 """
354 if not msg.HasField(name):
355 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700356 raise error.PayloadError('%smissing mandatory %s %r.' %
357 (msg_name + ' ' if msg_name else '',
358 'sub-message' if is_submsg else 'field',
359 name))
360 return None, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800361
362 value = getattr(msg, name)
363 if is_submsg:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700364 return value, report and report.AddSubReport(name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800365 else:
366 if report:
367 report.AddField(name, convert(value), linebreak=linebreak,
368 indent=indent)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700369 return value, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800370
371 @staticmethod
372 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
373 linebreak=False, indent=0):
374 """Adds a mandatory field; returning first component from _CheckElem."""
375 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
376 convert=convert, msg_name=msg_name,
377 linebreak=linebreak, indent=indent)[0]
378
379 @staticmethod
380 def _CheckOptionalField(msg, field_name, report, convert=str,
381 linebreak=False, indent=0):
382 """Adds an optional field; returning first component from _CheckElem."""
383 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
384 convert=convert, linebreak=linebreak,
385 indent=indent)[0]
386
387 @staticmethod
388 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
389 """Adds a mandatory sub-message; wrapper for _CheckElem."""
390 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
391 msg_name)
392
393 @staticmethod
394 def _CheckOptionalSubMsg(msg, submsg_name, report):
395 """Adds an optional sub-message; wrapper for _CheckElem."""
396 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
397
398 @staticmethod
399 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
400 """Checks that val1 is None iff val2 is None.
401
402 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700403 val1: first value to be compared.
404 val2: second value to be compared.
405 name1: name of object holding the first value.
406 name2: name of object holding the second value.
407 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800408
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800409 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700410 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800411 """
412 if None in (val1, val2) and val1 is not val2:
413 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700414 raise error.PayloadError('%r present without %r%s.' %
415 (present, missing,
416 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800417
418 @staticmethod
419 def _Run(cmd, send_data=None):
420 """Runs a subprocess, returns its output.
421
422 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700423 cmd: Sequence of command-line argument for invoking the subprocess.
424 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800425
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800426 Returns:
427 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800428 """
429 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
430 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700431 try:
432 result = run_process.communicate(input=send_data)
433 finally:
434 exit_code = run_process.wait()
435
436 if exit_code:
437 raise RuntimeError('Subprocess %r failed with code %r.' %
438 (cmd, exit_code))
439
440 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800441
442 @staticmethod
443 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
444 """Verifies an actual hash against a signed one.
445
446 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700447 sig_data: The raw signature data.
448 pubkey_file_name: Public key used for verifying signature.
449 actual_hash: The actual hash digest.
450 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800451
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800452 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700453 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800454 """
455 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700456 raise error.PayloadError(
457 '%s: signature size (%d) not as expected (256).' %
458 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800459 signed_data, _ = PayloadChecker._Run(
460 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
461 send_data=sig_data)
462
Gilad Arnold5502b562013-03-08 13:22:31 -0800463 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700464 raise error.PayloadError('%s: unexpected signed data length (%d).' %
465 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800466
Gilad Arnold5502b562013-03-08 13:22:31 -0800467 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700468 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
469 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800470
Gilad Arnold5502b562013-03-08 13:22:31 -0800471 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800472 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700473 raise error.PayloadError(
474 '%s: signed hash (%s) different from actual (%s).' %
475 (sig_name, common.FormatSha256(signed_hash),
476 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800477
478 @staticmethod
479 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
480 block_name=None):
481 """Checks that a given length fits given block space.
482
483 This ensures that the number of blocks allocated is appropriate for the
484 length of the data residing in these blocks.
485
486 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700487 length: The actual length of the data.
488 num_blocks: The number of blocks allocated for it.
489 block_size: The size of each block in bytes.
490 length_name: Name of length (used for error reporting).
491 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800492
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800493 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700494 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800495 """
496 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700497 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700498 raise error.PayloadError(
499 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800500 (length_name, length, block_name or '', num_blocks, block_size))
501
502 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700503 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700504 raise error.PayloadError(
505 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800506 (length_name, length, block_name or '', num_blocks - 1, block_size))
507
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700508 def _CheckManifestMinorVersion(self, report):
509 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800510
511 Args:
512 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800513
514 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700515 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800516 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700517 self.minor_version = self._CheckOptionalField(self.payload.manifest,
518 'minor_version', report)
519 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
520 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800521 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700522 'Minor version %d not compatible with payload type %s.' %
523 (self.minor_version, self.payload_type))
524 elif self.minor_version is None:
525 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800526 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700527 raise error.PayloadError('Unsupported minor version: %d' %
528 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800529
Gilad Arnold382df5c2013-05-03 12:49:28 -0700530 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800531 """Checks the payload manifest.
532
533 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700534 report: A report object to add to.
535 rootfs_part_size: Size of the rootfs partition in bytes.
536 kernel_part_size: Size of the kernel partition in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800537
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800538 Returns:
539 A tuple consisting of the partition block size used during the update
540 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800541
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800542 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700543 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800544 """
545 manifest = self.payload.manifest
546 report.AddSection('manifest')
547
548 # Check: block_size must exist and match the expected value.
549 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
550 report, 'manifest')
551 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700552 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
553 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800554
555 # Check: signatures_offset <==> signatures_size.
556 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
557 report)
558 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
559 report)
560 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
561 'signatures_offset', 'signatures_size', 'manifest')
562
563 # Check: old_kernel_info <==> old_rootfs_info.
564 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
565 'old_kernel_info', report)
566 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
567 'old_rootfs_info', report)
568 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
569 'old_rootfs_info', 'manifest')
570 if oki_msg: # equivalently, ori_msg
571 # Assert/mark delta payload.
572 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700573 raise error.PayloadError(
574 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800575 self.payload_type = _TYPE_DELTA
576
577 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700578 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800579 oki_msg, 'size', oki_report, 'old_kernel_info')
580 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
581 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700582 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800583 ori_msg, 'size', ori_report, 'old_rootfs_info')
584 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
585 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700586
587 # Check: old_{kernel,rootfs} size must fit in respective partition.
588 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700589 raise error.PayloadError(
590 'Old kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700591 (self.old_kernel_fs_size, kernel_part_size))
592 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700593 raise error.PayloadError(
594 'Old rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700595 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800596 else:
597 # Assert/mark full payload.
598 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700599 raise error.PayloadError(
600 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800601 self.payload_type = _TYPE_FULL
602
603 # Check: new_kernel_info present; contains {size, hash}.
604 nki_msg, nki_report = self._CheckMandatorySubMsg(
605 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700606 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800607 nki_msg, 'size', nki_report, 'new_kernel_info')
608 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
609 convert=common.FormatSha256)
610
611 # Check: new_rootfs_info present; contains {size, hash}.
612 nri_msg, nri_report = self._CheckMandatorySubMsg(
613 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700614 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800615 nri_msg, 'size', nri_report, 'new_rootfs_info')
616 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
617 convert=common.FormatSha256)
618
Gilad Arnold382df5c2013-05-03 12:49:28 -0700619 # Check: new_{kernel,rootfs} size must fit in respective partition.
620 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700621 raise error.PayloadError(
622 'New kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700623 (self.new_kernel_fs_size, kernel_part_size))
624 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700625 raise error.PayloadError(
626 'New rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700627 (self.new_rootfs_fs_size, rootfs_part_size))
628
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800629 # Check: minor_version makes sense for the payload type. This check should
630 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700631 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800632
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800633 def _CheckLength(self, length, total_blocks, op_name, length_name):
634 """Checks whether a length matches the space designated in extents.
635
636 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700637 length: The total length of the data.
638 total_blocks: The total number of blocks in extents.
639 op_name: Operation name (for error reporting).
640 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800641
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800642 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700643 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800644 """
645 # Check: length is non-zero.
646 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700647 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800648
649 # Check that length matches number of blocks.
650 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
651 '%s: %s' % (op_name, length_name))
652
Gilad Arnold382df5c2013-05-03 12:49:28 -0700653 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800654 allow_pseudo=False, allow_signature=False):
655 """Checks a sequence of extents.
656
657 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700658 extents: The sequence of extents to check.
659 usable_size: The usable size of the partition to which the extents apply.
660 block_counters: Array of counters corresponding to the number of blocks.
661 name: The name of the extent block.
662 allow_pseudo: Whether or not pseudo block numbers are allowed.
663 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800664
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800665 Returns:
666 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800667
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800668 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700669 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800670 """
671 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800672 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700673 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800674 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
675 None, ex_name)
676 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
677 ex_name)
678 end_block = start_block + num_blocks
679
680 # Check: num_blocks > 0.
681 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700682 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800683
684 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700685 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700686 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700687 raise error.PayloadError(
688 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700689 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800690
691 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700692 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800693 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800694 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
695 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
696 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700697 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800698
699 total_num_blocks += num_blocks
700
701 return total_num_blocks
702
703 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
704 """Specific checks for REPLACE/REPLACE_BZ operations.
705
706 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700707 op: The operation object from the manifest.
708 data_length: The length of the data blob associated with the operation.
709 total_dst_blocks: Total number of blocks in dst_extents.
710 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800711
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800712 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700713 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800714 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700715 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800716 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700717 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800718
Gilad Arnoldcb638912013-06-24 04:57:11 -0700719 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800720 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700721 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800722
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800723 if op.type == common.OpType.REPLACE:
724 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
725 self.block_size,
726 op_name + '.data_length', 'dst')
727 else:
728 # Check: data_length must be smaller than the alotted dst blocks.
729 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700730 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800731 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700732 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800733 (op_name, data_length, total_dst_blocks, self.block_size))
734
735 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
736 total_dst_blocks, op_name):
737 """Specific checks for MOVE operations.
738
739 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700740 op: The operation object from the manifest.
741 data_offset: The offset of a data blob for the operation.
742 total_src_blocks: Total number of blocks in src_extents.
743 total_dst_blocks: Total number of blocks in dst_extents.
744 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800745
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800746 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700747 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800748 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700749 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800750 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700751 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800752
Gilad Arnoldcb638912013-06-24 04:57:11 -0700753 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800754 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700755 raise error.PayloadError(
756 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800757 (op_name, total_src_blocks, total_dst_blocks))
758
Gilad Arnoldcb638912013-06-24 04:57:11 -0700759 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800760 i = 0
761 src_extent_iter = iter(op.src_extents)
762 dst_extent_iter = iter(op.dst_extents)
763 src_extent = dst_extent = None
764 src_idx = src_num = dst_idx = dst_num = 0
765 while i < total_src_blocks:
766 # Get the next source extent, if needed.
767 if not src_extent:
768 try:
769 src_extent = src_extent_iter.next()
770 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700771 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
772 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800773 src_idx = src_extent.start_block
774 src_num = src_extent.num_blocks
775
776 # Get the next dest extent, if needed.
777 if not dst_extent:
778 try:
779 dst_extent = dst_extent_iter.next()
780 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700781 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
782 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800783 dst_idx = dst_extent.start_block
784 dst_num = dst_extent.num_blocks
785
Allie Woodb065e132015-04-24 10:20:27 -0700786 # Check: start block is not 0. See crbug/480751; there are still versions
787 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
788 # so we need to fail payloads that try to MOVE to/from block 0.
789 if src_idx == 0 or dst_idx == 0:
790 raise error.PayloadError(
791 '%s: MOVE operation cannot have extent with start block 0' %
792 op_name)
793
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700794 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700795 raise error.PayloadError(
796 '%s: src/dst block number %d is the same (%d).' %
797 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800798
799 advance = min(src_num, dst_num)
800 i += advance
801
802 src_idx += advance
803 src_num -= advance
804 if src_num == 0:
805 src_extent = None
806
807 dst_idx += advance
808 dst_num -= advance
809 if dst_num == 0:
810 dst_extent = None
811
Gilad Arnold5502b562013-03-08 13:22:31 -0800812 # Make sure we've exhausted all src/dst extents.
813 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700814 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800815 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700816 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800817
Sen Jiang92161a72016-06-28 16:09:38 -0700818 def _CheckAnyDiffOperation(self, data_length, total_dst_blocks, op_name):
Amin Hassani49fdb092017-08-04 13:10:59 -0700819 """Specific checks for BSDIFF, SOURCE_BSDIFF and PUFFDIFF operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800820
821 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700822 data_length: The length of the data blob associated with the operation.
823 total_dst_blocks: Total number of blocks in dst_extents.
824 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800825
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800826 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700827 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800828 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800829 # Check: data_{offset,length} present.
830 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700831 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800832
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800833 # Check: data_length is strictly smaller than the alotted dst blocks.
834 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700835 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800836 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700837 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800838 (op_name, data_length, total_dst_blocks, self.block_size,
839 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800840
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800841 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
842 total_dst_blocks, op_name):
843 """Specific checks for SOURCE_COPY.
844
845 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800846 data_offset: The offset of a data blob for the operation.
847 total_src_blocks: Total number of blocks in src_extents.
848 total_dst_blocks: Total number of blocks in dst_extents.
849 op_name: Operation name for error reporting.
850
851 Raises:
852 error.PayloadError if any check fails.
853 """
854 # Check: No data_{offset,length}.
855 if data_offset is not None:
856 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
857
858 # Check: total_src_blocks == total_dst_blocks.
859 if total_src_blocks != total_dst_blocks:
860 raise error.PayloadError(
861 '%s: total src blocks (%d) != total dst blocks (%d).' %
862 (op_name, total_src_blocks, total_dst_blocks))
863
Sen Jiangd6122bb2015-12-11 10:27:04 -0800864 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800865 """Specific checks for SOURCE_* operations.
866
867 Args:
868 op: The operation object from the manifest.
869 total_src_blocks: Total number of blocks in src_extents.
870 op_name: Operation name for error reporting.
871
872 Raises:
873 error.PayloadError if any check fails.
874 """
875 # Check: total_src_blocks != 0.
876 if total_src_blocks == 0:
877 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
878
Sen Jiang92161a72016-06-28 16:09:38 -0700879 # Check: src_sha256_hash present in minor version >= 3.
880 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800881 raise error.PayloadError('%s: source hash missing.' % op_name)
882
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800883 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700884 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700885 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800886 """Checks a single update operation.
887
888 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700889 op: The operation object.
890 op_name: Operation name string for error reporting.
891 is_last: Whether this is the last operation in the sequence.
892 old_block_counters: Arrays of block read counters.
893 new_block_counters: Arrays of block write counters.
894 old_usable_size: The overall usable size for src data in bytes.
895 new_usable_size: The overall usable size for dst data in bytes.
896 prev_data_offset: Offset of last used data bytes.
897 allow_signature: Whether this may be a signature operation.
898 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800899
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800900 Returns:
901 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800902
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800903 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700904 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800905 """
906 # Check extents.
907 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700908 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800909 op_name + '.src_extents', allow_pseudo=True)
910 allow_signature_in_extents = (allow_signature and is_last and
911 op.type == common.OpType.REPLACE)
912 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700913 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700914 op_name + '.dst_extents',
915 allow_pseudo=(not self.check_dst_pseudo_extents),
916 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800917
918 # Check: data_offset present <==> data_length present.
919 data_offset = self._CheckOptionalField(op, 'data_offset', None)
920 data_length = self._CheckOptionalField(op, 'data_length', None)
921 self._CheckPresentIff(data_offset, data_length, 'data_offset',
922 'data_length', op_name)
923
Gilad Arnoldcb638912013-06-24 04:57:11 -0700924 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800925 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700926 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800927
928 # Check {src,dst}_length, if present.
929 if op.HasField('src_length'):
930 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
931 if op.HasField('dst_length'):
932 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
933
934 if op.HasField('data_sha256_hash'):
935 blob_hash_counts['hashed'] += 1
936
Gilad Arnoldcb638912013-06-24 04:57:11 -0700937 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800938 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700939 raise error.PayloadError(
940 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800941 op_name)
942
Gilad Arnoldcb638912013-06-24 04:57:11 -0700943 # Check: Hash verifies correctly.
944 # pylint cannot find the method in hashlib, for some reason.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800945 # pylint: disable=E1101
946 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
947 data_length))
948 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700949 raise error.PayloadError(
950 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700951 (op_name, common.FormatSha256(op.data_sha256_hash),
952 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800953 elif data_offset is not None:
954 if allow_signature_in_extents:
955 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700956 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800957 blob_hash_counts['unhashed'] += 1
958 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700959 raise error.PayloadError('%s: unhashed operation not allowed.' %
960 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800961
962 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700963 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800964 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700965 raise error.PayloadError(
966 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800967 (op_name, data_offset, prev_data_offset))
968
969 # Type-specific checks.
970 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
971 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700972 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800973 self._CheckMoveOperation(op, data_offset, total_src_blocks,
974 total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700975 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Sen Jiang92161a72016-06-28 16:09:38 -0700976 self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
977 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800978 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
979 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800980 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -0700981 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
982 self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
983 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani49fdb092017-08-04 13:10:59 -0700984 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 4:
Sen Jiang92161a72016-06-28 16:09:38 -0700985 self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800986 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800987 else:
Allie Wood7cf9f132015-02-26 14:28:19 -0800988 raise error.PayloadError(
989 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700990 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800991 return data_length if data_length is not None else 0
992
Gilad Arnold382df5c2013-05-03 12:49:28 -0700993 def _SizeToNumBlocks(self, size):
994 """Returns the number of blocks needed to contain a given byte size."""
995 return (size + self.block_size - 1) / self.block_size
996
997 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800998 """Returns a freshly initialized array of block counters.
999
Gilad Arnoldcb638912013-06-24 04:57:11 -07001000 Note that the generated array is not portable as is due to byte-ordering
1001 issues, hence it should not be serialized.
1002
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001003 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001004 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001005
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001006 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001007 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001008 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001009 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001010 return array.array('H',
1011 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001012
Gilad Arnold382df5c2013-05-03 12:49:28 -07001013 def _CheckOperations(self, operations, report, base_name, old_fs_size,
1014 new_fs_size, new_usable_size, prev_data_offset,
1015 allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001016 """Checks a sequence of update operations.
1017
1018 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001019 operations: The sequence of operations to check.
1020 report: The report object to add to.
1021 base_name: The name of the operation block.
1022 old_fs_size: The old filesystem size in bytes.
1023 new_fs_size: The new filesystem size in bytes.
1024 new_usable_size: The overall usable size of the new partition in bytes.
1025 prev_data_offset: Offset of last used data bytes.
1026 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001027
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001028 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001029 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001030
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001031 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001032 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001033 """
1034 # The total size of data blobs used by operations scanned thus far.
1035 total_data_used = 0
1036 # Counts of specific operation types.
1037 op_counts = {
1038 common.OpType.REPLACE: 0,
1039 common.OpType.REPLACE_BZ: 0,
1040 common.OpType.MOVE: 0,
1041 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001042 common.OpType.SOURCE_COPY: 0,
1043 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani49fdb092017-08-04 13:10:59 -07001044 common.OpType.PUFFDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001045 }
1046 # Total blob sizes for each operation type.
1047 op_blob_totals = {
1048 common.OpType.REPLACE: 0,
1049 common.OpType.REPLACE_BZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001050 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001051 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001052 # SOURCE_COPY operations don't have blobs.
1053 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani49fdb092017-08-04 13:10:59 -07001054 common.OpType.PUFFDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001055 }
1056 # Counts of hashed vs unhashed operations.
1057 blob_hash_counts = {
1058 'hashed': 0,
1059 'unhashed': 0,
1060 }
1061 if allow_signature:
1062 blob_hash_counts['signature'] = 0
1063
1064 # Allocate old and new block counters.
Gilad Arnold4f50b412013-05-14 09:19:17 -07001065 old_block_counters = (self._AllocBlockCounters(new_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001066 if old_fs_size else None)
1067 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001068
1069 # Process and verify each operation.
1070 op_num = 0
1071 for op, op_name in common.OperationIter(operations, base_name):
1072 op_num += 1
1073
Gilad Arnoldcb638912013-06-24 04:57:11 -07001074 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001075 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001076 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001077 op_counts[op.type] += 1
1078
1079 is_last = op_num == len(operations)
1080 curr_data_used = self._CheckOperation(
1081 op, op_name, is_last, old_block_counters, new_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001082 new_usable_size if old_fs_size else 0, new_usable_size,
1083 prev_data_offset + total_data_used, allow_signature,
1084 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001085 if curr_data_used:
1086 op_blob_totals[op.type] += curr_data_used
1087 total_data_used += curr_data_used
1088
1089 # Report totals and breakdown statistics.
1090 report.AddField('total operations', op_num)
1091 report.AddField(
1092 None,
1093 histogram.Histogram.FromCountDict(op_counts,
1094 key_names=common.OpType.NAMES),
1095 indent=1)
1096 report.AddField('total blobs', sum(blob_hash_counts.values()))
1097 report.AddField(None,
1098 histogram.Histogram.FromCountDict(blob_hash_counts),
1099 indent=1)
1100 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1101 report.AddField(
1102 None,
1103 histogram.Histogram.FromCountDict(op_blob_totals,
1104 formatter=_AddHumanReadableSize,
1105 key_names=common.OpType.NAMES),
1106 indent=1)
1107
1108 # Report read/write histograms.
1109 if old_block_counters:
1110 report.AddField('block read hist',
1111 histogram.Histogram.FromKeyList(old_block_counters),
1112 linebreak=True, indent=1)
1113
Gilad Arnold382df5c2013-05-03 12:49:28 -07001114 new_write_hist = histogram.Histogram.FromKeyList(
1115 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1116 report.AddField('block write hist', new_write_hist, linebreak=True,
1117 indent=1)
1118
Gilad Arnoldcb638912013-06-24 04:57:11 -07001119 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001120 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001121 raise error.PayloadError(
1122 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001123 base_name)
1124
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001125 return total_data_used
1126
1127 def _CheckSignatures(self, report, pubkey_file_name):
1128 """Checks a payload's signature block."""
1129 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1130 sigs = update_metadata_pb2.Signatures()
1131 sigs.ParseFromString(sigs_raw)
1132 report.AddSection('signatures')
1133
Gilad Arnoldcb638912013-06-24 04:57:11 -07001134 # Check: At least one signature present.
1135 # pylint cannot see through the protobuf object, it seems.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001136 # pylint: disable=E1101
1137 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001138 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001139
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001140 last_ops_section = (self.payload.manifest.kernel_install_operations or
1141 self.payload.manifest.install_operations)
1142 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001143 # Check: signatures_{offset,size} must match the last (fake) operation.
1144 if not (fake_sig_op.type == common.OpType.REPLACE and
1145 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001146 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001147 raise error.PayloadError(
1148 'Signatures_{offset,size} (%d+%d) does not match last operation '
1149 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001150 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1151 fake_sig_op.data_length))
1152
1153 # Compute the checksum of all data up to signature blob.
1154 # TODO(garnold) we're re-reading the whole data section into a string
1155 # just to compute the checksum; instead, we could do it incrementally as
1156 # we read the blobs one-by-one, under the assumption that we're reading
1157 # them in order (which currently holds). This should be reconsidered.
1158 payload_hasher = self.payload.manifest_hasher.copy()
1159 common.Read(self.payload.payload_file, self.sigs_offset,
1160 offset=self.payload.data_offset, hasher=payload_hasher)
1161
1162 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1163 sig_report = report.AddSubReport(sig_name)
1164
Gilad Arnoldcb638912013-06-24 04:57:11 -07001165 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001166 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1167 self._CheckMandatoryField(sig, 'data', None, sig_name)
1168 sig_report.AddField('data len', len(sig.data))
1169
Gilad Arnoldcb638912013-06-24 04:57:11 -07001170 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001171 if sig.version == 1:
1172 self._CheckSha256Signature(sig.data, pubkey_file_name,
1173 payload_hasher.digest(), sig_name)
1174 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001175 raise error.PayloadError('Unknown signature version (%d).' %
1176 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001177
1178 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001179 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001180 """Checker entry point, invoking all checks.
1181
1182 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001183 pubkey_file_name: Public key used for signature verification.
1184 metadata_sig_file: Metadata signature, if verification is desired.
Gilad Arnold06eea332015-07-13 18:06:33 -07001185 rootfs_part_size: The size of rootfs partitions in bytes (default: infer
1186 based on payload type and version).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001187 kernel_part_size: The size of kernel partitions in bytes (default: use
1188 reported filesystem size).
1189 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001190
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001191 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001192 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001193 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001194 if not pubkey_file_name:
1195 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1196
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001197 report = _PayloadReport()
1198
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001199 # Get payload file size.
1200 self.payload.payload_file.seek(0, 2)
1201 payload_file_size = self.payload.payload_file.tell()
1202 self.payload.ResetFile()
1203
1204 try:
1205 # Check metadata signature (if provided).
1206 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001207 metadata_sig = base64.b64decode(metadata_sig_file.read())
1208 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1209 self.payload.manifest_hasher.digest(),
1210 'metadata signature')
1211
Gilad Arnoldcb638912013-06-24 04:57:11 -07001212 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001213 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001214 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001215 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001216 raise error.PayloadError('Unknown payload version (%d).' %
1217 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001218 report.AddField('version', self.payload.header.version)
1219 report.AddField('manifest len', self.payload.header.manifest_len)
1220
Gilad Arnoldcb638912013-06-24 04:57:11 -07001221 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001222 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001223 assert self.payload_type, 'payload type should be known by now'
1224
Gilad Arnold06eea332015-07-13 18:06:33 -07001225 # Infer the usable partition size when validating rootfs operations:
1226 # - If rootfs partition size was provided, use that.
1227 # - Otherwise, if this is an older delta (minor version < 2), stick with
1228 # a known constant size. This is necessary because older deltas may
1229 # exceed the filesystem size when moving data blocks around.
1230 # - Otherwise, use the encoded filesystem size.
1231 new_rootfs_usable_size = self.new_rootfs_fs_size
1232 if rootfs_part_size:
1233 new_rootfs_usable_size = rootfs_part_size
1234 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
1235 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
1236
Gilad Arnoldcb638912013-06-24 04:57:11 -07001237 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001238 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1239 # no explicit size provided *and* the partition size is not embedded in
1240 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001241 report.AddSection('rootfs operations')
1242 total_blob_size = self._CheckOperations(
1243 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001244 'install_operations', self.old_rootfs_fs_size,
Gilad Arnold06eea332015-07-13 18:06:33 -07001245 self.new_rootfs_fs_size, new_rootfs_usable_size, 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001246
Gilad Arnoldcb638912013-06-24 04:57:11 -07001247 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001248 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001249 report.AddSection('kernel operations')
1250 total_blob_size += self._CheckOperations(
1251 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001252 'kernel_install_operations', self.old_kernel_fs_size,
1253 self.new_kernel_fs_size,
1254 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1255 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001256
Gilad Arnoldcb638912013-06-24 04:57:11 -07001257 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001258 used_payload_size = self.payload.data_offset + total_blob_size
1259 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001260 raise error.PayloadError(
1261 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001262 (used_payload_size, payload_file_size))
1263
Gilad Arnoldcb638912013-06-24 04:57:11 -07001264 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001265 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001266 self._CheckSignatures(report, pubkey_file_name)
1267
Gilad Arnoldcb638912013-06-24 04:57:11 -07001268 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001269 report.AddSection('summary')
1270 report.AddField('update type', self.payload_type)
1271
1272 report.Finalize()
1273 finally:
1274 if report_out_file:
1275 report.Dump(report_out_file)