blob: 3cf98cae79a0b97f063e2aa9de37563ae3406f28 [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
Amin Hassanif1d6cea2017-12-07 12:13:03 -0800325 # TODO(*): When fixing crbug.com/794404, the major version should be
326 # correclty handled in update_payload scripts. So stop forcing
327 # major_verions=1 here and set it to the correct value.
328 self.major_version = 1;
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800329
330 @staticmethod
331 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
332 msg_name=None, linebreak=False, indent=0):
333 """Adds an element from a protobuf message to the payload report.
334
335 Checks to see whether a message contains a given element, and if so adds
336 the element value to the provided report. A missing mandatory element
337 causes an exception to be raised.
338
339 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700340 msg: The message containing the element.
341 name: The name of the element.
342 report: A report object to add the element name/value to.
343 is_mandatory: Whether or not this element must be present.
344 is_submsg: Whether this element is itself a message.
345 convert: A function for converting the element value for reporting.
346 msg_name: The name of the message object (for error reporting).
347 linebreak: Whether the value report should induce a line break.
348 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800349
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800350 Returns:
351 A pair consisting of the element value and the generated sub-report for
352 it (if the element is a sub-message, None otherwise). If the element is
353 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800354
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800355 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700356 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800357 """
358 if not msg.HasField(name):
359 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700360 raise error.PayloadError('%smissing mandatory %s %r.' %
361 (msg_name + ' ' if msg_name else '',
362 'sub-message' if is_submsg else 'field',
363 name))
364 return None, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800365
366 value = getattr(msg, name)
367 if is_submsg:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700368 return value, report and report.AddSubReport(name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800369 else:
370 if report:
371 report.AddField(name, convert(value), linebreak=linebreak,
372 indent=indent)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700373 return value, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800374
375 @staticmethod
376 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
377 linebreak=False, indent=0):
378 """Adds a mandatory field; returning first component from _CheckElem."""
379 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
380 convert=convert, msg_name=msg_name,
381 linebreak=linebreak, indent=indent)[0]
382
383 @staticmethod
384 def _CheckOptionalField(msg, field_name, report, convert=str,
385 linebreak=False, indent=0):
386 """Adds an optional field; returning first component from _CheckElem."""
387 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
388 convert=convert, linebreak=linebreak,
389 indent=indent)[0]
390
391 @staticmethod
392 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
393 """Adds a mandatory sub-message; wrapper for _CheckElem."""
394 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
395 msg_name)
396
397 @staticmethod
398 def _CheckOptionalSubMsg(msg, submsg_name, report):
399 """Adds an optional sub-message; wrapper for _CheckElem."""
400 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
401
402 @staticmethod
403 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
404 """Checks that val1 is None iff val2 is None.
405
406 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700407 val1: first value to be compared.
408 val2: second value to be compared.
409 name1: name of object holding the first value.
410 name2: name of object holding the second value.
411 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800412
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800413 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700414 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800415 """
416 if None in (val1, val2) and val1 is not val2:
417 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700418 raise error.PayloadError('%r present without %r%s.' %
419 (present, missing,
420 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800421
422 @staticmethod
423 def _Run(cmd, send_data=None):
424 """Runs a subprocess, returns its output.
425
426 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700427 cmd: Sequence of command-line argument for invoking the subprocess.
428 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800429
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800430 Returns:
431 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800432 """
433 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
434 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700435 try:
436 result = run_process.communicate(input=send_data)
437 finally:
438 exit_code = run_process.wait()
439
440 if exit_code:
441 raise RuntimeError('Subprocess %r failed with code %r.' %
442 (cmd, exit_code))
443
444 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800445
446 @staticmethod
447 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
448 """Verifies an actual hash against a signed one.
449
450 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700451 sig_data: The raw signature data.
452 pubkey_file_name: Public key used for verifying signature.
453 actual_hash: The actual hash digest.
454 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800455
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800456 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700457 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800458 """
459 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700460 raise error.PayloadError(
461 '%s: signature size (%d) not as expected (256).' %
462 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800463 signed_data, _ = PayloadChecker._Run(
464 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
465 send_data=sig_data)
466
Gilad Arnold5502b562013-03-08 13:22:31 -0800467 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700468 raise error.PayloadError('%s: unexpected signed data length (%d).' %
469 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800470
Gilad Arnold5502b562013-03-08 13:22:31 -0800471 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700472 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
473 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800474
Gilad Arnold5502b562013-03-08 13:22:31 -0800475 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800476 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700477 raise error.PayloadError(
478 '%s: signed hash (%s) different from actual (%s).' %
479 (sig_name, common.FormatSha256(signed_hash),
480 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800481
482 @staticmethod
483 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
484 block_name=None):
485 """Checks that a given length fits given block space.
486
487 This ensures that the number of blocks allocated is appropriate for the
488 length of the data residing in these blocks.
489
490 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700491 length: The actual length of the data.
492 num_blocks: The number of blocks allocated for it.
493 block_size: The size of each block in bytes.
494 length_name: Name of length (used for error reporting).
495 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800496
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800497 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700498 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800499 """
500 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700501 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700502 raise error.PayloadError(
503 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800504 (length_name, length, block_name or '', num_blocks, block_size))
505
506 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700507 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700508 raise error.PayloadError(
509 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800510 (length_name, length, block_name or '', num_blocks - 1, block_size))
511
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700512 def _CheckManifestMinorVersion(self, report):
513 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800514
515 Args:
516 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800517
518 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700519 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800520 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700521 self.minor_version = self._CheckOptionalField(self.payload.manifest,
522 'minor_version', report)
523 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
524 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800525 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700526 'Minor version %d not compatible with payload type %s.' %
527 (self.minor_version, self.payload_type))
528 elif self.minor_version is None:
529 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800530 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700531 raise error.PayloadError('Unsupported minor version: %d' %
532 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800533
Gilad Arnold382df5c2013-05-03 12:49:28 -0700534 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800535 """Checks the payload manifest.
536
537 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700538 report: A report object to add to.
539 rootfs_part_size: Size of the rootfs partition in bytes.
540 kernel_part_size: Size of the kernel partition in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800541
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800542 Returns:
543 A tuple consisting of the partition block size used during the update
544 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800545
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800546 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700547 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800548 """
549 manifest = self.payload.manifest
550 report.AddSection('manifest')
551
552 # Check: block_size must exist and match the expected value.
553 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
554 report, 'manifest')
555 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700556 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
557 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800558
559 # Check: signatures_offset <==> signatures_size.
560 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
561 report)
562 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
563 report)
564 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
565 'signatures_offset', 'signatures_size', 'manifest')
566
567 # Check: old_kernel_info <==> old_rootfs_info.
568 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
569 'old_kernel_info', report)
570 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
571 'old_rootfs_info', report)
572 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
573 'old_rootfs_info', 'manifest')
574 if oki_msg: # equivalently, ori_msg
575 # Assert/mark delta payload.
576 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700577 raise error.PayloadError(
578 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800579 self.payload_type = _TYPE_DELTA
580
581 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700582 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800583 oki_msg, 'size', oki_report, 'old_kernel_info')
584 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
585 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700586 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800587 ori_msg, 'size', ori_report, 'old_rootfs_info')
588 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
589 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700590
591 # Check: old_{kernel,rootfs} size must fit in respective partition.
592 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700593 raise error.PayloadError(
594 'Old kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700595 (self.old_kernel_fs_size, kernel_part_size))
596 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700597 raise error.PayloadError(
598 'Old rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700599 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800600 else:
601 # Assert/mark full payload.
602 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700603 raise error.PayloadError(
604 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800605 self.payload_type = _TYPE_FULL
606
607 # Check: new_kernel_info present; contains {size, hash}.
608 nki_msg, nki_report = self._CheckMandatorySubMsg(
609 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700610 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800611 nki_msg, 'size', nki_report, 'new_kernel_info')
612 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
613 convert=common.FormatSha256)
614
615 # Check: new_rootfs_info present; contains {size, hash}.
616 nri_msg, nri_report = self._CheckMandatorySubMsg(
617 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700618 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800619 nri_msg, 'size', nri_report, 'new_rootfs_info')
620 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
621 convert=common.FormatSha256)
622
Gilad Arnold382df5c2013-05-03 12:49:28 -0700623 # Check: new_{kernel,rootfs} size must fit in respective partition.
624 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700625 raise error.PayloadError(
626 'New kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700627 (self.new_kernel_fs_size, kernel_part_size))
628 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700629 raise error.PayloadError(
630 'New rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700631 (self.new_rootfs_fs_size, rootfs_part_size))
632
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800633 # Check: minor_version makes sense for the payload type. This check should
634 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700635 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800636
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800637 def _CheckLength(self, length, total_blocks, op_name, length_name):
638 """Checks whether a length matches the space designated in extents.
639
640 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700641 length: The total length of the data.
642 total_blocks: The total number of blocks in extents.
643 op_name: Operation name (for error reporting).
644 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800645
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800646 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700647 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800648 """
649 # Check: length is non-zero.
650 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700651 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800652
653 # Check that length matches number of blocks.
654 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
655 '%s: %s' % (op_name, length_name))
656
Gilad Arnold382df5c2013-05-03 12:49:28 -0700657 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800658 allow_pseudo=False, allow_signature=False):
659 """Checks a sequence of extents.
660
661 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700662 extents: The sequence of extents to check.
663 usable_size: The usable size of the partition to which the extents apply.
664 block_counters: Array of counters corresponding to the number of blocks.
665 name: The name of the extent block.
666 allow_pseudo: Whether or not pseudo block numbers are allowed.
667 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800668
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800669 Returns:
670 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800671
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800672 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700673 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800674 """
675 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800676 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700677 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800678 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
679 None, ex_name)
680 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
681 ex_name)
682 end_block = start_block + num_blocks
683
684 # Check: num_blocks > 0.
685 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700686 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800687
688 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700689 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700690 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700691 raise error.PayloadError(
692 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700693 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800694
695 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700696 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800697 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800698 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
699 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
700 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700701 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800702
703 total_num_blocks += num_blocks
704
705 return total_num_blocks
706
707 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassanif1d6cea2017-12-07 12:13:03 -0800708 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800709
710 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700711 op: The operation object from the manifest.
712 data_length: The length of the data blob associated with the operation.
713 total_dst_blocks: Total number of blocks in dst_extents.
714 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800715
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800716 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700717 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800718 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700719 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800720 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700721 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800722
Gilad Arnoldcb638912013-06-24 04:57:11 -0700723 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800724 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700725 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800726
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800727 if op.type == common.OpType.REPLACE:
728 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
729 self.block_size,
730 op_name + '.data_length', 'dst')
731 else:
732 # Check: data_length must be smaller than the alotted dst blocks.
733 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700734 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800735 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700736 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800737 (op_name, data_length, total_dst_blocks, self.block_size))
738
739 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
740 total_dst_blocks, op_name):
741 """Specific checks for MOVE operations.
742
743 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700744 op: The operation object from the manifest.
745 data_offset: The offset of a data blob for the operation.
746 total_src_blocks: Total number of blocks in src_extents.
747 total_dst_blocks: Total number of blocks in dst_extents.
748 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800749
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800750 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700751 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800752 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700753 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800754 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700755 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800756
Gilad Arnoldcb638912013-06-24 04:57:11 -0700757 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800758 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700759 raise error.PayloadError(
760 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800761 (op_name, total_src_blocks, total_dst_blocks))
762
Gilad Arnoldcb638912013-06-24 04:57:11 -0700763 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800764 i = 0
765 src_extent_iter = iter(op.src_extents)
766 dst_extent_iter = iter(op.dst_extents)
767 src_extent = dst_extent = None
768 src_idx = src_num = dst_idx = dst_num = 0
769 while i < total_src_blocks:
770 # Get the next source extent, if needed.
771 if not src_extent:
772 try:
773 src_extent = src_extent_iter.next()
774 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700775 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
776 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800777 src_idx = src_extent.start_block
778 src_num = src_extent.num_blocks
779
780 # Get the next dest extent, if needed.
781 if not dst_extent:
782 try:
783 dst_extent = dst_extent_iter.next()
784 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700785 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
786 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800787 dst_idx = dst_extent.start_block
788 dst_num = dst_extent.num_blocks
789
Allie Woodb065e132015-04-24 10:20:27 -0700790 # Check: start block is not 0. See crbug/480751; there are still versions
791 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
792 # so we need to fail payloads that try to MOVE to/from block 0.
793 if src_idx == 0 or dst_idx == 0:
794 raise error.PayloadError(
795 '%s: MOVE operation cannot have extent with start block 0' %
796 op_name)
797
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700798 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700799 raise error.PayloadError(
800 '%s: src/dst block number %d is the same (%d).' %
801 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800802
803 advance = min(src_num, dst_num)
804 i += advance
805
806 src_idx += advance
807 src_num -= advance
808 if src_num == 0:
809 src_extent = None
810
811 dst_idx += advance
812 dst_num -= advance
813 if dst_num == 0:
814 dst_extent = None
815
Gilad Arnold5502b562013-03-08 13:22:31 -0800816 # Make sure we've exhausted all src/dst extents.
817 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700818 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800819 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700820 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800821
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700822 def _CheckZeroOperation(self, op, op_name):
823 """Specific checks for ZERO operations.
824
825 Args:
826 op: The operation object from the manifest.
827 op_name: Operation name for error reporting.
828
829 Raises:
830 error.PayloadError if any check fails.
831 """
832 # Check: Does not contain src extents, data_length and data_offset.
833 if op.src_extents:
834 raise error.PayloadError('%s: contains src_extents.' % op_name)
835 if op.data_length:
836 raise error.PayloadError('%s: contains data_length.' % op_name)
837 if op.data_offset:
838 raise error.PayloadError('%s: contains data_offset.' % op_name)
839
Amin Hassaniefa62d92017-11-09 13:46:56 -0800840 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
841 """Specific checks for BSDIFF, SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
842 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800843
844 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800845 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700846 data_length: The length of the data blob associated with the operation.
847 total_dst_blocks: Total number of blocks in dst_extents.
848 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800849
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800850 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700851 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800852 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800853 # Check: data_{offset,length} present.
854 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700855 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800856
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800857 # Check: data_length is strictly smaller than the alotted dst blocks.
858 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700859 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800860 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700861 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800862 (op_name, data_length, total_dst_blocks, self.block_size,
863 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800864
Amin Hassaniefa62d92017-11-09 13:46:56 -0800865 # Check the existence of src_length and dst_length for legacy bsdiffs.
866 if (op.type == common.OpType.BSDIFF or
867 (op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3)):
868 if not op.HasField('src_length') or not op.HasField('dst_length'):
869 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
870 else:
871 if op.HasField('src_length') or op.HasField('dst_length'):
872 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
873
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800874 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
875 total_dst_blocks, op_name):
876 """Specific checks for SOURCE_COPY.
877
878 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800879 data_offset: The offset of a data blob for the operation.
880 total_src_blocks: Total number of blocks in src_extents.
881 total_dst_blocks: Total number of blocks in dst_extents.
882 op_name: Operation name for error reporting.
883
884 Raises:
885 error.PayloadError if any check fails.
886 """
887 # Check: No data_{offset,length}.
888 if data_offset is not None:
889 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
890
891 # Check: total_src_blocks == total_dst_blocks.
892 if total_src_blocks != total_dst_blocks:
893 raise error.PayloadError(
894 '%s: total src blocks (%d) != total dst blocks (%d).' %
895 (op_name, total_src_blocks, total_dst_blocks))
896
Sen Jiangd6122bb2015-12-11 10:27:04 -0800897 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800898 """Specific checks for SOURCE_* operations.
899
900 Args:
901 op: The operation object from the manifest.
902 total_src_blocks: Total number of blocks in src_extents.
903 op_name: Operation name for error reporting.
904
905 Raises:
906 error.PayloadError if any check fails.
907 """
908 # Check: total_src_blocks != 0.
909 if total_src_blocks == 0:
910 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
911
Sen Jiang92161a72016-06-28 16:09:38 -0700912 # Check: src_sha256_hash present in minor version >= 3.
913 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800914 raise error.PayloadError('%s: source hash missing.' % op_name)
915
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800916 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700917 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700918 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800919 """Checks a single update operation.
920
921 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700922 op: The operation object.
923 op_name: Operation name string for error reporting.
924 is_last: Whether this is the last operation in the sequence.
925 old_block_counters: Arrays of block read counters.
926 new_block_counters: Arrays of block write counters.
927 old_usable_size: The overall usable size for src data in bytes.
928 new_usable_size: The overall usable size for dst data in bytes.
929 prev_data_offset: Offset of last used data bytes.
930 allow_signature: Whether this may be a signature operation.
931 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800932
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800933 Returns:
934 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800935
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800936 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700937 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800938 """
939 # Check extents.
940 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700941 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800942 op_name + '.src_extents', allow_pseudo=True)
943 allow_signature_in_extents = (allow_signature and is_last and
944 op.type == common.OpType.REPLACE)
945 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700946 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700947 op_name + '.dst_extents',
948 allow_pseudo=(not self.check_dst_pseudo_extents),
949 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800950
951 # Check: data_offset present <==> data_length present.
952 data_offset = self._CheckOptionalField(op, 'data_offset', None)
953 data_length = self._CheckOptionalField(op, 'data_length', None)
954 self._CheckPresentIff(data_offset, data_length, 'data_offset',
955 'data_length', op_name)
956
Gilad Arnoldcb638912013-06-24 04:57:11 -0700957 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800958 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700959 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800960
961 # Check {src,dst}_length, if present.
962 if op.HasField('src_length'):
963 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
964 if op.HasField('dst_length'):
965 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
966
967 if op.HasField('data_sha256_hash'):
968 blob_hash_counts['hashed'] += 1
969
Gilad Arnoldcb638912013-06-24 04:57:11 -0700970 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800971 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700972 raise error.PayloadError(
973 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800974 op_name)
975
Gilad Arnoldcb638912013-06-24 04:57:11 -0700976 # Check: Hash verifies correctly.
977 # pylint cannot find the method in hashlib, for some reason.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800978 # pylint: disable=E1101
979 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
980 data_length))
981 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700982 raise error.PayloadError(
983 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700984 (op_name, common.FormatSha256(op.data_sha256_hash),
985 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800986 elif data_offset is not None:
987 if allow_signature_in_extents:
988 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700989 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800990 blob_hash_counts['unhashed'] += 1
991 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700992 raise error.PayloadError('%s: unhashed operation not allowed.' %
993 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800994
995 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700996 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800997 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700998 raise error.PayloadError(
999 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001000 (op_name, data_offset, prev_data_offset))
1001
1002 # Type-specific checks.
1003 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
1004 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassanif1d6cea2017-12-07 12:13:03 -08001005 elif op.type == common.OpType.REPLACE_XZ and (self.minor_version >= 3 or
1006 self.major_version >= 2):
1007 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001008 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001009 self._CheckMoveOperation(op, data_offset, total_src_blocks,
1010 total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001011 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
1012 self._CheckZeroOperation(op, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001013 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001014 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001015 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001016 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
1017 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001018 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001019 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001020 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001021 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassaniefa62d92017-11-09 13:46:56 -08001022 elif (op.type in (common.OpType.PUFFDIFF, common.OpType.BROTLI_BSDIFF) and
1023 self.minor_version >= 4):
1024 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001025 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001026 else:
Allie Wood7cf9f132015-02-26 14:28:19 -08001027 raise error.PayloadError(
1028 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001029 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001030 return data_length if data_length is not None else 0
1031
Gilad Arnold382df5c2013-05-03 12:49:28 -07001032 def _SizeToNumBlocks(self, size):
1033 """Returns the number of blocks needed to contain a given byte size."""
1034 return (size + self.block_size - 1) / self.block_size
1035
1036 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001037 """Returns a freshly initialized array of block counters.
1038
Gilad Arnoldcb638912013-06-24 04:57:11 -07001039 Note that the generated array is not portable as is due to byte-ordering
1040 issues, hence it should not be serialized.
1041
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001042 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001043 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001044
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001045 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001046 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001047 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001048 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001049 return array.array('H',
1050 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001051
Gilad Arnold382df5c2013-05-03 12:49:28 -07001052 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001053 new_fs_size, old_usable_size, new_usable_size,
1054 prev_data_offset, allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001055 """Checks a sequence of update operations.
1056
1057 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001058 operations: The sequence of operations to check.
1059 report: The report object to add to.
1060 base_name: The name of the operation block.
1061 old_fs_size: The old filesystem size in bytes.
1062 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001063 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001064 new_usable_size: The overall usable size of the new partition in bytes.
1065 prev_data_offset: Offset of last used data bytes.
1066 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001067
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001068 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001069 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001070
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001071 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001072 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001073 """
1074 # The total size of data blobs used by operations scanned thus far.
1075 total_data_used = 0
1076 # Counts of specific operation types.
1077 op_counts = {
1078 common.OpType.REPLACE: 0,
1079 common.OpType.REPLACE_BZ: 0,
Amin Hassanif1d6cea2017-12-07 12:13:03 -08001080 common.OpType.REPLACE_XZ: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001081 common.OpType.MOVE: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001082 common.OpType.ZERO: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001083 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001084 common.OpType.SOURCE_COPY: 0,
1085 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001086 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001087 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001088 }
1089 # Total blob sizes for each operation type.
1090 op_blob_totals = {
1091 common.OpType.REPLACE: 0,
1092 common.OpType.REPLACE_BZ: 0,
Amin Hassanif1d6cea2017-12-07 12:13:03 -08001093 common.OpType.REPLACE_XZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001094 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001095 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001096 # SOURCE_COPY operations don't have blobs.
1097 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001098 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001099 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001100 }
1101 # Counts of hashed vs unhashed operations.
1102 blob_hash_counts = {
1103 'hashed': 0,
1104 'unhashed': 0,
1105 }
1106 if allow_signature:
1107 blob_hash_counts['signature'] = 0
1108
1109 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001110 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001111 if old_fs_size else None)
1112 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001113
1114 # Process and verify each operation.
1115 op_num = 0
1116 for op, op_name in common.OperationIter(operations, base_name):
1117 op_num += 1
1118
Gilad Arnoldcb638912013-06-24 04:57:11 -07001119 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001120 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001121 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001122 op_counts[op.type] += 1
1123
1124 is_last = op_num == len(operations)
1125 curr_data_used = self._CheckOperation(
1126 op, op_name, is_last, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001127 old_usable_size, new_usable_size,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001128 prev_data_offset + total_data_used, allow_signature,
1129 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001130 if curr_data_used:
1131 op_blob_totals[op.type] += curr_data_used
1132 total_data_used += curr_data_used
1133
1134 # Report totals and breakdown statistics.
1135 report.AddField('total operations', op_num)
1136 report.AddField(
1137 None,
1138 histogram.Histogram.FromCountDict(op_counts,
1139 key_names=common.OpType.NAMES),
1140 indent=1)
1141 report.AddField('total blobs', sum(blob_hash_counts.values()))
1142 report.AddField(None,
1143 histogram.Histogram.FromCountDict(blob_hash_counts),
1144 indent=1)
1145 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1146 report.AddField(
1147 None,
1148 histogram.Histogram.FromCountDict(op_blob_totals,
1149 formatter=_AddHumanReadableSize,
1150 key_names=common.OpType.NAMES),
1151 indent=1)
1152
1153 # Report read/write histograms.
1154 if old_block_counters:
1155 report.AddField('block read hist',
1156 histogram.Histogram.FromKeyList(old_block_counters),
1157 linebreak=True, indent=1)
1158
Gilad Arnold382df5c2013-05-03 12:49:28 -07001159 new_write_hist = histogram.Histogram.FromKeyList(
1160 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1161 report.AddField('block write hist', new_write_hist, linebreak=True,
1162 indent=1)
1163
Gilad Arnoldcb638912013-06-24 04:57:11 -07001164 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001165 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001166 raise error.PayloadError(
1167 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001168 base_name)
1169
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001170 return total_data_used
1171
1172 def _CheckSignatures(self, report, pubkey_file_name):
1173 """Checks a payload's signature block."""
1174 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1175 sigs = update_metadata_pb2.Signatures()
1176 sigs.ParseFromString(sigs_raw)
1177 report.AddSection('signatures')
1178
Gilad Arnoldcb638912013-06-24 04:57:11 -07001179 # Check: At least one signature present.
1180 # pylint cannot see through the protobuf object, it seems.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001181 # pylint: disable=E1101
1182 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001183 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001184
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001185 last_ops_section = (self.payload.manifest.kernel_install_operations or
1186 self.payload.manifest.install_operations)
1187 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001188 # Check: signatures_{offset,size} must match the last (fake) operation.
1189 if not (fake_sig_op.type == common.OpType.REPLACE and
1190 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001191 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001192 raise error.PayloadError(
1193 'Signatures_{offset,size} (%d+%d) does not match last operation '
1194 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001195 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1196 fake_sig_op.data_length))
1197
1198 # Compute the checksum of all data up to signature blob.
1199 # TODO(garnold) we're re-reading the whole data section into a string
1200 # just to compute the checksum; instead, we could do it incrementally as
1201 # we read the blobs one-by-one, under the assumption that we're reading
1202 # them in order (which currently holds). This should be reconsidered.
1203 payload_hasher = self.payload.manifest_hasher.copy()
1204 common.Read(self.payload.payload_file, self.sigs_offset,
1205 offset=self.payload.data_offset, hasher=payload_hasher)
1206
1207 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1208 sig_report = report.AddSubReport(sig_name)
1209
Gilad Arnoldcb638912013-06-24 04:57:11 -07001210 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001211 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1212 self._CheckMandatoryField(sig, 'data', None, sig_name)
1213 sig_report.AddField('data len', len(sig.data))
1214
Gilad Arnoldcb638912013-06-24 04:57:11 -07001215 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001216 if sig.version == 1:
1217 self._CheckSha256Signature(sig.data, pubkey_file_name,
1218 payload_hasher.digest(), sig_name)
1219 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001220 raise error.PayloadError('Unknown signature version (%d).' %
1221 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001222
1223 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001224 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001225 """Checker entry point, invoking all checks.
1226
1227 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001228 pubkey_file_name: Public key used for signature verification.
1229 metadata_sig_file: Metadata signature, if verification is desired.
Gilad Arnold06eea332015-07-13 18:06:33 -07001230 rootfs_part_size: The size of rootfs partitions in bytes (default: infer
1231 based on payload type and version).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001232 kernel_part_size: The size of kernel partitions in bytes (default: use
1233 reported filesystem size).
1234 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001235
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001236 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001237 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001238 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001239 if not pubkey_file_name:
1240 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1241
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001242 report = _PayloadReport()
1243
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001244 # Get payload file size.
1245 self.payload.payload_file.seek(0, 2)
1246 payload_file_size = self.payload.payload_file.tell()
1247 self.payload.ResetFile()
1248
1249 try:
1250 # Check metadata signature (if provided).
1251 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001252 metadata_sig = base64.b64decode(metadata_sig_file.read())
1253 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1254 self.payload.manifest_hasher.digest(),
1255 'metadata signature')
1256
Gilad Arnoldcb638912013-06-24 04:57:11 -07001257 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001258 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001259 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001260 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001261 raise error.PayloadError('Unknown payload version (%d).' %
1262 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001263 report.AddField('version', self.payload.header.version)
1264 report.AddField('manifest len', self.payload.header.manifest_len)
1265
Gilad Arnoldcb638912013-06-24 04:57:11 -07001266 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001267 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001268 assert self.payload_type, 'payload type should be known by now'
1269
Gilad Arnold06eea332015-07-13 18:06:33 -07001270 # Infer the usable partition size when validating rootfs operations:
1271 # - If rootfs partition size was provided, use that.
1272 # - Otherwise, if this is an older delta (minor version < 2), stick with
1273 # a known constant size. This is necessary because older deltas may
1274 # exceed the filesystem size when moving data blocks around.
1275 # - Otherwise, use the encoded filesystem size.
1276 new_rootfs_usable_size = self.new_rootfs_fs_size
Amin Hassaniae853742017-10-11 10:27:27 -07001277 old_rootfs_usable_size = self.old_rootfs_fs_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001278 if rootfs_part_size:
1279 new_rootfs_usable_size = rootfs_part_size
Amin Hassaniae853742017-10-11 10:27:27 -07001280 old_rootfs_usable_size = rootfs_part_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001281 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
1282 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Amin Hassaniae853742017-10-11 10:27:27 -07001283 old_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Gilad Arnold06eea332015-07-13 18:06:33 -07001284
Gilad Arnoldcb638912013-06-24 04:57:11 -07001285 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001286 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1287 # no explicit size provided *and* the partition size is not embedded in
1288 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001289 report.AddSection('rootfs operations')
1290 total_blob_size = self._CheckOperations(
1291 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001292 'install_operations', self.old_rootfs_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001293 self.new_rootfs_fs_size, old_rootfs_usable_size,
1294 new_rootfs_usable_size, 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001295
Gilad Arnoldcb638912013-06-24 04:57:11 -07001296 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001297 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001298 report.AddSection('kernel operations')
1299 total_blob_size += self._CheckOperations(
1300 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001301 'kernel_install_operations', self.old_kernel_fs_size,
1302 self.new_kernel_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001303 kernel_part_size if kernel_part_size else self.old_kernel_fs_size,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001304 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1305 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001306
Gilad Arnoldcb638912013-06-24 04:57:11 -07001307 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001308 used_payload_size = self.payload.data_offset + total_blob_size
1309 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001310 raise error.PayloadError(
1311 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001312 (used_payload_size, payload_file_size))
1313
Gilad Arnoldcb638912013-06-24 04:57:11 -07001314 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001315 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001316 self._CheckSignatures(report, pubkey_file_name)
1317
Gilad Arnoldcb638912013-06-24 04:57:11 -07001318 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001319 report.AddSection('summary')
1320 report.AddField('update type', self.payload_type)
1321
1322 report.Finalize()
1323 finally:
1324 if report_out_file:
1325 report.Dump(report_out_file)