blob: c6136428e8cb5e07c2a068bc910908f800427a68 [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
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700818 def _CheckZeroOperation(self, op, op_name):
819 """Specific checks for ZERO operations.
820
821 Args:
822 op: The operation object from the manifest.
823 op_name: Operation name for error reporting.
824
825 Raises:
826 error.PayloadError if any check fails.
827 """
828 # Check: Does not contain src extents, data_length and data_offset.
829 if op.src_extents:
830 raise error.PayloadError('%s: contains src_extents.' % op_name)
831 if op.data_length:
832 raise error.PayloadError('%s: contains data_length.' % op_name)
833 if op.data_offset:
834 raise error.PayloadError('%s: contains data_offset.' % op_name)
835
Sen Jiang92161a72016-06-28 16:09:38 -0700836 def _CheckAnyDiffOperation(self, data_length, total_dst_blocks, op_name):
Amin Hassani5ef5d452017-08-04 13:10:59 -0700837 """Specific checks for BSDIFF, SOURCE_BSDIFF and PUFFDIFF operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800838
839 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700840 data_length: The length of the data blob associated with the operation.
841 total_dst_blocks: Total number of blocks in dst_extents.
842 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800843
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800844 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700845 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800846 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800847 # Check: data_{offset,length} present.
848 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700849 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800850
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800851 # Check: data_length is strictly smaller than the alotted dst blocks.
852 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700853 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800854 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700855 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800856 (op_name, data_length, total_dst_blocks, self.block_size,
857 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800858
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800859 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
860 total_dst_blocks, op_name):
861 """Specific checks for SOURCE_COPY.
862
863 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800864 data_offset: The offset of a data blob for the operation.
865 total_src_blocks: Total number of blocks in src_extents.
866 total_dst_blocks: Total number of blocks in dst_extents.
867 op_name: Operation name for error reporting.
868
869 Raises:
870 error.PayloadError if any check fails.
871 """
872 # Check: No data_{offset,length}.
873 if data_offset is not None:
874 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
875
876 # Check: total_src_blocks == total_dst_blocks.
877 if total_src_blocks != total_dst_blocks:
878 raise error.PayloadError(
879 '%s: total src blocks (%d) != total dst blocks (%d).' %
880 (op_name, total_src_blocks, total_dst_blocks))
881
Sen Jiangd6122bb2015-12-11 10:27:04 -0800882 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800883 """Specific checks for SOURCE_* operations.
884
885 Args:
886 op: The operation object from the manifest.
887 total_src_blocks: Total number of blocks in src_extents.
888 op_name: Operation name for error reporting.
889
890 Raises:
891 error.PayloadError if any check fails.
892 """
893 # Check: total_src_blocks != 0.
894 if total_src_blocks == 0:
895 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
896
Sen Jiang92161a72016-06-28 16:09:38 -0700897 # Check: src_sha256_hash present in minor version >= 3.
898 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800899 raise error.PayloadError('%s: source hash missing.' % op_name)
900
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800901 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700902 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700903 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800904 """Checks a single update operation.
905
906 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700907 op: The operation object.
908 op_name: Operation name string for error reporting.
909 is_last: Whether this is the last operation in the sequence.
910 old_block_counters: Arrays of block read counters.
911 new_block_counters: Arrays of block write counters.
912 old_usable_size: The overall usable size for src data in bytes.
913 new_usable_size: The overall usable size for dst data in bytes.
914 prev_data_offset: Offset of last used data bytes.
915 allow_signature: Whether this may be a signature operation.
916 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800917
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800918 Returns:
919 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800920
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800921 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700922 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800923 """
924 # Check extents.
925 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700926 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800927 op_name + '.src_extents', allow_pseudo=True)
928 allow_signature_in_extents = (allow_signature and is_last and
929 op.type == common.OpType.REPLACE)
930 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700931 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700932 op_name + '.dst_extents',
933 allow_pseudo=(not self.check_dst_pseudo_extents),
934 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800935
936 # Check: data_offset present <==> data_length present.
937 data_offset = self._CheckOptionalField(op, 'data_offset', None)
938 data_length = self._CheckOptionalField(op, 'data_length', None)
939 self._CheckPresentIff(data_offset, data_length, 'data_offset',
940 'data_length', op_name)
941
Gilad Arnoldcb638912013-06-24 04:57:11 -0700942 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800943 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700944 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800945
946 # Check {src,dst}_length, if present.
947 if op.HasField('src_length'):
948 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
949 if op.HasField('dst_length'):
950 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
951
952 if op.HasField('data_sha256_hash'):
953 blob_hash_counts['hashed'] += 1
954
Gilad Arnoldcb638912013-06-24 04:57:11 -0700955 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800956 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700957 raise error.PayloadError(
958 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800959 op_name)
960
Gilad Arnoldcb638912013-06-24 04:57:11 -0700961 # Check: Hash verifies correctly.
962 # pylint cannot find the method in hashlib, for some reason.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800963 # pylint: disable=E1101
964 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
965 data_length))
966 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700967 raise error.PayloadError(
968 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700969 (op_name, common.FormatSha256(op.data_sha256_hash),
970 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800971 elif data_offset is not None:
972 if allow_signature_in_extents:
973 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700974 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800975 blob_hash_counts['unhashed'] += 1
976 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700977 raise error.PayloadError('%s: unhashed operation not allowed.' %
978 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800979
980 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700981 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800982 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700983 raise error.PayloadError(
984 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800985 (op_name, data_offset, prev_data_offset))
986
987 # Type-specific checks.
988 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
989 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700990 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800991 self._CheckMoveOperation(op, data_offset, total_src_blocks,
992 total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700993 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
994 self._CheckZeroOperation(op, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700995 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Sen Jiang92161a72016-06-28 16:09:38 -0700996 self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
997 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800998 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
999 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001000 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001001 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
1002 self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
1003 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani5ef5d452017-08-04 13:10:59 -07001004 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 4:
Sen Jiang92161a72016-06-28 16:09:38 -07001005 self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001006 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001007 else:
Allie Wood7cf9f132015-02-26 14:28:19 -08001008 raise error.PayloadError(
1009 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001010 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001011 return data_length if data_length is not None else 0
1012
Gilad Arnold382df5c2013-05-03 12:49:28 -07001013 def _SizeToNumBlocks(self, size):
1014 """Returns the number of blocks needed to contain a given byte size."""
1015 return (size + self.block_size - 1) / self.block_size
1016
1017 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001018 """Returns a freshly initialized array of block counters.
1019
Gilad Arnoldcb638912013-06-24 04:57:11 -07001020 Note that the generated array is not portable as is due to byte-ordering
1021 issues, hence it should not be serialized.
1022
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001023 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001024 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001025
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001026 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001027 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001028 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001029 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001030 return array.array('H',
1031 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001032
Gilad Arnold382df5c2013-05-03 12:49:28 -07001033 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001034 new_fs_size, old_usable_size, new_usable_size,
1035 prev_data_offset, allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001036 """Checks a sequence of update operations.
1037
1038 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001039 operations: The sequence of operations to check.
1040 report: The report object to add to.
1041 base_name: The name of the operation block.
1042 old_fs_size: The old filesystem size in bytes.
1043 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001044 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001045 new_usable_size: The overall usable size of the new partition in bytes.
1046 prev_data_offset: Offset of last used data bytes.
1047 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001048
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001049 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001050 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001051
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001052 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001053 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001054 """
1055 # The total size of data blobs used by operations scanned thus far.
1056 total_data_used = 0
1057 # Counts of specific operation types.
1058 op_counts = {
1059 common.OpType.REPLACE: 0,
1060 common.OpType.REPLACE_BZ: 0,
1061 common.OpType.MOVE: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001062 common.OpType.ZERO: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001063 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001064 common.OpType.SOURCE_COPY: 0,
1065 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001066 common.OpType.PUFFDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001067 }
1068 # Total blob sizes for each operation type.
1069 op_blob_totals = {
1070 common.OpType.REPLACE: 0,
1071 common.OpType.REPLACE_BZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001072 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001073 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001074 # SOURCE_COPY operations don't have blobs.
1075 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001076 common.OpType.PUFFDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001077 }
1078 # Counts of hashed vs unhashed operations.
1079 blob_hash_counts = {
1080 'hashed': 0,
1081 'unhashed': 0,
1082 }
1083 if allow_signature:
1084 blob_hash_counts['signature'] = 0
1085
1086 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001087 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001088 if old_fs_size else None)
1089 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001090
1091 # Process and verify each operation.
1092 op_num = 0
1093 for op, op_name in common.OperationIter(operations, base_name):
1094 op_num += 1
1095
Gilad Arnoldcb638912013-06-24 04:57:11 -07001096 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001097 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001098 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001099 op_counts[op.type] += 1
1100
1101 is_last = op_num == len(operations)
1102 curr_data_used = self._CheckOperation(
1103 op, op_name, is_last, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001104 old_usable_size, new_usable_size,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001105 prev_data_offset + total_data_used, allow_signature,
1106 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001107 if curr_data_used:
1108 op_blob_totals[op.type] += curr_data_used
1109 total_data_used += curr_data_used
1110
1111 # Report totals and breakdown statistics.
1112 report.AddField('total operations', op_num)
1113 report.AddField(
1114 None,
1115 histogram.Histogram.FromCountDict(op_counts,
1116 key_names=common.OpType.NAMES),
1117 indent=1)
1118 report.AddField('total blobs', sum(blob_hash_counts.values()))
1119 report.AddField(None,
1120 histogram.Histogram.FromCountDict(blob_hash_counts),
1121 indent=1)
1122 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1123 report.AddField(
1124 None,
1125 histogram.Histogram.FromCountDict(op_blob_totals,
1126 formatter=_AddHumanReadableSize,
1127 key_names=common.OpType.NAMES),
1128 indent=1)
1129
1130 # Report read/write histograms.
1131 if old_block_counters:
1132 report.AddField('block read hist',
1133 histogram.Histogram.FromKeyList(old_block_counters),
1134 linebreak=True, indent=1)
1135
Gilad Arnold382df5c2013-05-03 12:49:28 -07001136 new_write_hist = histogram.Histogram.FromKeyList(
1137 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1138 report.AddField('block write hist', new_write_hist, linebreak=True,
1139 indent=1)
1140
Gilad Arnoldcb638912013-06-24 04:57:11 -07001141 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001142 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001143 raise error.PayloadError(
1144 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001145 base_name)
1146
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001147 return total_data_used
1148
1149 def _CheckSignatures(self, report, pubkey_file_name):
1150 """Checks a payload's signature block."""
1151 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1152 sigs = update_metadata_pb2.Signatures()
1153 sigs.ParseFromString(sigs_raw)
1154 report.AddSection('signatures')
1155
Gilad Arnoldcb638912013-06-24 04:57:11 -07001156 # Check: At least one signature present.
1157 # pylint cannot see through the protobuf object, it seems.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001158 # pylint: disable=E1101
1159 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001160 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001161
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001162 last_ops_section = (self.payload.manifest.kernel_install_operations or
1163 self.payload.manifest.install_operations)
1164 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001165 # Check: signatures_{offset,size} must match the last (fake) operation.
1166 if not (fake_sig_op.type == common.OpType.REPLACE and
1167 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001168 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001169 raise error.PayloadError(
1170 'Signatures_{offset,size} (%d+%d) does not match last operation '
1171 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001172 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1173 fake_sig_op.data_length))
1174
1175 # Compute the checksum of all data up to signature blob.
1176 # TODO(garnold) we're re-reading the whole data section into a string
1177 # just to compute the checksum; instead, we could do it incrementally as
1178 # we read the blobs one-by-one, under the assumption that we're reading
1179 # them in order (which currently holds). This should be reconsidered.
1180 payload_hasher = self.payload.manifest_hasher.copy()
1181 common.Read(self.payload.payload_file, self.sigs_offset,
1182 offset=self.payload.data_offset, hasher=payload_hasher)
1183
1184 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1185 sig_report = report.AddSubReport(sig_name)
1186
Gilad Arnoldcb638912013-06-24 04:57:11 -07001187 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001188 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1189 self._CheckMandatoryField(sig, 'data', None, sig_name)
1190 sig_report.AddField('data len', len(sig.data))
1191
Gilad Arnoldcb638912013-06-24 04:57:11 -07001192 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001193 if sig.version == 1:
1194 self._CheckSha256Signature(sig.data, pubkey_file_name,
1195 payload_hasher.digest(), sig_name)
1196 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001197 raise error.PayloadError('Unknown signature version (%d).' %
1198 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001199
1200 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001201 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001202 """Checker entry point, invoking all checks.
1203
1204 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001205 pubkey_file_name: Public key used for signature verification.
1206 metadata_sig_file: Metadata signature, if verification is desired.
Gilad Arnold06eea332015-07-13 18:06:33 -07001207 rootfs_part_size: The size of rootfs partitions in bytes (default: infer
1208 based on payload type and version).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001209 kernel_part_size: The size of kernel partitions in bytes (default: use
1210 reported filesystem size).
1211 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001212
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001213 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001214 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001215 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001216 if not pubkey_file_name:
1217 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1218
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001219 report = _PayloadReport()
1220
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001221 # Get payload file size.
1222 self.payload.payload_file.seek(0, 2)
1223 payload_file_size = self.payload.payload_file.tell()
1224 self.payload.ResetFile()
1225
1226 try:
1227 # Check metadata signature (if provided).
1228 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001229 metadata_sig = base64.b64decode(metadata_sig_file.read())
1230 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1231 self.payload.manifest_hasher.digest(),
1232 'metadata signature')
1233
Gilad Arnoldcb638912013-06-24 04:57:11 -07001234 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001235 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001236 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001237 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001238 raise error.PayloadError('Unknown payload version (%d).' %
1239 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001240 report.AddField('version', self.payload.header.version)
1241 report.AddField('manifest len', self.payload.header.manifest_len)
1242
Gilad Arnoldcb638912013-06-24 04:57:11 -07001243 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001244 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001245 assert self.payload_type, 'payload type should be known by now'
1246
Gilad Arnold06eea332015-07-13 18:06:33 -07001247 # Infer the usable partition size when validating rootfs operations:
1248 # - If rootfs partition size was provided, use that.
1249 # - Otherwise, if this is an older delta (minor version < 2), stick with
1250 # a known constant size. This is necessary because older deltas may
1251 # exceed the filesystem size when moving data blocks around.
1252 # - Otherwise, use the encoded filesystem size.
1253 new_rootfs_usable_size = self.new_rootfs_fs_size
Amin Hassaniae853742017-10-11 10:27:27 -07001254 old_rootfs_usable_size = self.old_rootfs_fs_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001255 if rootfs_part_size:
1256 new_rootfs_usable_size = rootfs_part_size
Amin Hassaniae853742017-10-11 10:27:27 -07001257 old_rootfs_usable_size = rootfs_part_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001258 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
1259 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Amin Hassaniae853742017-10-11 10:27:27 -07001260 old_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Gilad Arnold06eea332015-07-13 18:06:33 -07001261
Gilad Arnoldcb638912013-06-24 04:57:11 -07001262 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001263 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1264 # no explicit size provided *and* the partition size is not embedded in
1265 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001266 report.AddSection('rootfs operations')
1267 total_blob_size = self._CheckOperations(
1268 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001269 'install_operations', self.old_rootfs_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001270 self.new_rootfs_fs_size, old_rootfs_usable_size,
1271 new_rootfs_usable_size, 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001272
Gilad Arnoldcb638912013-06-24 04:57:11 -07001273 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001274 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001275 report.AddSection('kernel operations')
1276 total_blob_size += self._CheckOperations(
1277 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001278 'kernel_install_operations', self.old_kernel_fs_size,
1279 self.new_kernel_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001280 kernel_part_size if kernel_part_size else self.old_kernel_fs_size,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001281 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1282 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001283
Gilad Arnoldcb638912013-06-24 04:57:11 -07001284 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001285 used_payload_size = self.payload.data_offset + total_blob_size
1286 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001287 raise error.PayloadError(
1288 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001289 (used_payload_size, payload_file_size))
1290
Gilad Arnoldcb638912013-06-24 04:57:11 -07001291 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001292 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001293 self._CheckSignatures(report, pubkey_file_name)
1294
Gilad Arnoldcb638912013-06-24 04:57:11 -07001295 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001296 report.AddSection('summary')
1297 report.AddField('update type', self.payload_type)
1298
1299 report.Finalize()
1300 finally:
1301 if report_out_file:
1302 report.Dump(report_out_file)