blob: 21d99a0ec2def0d2020135eecfc302c091cc2d47 [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
Amin Hassanib05a65a2017-12-18 15:15:32 -080024from update_payload import common
25from update_payload import error
26from update_payload import format_utils
27from update_payload import histogram
28from update_payload import update_metadata_pb2
Gilad Arnold553b0ec2013-01-26 01:00:39 -080029
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,),
Amin Hassani90c57d72018-02-07 16:21:33 -080060 5: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070061}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080062
Gilad Arnold06eea332015-07-13 18:06:33 -070063_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
64
Gilad Arnold553b0ec2013-01-26 01:00:39 -080065#
66# Helper functions.
67#
Gilad Arnoldcb638912013-06-24 04:57:11 -070068
Gilad Arnold553b0ec2013-01-26 01:00:39 -080069def _IsPowerOfTwo(val):
70 """Returns True iff val is a power of two."""
71 return val > 0 and (val & (val - 1)) == 0
72
73
74def _AddFormat(format_func, value):
75 """Adds a custom formatted representation to ordinary string representation.
76
77 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070078 format_func: A value formatter.
79 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080080
Gilad Arnold553b0ec2013-01-26 01:00:39 -080081 Returns:
82 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080083 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070084 ret = str(value)
85 formatted_str = format_func(value)
86 if formatted_str:
87 ret += ' (%s)' % formatted_str
88 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -080089
90
91def _AddHumanReadableSize(size):
92 """Adds a human readable representation to a byte size value."""
93 return _AddFormat(format_utils.BytesToHumanReadable, size)
94
95
96#
97# Payload report generator.
98#
Gilad Arnoldcb638912013-06-24 04:57:11 -070099
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800100class _PayloadReport(object):
101 """A payload report generator.
102
103 A report is essentially a sequence of nodes, which represent data points. It
104 is initialized to have a "global", untitled section. A node may be a
105 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800106 """
107
Gilad Arnoldcb638912013-06-24 04:57:11 -0700108 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800109 class Node(object):
110 """A report node interface."""
111
112 @staticmethod
113 def _Indent(indent, line):
114 """Indents a line by a given indentation amount.
115
116 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700117 indent: The indentation amount.
118 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800119
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800120 Returns:
121 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800122 """
123 return '%*s%s' % (indent, '', line)
124
125 def GenerateLines(self, base_indent, sub_indent, curr_section):
126 """Generates the report lines for this node.
127
128 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700129 base_indent: Base indentation for each line.
130 sub_indent: Additional indentation for sub-nodes.
131 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800132
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800133 Returns:
134 A pair consisting of a list of properly indented report lines and a new
135 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800136 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700137 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800138
139 class FieldNode(Node):
140 """A field report node, representing a (name, value) pair."""
141
142 def __init__(self, name, value, linebreak, indent):
143 super(_PayloadReport.FieldNode, self).__init__()
144 self.name = name
145 self.value = value
146 self.linebreak = linebreak
147 self.indent = indent
148
149 def GenerateLines(self, base_indent, sub_indent, curr_section):
150 """Generates a properly formatted 'name : value' entry."""
151 report_output = ''
152 if self.name:
153 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
154 value_lines = str(self.value).splitlines()
155 if self.linebreak and self.name:
156 report_output += '\n' + '\n'.join(
157 ['%*s%s' % (self.indent, '', line) for line in value_lines])
158 else:
159 if self.name:
160 report_output += ' '
161 report_output += '%*s' % (self.indent, '')
162 cont_line_indent = len(report_output)
163 indented_value_lines = [value_lines[0]]
164 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
165 for line in value_lines[1:]])
166 report_output += '\n'.join(indented_value_lines)
167
168 report_lines = [self._Indent(base_indent, line + '\n')
169 for line in report_output.split('\n')]
170 return report_lines, curr_section
171
172 class SubReportNode(Node):
173 """A sub-report node, representing a nested report."""
174
175 def __init__(self, title, report):
176 super(_PayloadReport.SubReportNode, self).__init__()
177 self.title = title
178 self.report = report
179
180 def GenerateLines(self, base_indent, sub_indent, curr_section):
181 """Recurse with indentation."""
182 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
183 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
184 sub_indent))
185 return report_lines, curr_section
186
187 class SectionNode(Node):
188 """A section header node."""
189
190 def __init__(self, title=None):
191 super(_PayloadReport.SectionNode, self).__init__()
192 self.title = title
193 self.max_field_name_len = 0
194
195 def GenerateLines(self, base_indent, sub_indent, curr_section):
196 """Dump a title line, return self as the (new) current section."""
197 report_lines = []
198 if self.title:
199 report_lines.append(self._Indent(base_indent,
200 '=== %s ===\n' % self.title))
201 return report_lines, self
202
203 def __init__(self):
204 self.report = []
205 self.last_section = self.global_section = self.SectionNode()
206 self.is_finalized = False
207
208 def GenerateLines(self, base_indent, sub_indent):
209 """Generates the lines in the report, properly indented.
210
211 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700212 base_indent: The indentation used for root-level report lines.
213 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800214
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800215 Returns:
216 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800217 """
218 report_lines = []
219 curr_section = self.global_section
220 for node in self.report:
221 node_report_lines, curr_section = node.GenerateLines(
222 base_indent, sub_indent, curr_section)
223 report_lines.extend(node_report_lines)
224
225 return report_lines
226
227 def Dump(self, out_file, base_indent=0, sub_indent=2):
228 """Dumps the report to a file.
229
230 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700231 out_file: File object to output the content to.
232 base_indent: Base indentation for report lines.
233 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800234 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800235 report_lines = self.GenerateLines(base_indent, sub_indent)
236 if report_lines and not self.is_finalized:
237 report_lines.append('(incomplete report)\n')
238
239 for line in report_lines:
240 out_file.write(line)
241
242 def AddField(self, name, value, linebreak=False, indent=0):
243 """Adds a field/value pair to the payload report.
244
245 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700246 name: The field's name.
247 value: The field's value.
248 linebreak: Whether the value should be printed on a new line.
249 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800250 """
251 assert not self.is_finalized
252 if name and self.last_section.max_field_name_len < len(name):
253 self.last_section.max_field_name_len = len(name)
254 self.report.append(self.FieldNode(name, value, linebreak, indent))
255
256 def AddSubReport(self, title):
257 """Adds and returns a sub-report with a title."""
258 assert not self.is_finalized
259 sub_report = self.SubReportNode(title, type(self)())
260 self.report.append(sub_report)
261 return sub_report.report
262
263 def AddSection(self, title):
264 """Adds a new section title."""
265 assert not self.is_finalized
266 self.last_section = self.SectionNode(title)
267 self.report.append(self.last_section)
268
269 def Finalize(self):
270 """Seals the report, marking it as complete."""
271 self.is_finalized = True
272
273
274#
275# Payload verification.
276#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700277
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800278class PayloadChecker(object):
279 """Checking the integrity of an update payload.
280
281 This is a short-lived object whose purpose is to isolate the logic used for
282 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800283 """
284
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700285 def __init__(self, payload, assert_type=None, block_size=0,
286 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700287 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700288
289 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700290 payload: The payload object to check.
291 assert_type: Assert that payload is either 'full' or 'delta' (optional).
292 block_size: Expected filesystem / payload block size (optional).
293 allow_unhashed: Allow operations with unhashed data blobs.
294 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700295 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700296 if not payload.is_init:
297 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700298
299 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800300 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700301 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
302 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700303 raise error.PayloadError(
304 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700305 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700306 raise error.PayloadError('Invalid assert_type value (%r).' %
307 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700308 self.payload_type = assert_type
309 self.allow_unhashed = allow_unhashed
310
311 # Disable specific tests.
312 self.check_dst_pseudo_extents = (
313 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
314 self.check_move_same_src_dst_block = (
315 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
316 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800317
318 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800319 self.sigs_offset = 0
320 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700321 self.old_rootfs_fs_size = 0
322 self.old_kernel_fs_size = 0
323 self.new_rootfs_fs_size = 0
324 self.new_kernel_fs_size = 0
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700325 self.minor_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800326
327 @staticmethod
328 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
329 msg_name=None, linebreak=False, indent=0):
330 """Adds an element from a protobuf message to the payload report.
331
332 Checks to see whether a message contains a given element, and if so adds
333 the element value to the provided report. A missing mandatory element
334 causes an exception to be raised.
335
336 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700337 msg: The message containing the element.
338 name: The name of the element.
339 report: A report object to add the element name/value to.
340 is_mandatory: Whether or not this element must be present.
341 is_submsg: Whether this element is itself a message.
342 convert: A function for converting the element value for reporting.
343 msg_name: The name of the message object (for error reporting).
344 linebreak: Whether the value report should induce a line break.
345 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800346
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800347 Returns:
348 A pair consisting of the element value and the generated sub-report for
349 it (if the element is a sub-message, None otherwise). If the element is
350 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800351
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800352 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700353 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800354 """
355 if not msg.HasField(name):
356 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700357 raise error.PayloadError('%smissing mandatory %s %r.' %
358 (msg_name + ' ' if msg_name else '',
359 'sub-message' if is_submsg else 'field',
360 name))
361 return None, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800362
363 value = getattr(msg, name)
364 if is_submsg:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700365 return value, report and report.AddSubReport(name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800366 else:
367 if report:
368 report.AddField(name, convert(value), linebreak=linebreak,
369 indent=indent)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700370 return value, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800371
372 @staticmethod
373 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
374 linebreak=False, indent=0):
375 """Adds a mandatory field; returning first component from _CheckElem."""
376 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
377 convert=convert, msg_name=msg_name,
378 linebreak=linebreak, indent=indent)[0]
379
380 @staticmethod
381 def _CheckOptionalField(msg, field_name, report, convert=str,
382 linebreak=False, indent=0):
383 """Adds an optional field; returning first component from _CheckElem."""
384 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
385 convert=convert, linebreak=linebreak,
386 indent=indent)[0]
387
388 @staticmethod
389 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
390 """Adds a mandatory sub-message; wrapper for _CheckElem."""
391 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
392 msg_name)
393
394 @staticmethod
395 def _CheckOptionalSubMsg(msg, submsg_name, report):
396 """Adds an optional sub-message; wrapper for _CheckElem."""
397 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
398
399 @staticmethod
400 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
401 """Checks that val1 is None iff val2 is None.
402
403 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700404 val1: first value to be compared.
405 val2: second value to be compared.
406 name1: name of object holding the first value.
407 name2: name of object holding the second value.
408 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800409
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800410 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700411 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800412 """
413 if None in (val1, val2) and val1 is not val2:
414 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700415 raise error.PayloadError('%r present without %r%s.' %
416 (present, missing,
417 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800418
419 @staticmethod
420 def _Run(cmd, send_data=None):
421 """Runs a subprocess, returns its output.
422
423 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700424 cmd: Sequence of command-line argument for invoking the subprocess.
425 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800426
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800427 Returns:
428 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800429 """
430 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
431 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700432 try:
433 result = run_process.communicate(input=send_data)
434 finally:
435 exit_code = run_process.wait()
436
437 if exit_code:
438 raise RuntimeError('Subprocess %r failed with code %r.' %
439 (cmd, exit_code))
440
441 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800442
443 @staticmethod
444 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
445 """Verifies an actual hash against a signed one.
446
447 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700448 sig_data: The raw signature data.
449 pubkey_file_name: Public key used for verifying signature.
450 actual_hash: The actual hash digest.
451 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800452
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800453 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700454 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800455 """
456 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700457 raise error.PayloadError(
458 '%s: signature size (%d) not as expected (256).' %
459 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800460 signed_data, _ = PayloadChecker._Run(
461 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
462 send_data=sig_data)
463
Gilad Arnold5502b562013-03-08 13:22:31 -0800464 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700465 raise error.PayloadError('%s: unexpected signed data length (%d).' %
466 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800467
Gilad Arnold5502b562013-03-08 13:22:31 -0800468 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700469 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
470 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800471
Gilad Arnold5502b562013-03-08 13:22:31 -0800472 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800473 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700474 raise error.PayloadError(
475 '%s: signed hash (%s) different from actual (%s).' %
476 (sig_name, common.FormatSha256(signed_hash),
477 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800478
479 @staticmethod
480 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
481 block_name=None):
482 """Checks that a given length fits given block space.
483
484 This ensures that the number of blocks allocated is appropriate for the
485 length of the data residing in these blocks.
486
487 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700488 length: The actual length of the data.
489 num_blocks: The number of blocks allocated for it.
490 block_size: The size of each block in bytes.
491 length_name: Name of length (used for error reporting).
492 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800493
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800494 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700495 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800496 """
497 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700498 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700499 raise error.PayloadError(
500 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800501 (length_name, length, block_name or '', num_blocks, block_size))
502
503 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700504 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700505 raise error.PayloadError(
506 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800507 (length_name, length, block_name or '', num_blocks - 1, block_size))
508
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700509 def _CheckManifestMinorVersion(self, report):
510 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800511
512 Args:
513 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800514
515 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700516 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800517 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700518 self.minor_version = self._CheckOptionalField(self.payload.manifest,
519 'minor_version', report)
520 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
521 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800522 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700523 'Minor version %d not compatible with payload type %s.' %
524 (self.minor_version, self.payload_type))
525 elif self.minor_version is None:
526 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800527 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700528 raise error.PayloadError('Unsupported minor version: %d' %
529 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800530
Gilad Arnold382df5c2013-05-03 12:49:28 -0700531 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800532 """Checks the payload manifest.
533
534 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700535 report: A report object to add to.
536 rootfs_part_size: Size of the rootfs partition in bytes.
537 kernel_part_size: Size of the kernel partition in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800538
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800539 Returns:
540 A tuple consisting of the partition block size used during the update
541 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800542
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800543 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700544 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800545 """
546 manifest = self.payload.manifest
547 report.AddSection('manifest')
548
549 # Check: block_size must exist and match the expected value.
550 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
551 report, 'manifest')
552 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700553 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
554 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800555
556 # Check: signatures_offset <==> signatures_size.
557 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
558 report)
559 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
560 report)
561 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
562 'signatures_offset', 'signatures_size', 'manifest')
563
564 # Check: old_kernel_info <==> old_rootfs_info.
565 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
566 'old_kernel_info', report)
567 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
568 'old_rootfs_info', report)
569 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
570 'old_rootfs_info', 'manifest')
571 if oki_msg: # equivalently, ori_msg
572 # Assert/mark delta payload.
573 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700574 raise error.PayloadError(
575 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800576 self.payload_type = _TYPE_DELTA
577
578 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700579 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800580 oki_msg, 'size', oki_report, 'old_kernel_info')
581 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
582 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700583 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800584 ori_msg, 'size', ori_report, 'old_rootfs_info')
585 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
586 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700587
588 # Check: old_{kernel,rootfs} size must fit in respective partition.
589 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700590 raise error.PayloadError(
591 'Old kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700592 (self.old_kernel_fs_size, kernel_part_size))
593 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700594 raise error.PayloadError(
595 'Old rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700596 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800597 else:
598 # Assert/mark full payload.
599 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700600 raise error.PayloadError(
601 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800602 self.payload_type = _TYPE_FULL
603
604 # Check: new_kernel_info present; contains {size, hash}.
605 nki_msg, nki_report = self._CheckMandatorySubMsg(
606 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700607 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800608 nki_msg, 'size', nki_report, 'new_kernel_info')
609 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
610 convert=common.FormatSha256)
611
612 # Check: new_rootfs_info present; contains {size, hash}.
613 nri_msg, nri_report = self._CheckMandatorySubMsg(
614 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700615 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800616 nri_msg, 'size', nri_report, 'new_rootfs_info')
617 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
618 convert=common.FormatSha256)
619
Gilad Arnold382df5c2013-05-03 12:49:28 -0700620 # Check: new_{kernel,rootfs} size must fit in respective partition.
621 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700622 raise error.PayloadError(
623 'New kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700624 (self.new_kernel_fs_size, kernel_part_size))
625 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700626 raise error.PayloadError(
627 'New rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700628 (self.new_rootfs_fs_size, rootfs_part_size))
629
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800630 # Check: minor_version makes sense for the payload type. This check should
631 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700632 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800633
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800634 def _CheckLength(self, length, total_blocks, op_name, length_name):
635 """Checks whether a length matches the space designated in extents.
636
637 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700638 length: The total length of the data.
639 total_blocks: The total number of blocks in extents.
640 op_name: Operation name (for error reporting).
641 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800642
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800643 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700644 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800645 """
646 # Check: length is non-zero.
647 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700648 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800649
650 # Check that length matches number of blocks.
651 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
652 '%s: %s' % (op_name, length_name))
653
Gilad Arnold382df5c2013-05-03 12:49:28 -0700654 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800655 allow_pseudo=False, allow_signature=False):
656 """Checks a sequence of extents.
657
658 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700659 extents: The sequence of extents to check.
660 usable_size: The usable size of the partition to which the extents apply.
661 block_counters: Array of counters corresponding to the number of blocks.
662 name: The name of the extent block.
663 allow_pseudo: Whether or not pseudo block numbers are allowed.
664 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800665
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800666 Returns:
667 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800668
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800669 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700670 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800671 """
672 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800673 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700674 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800675 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
676 None, ex_name)
677 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
678 ex_name)
679 end_block = start_block + num_blocks
680
681 # Check: num_blocks > 0.
682 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700683 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800684
685 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700686 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700687 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700688 raise error.PayloadError(
689 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700690 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800691
692 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700693 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800694 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800695 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
696 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
697 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700698 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800699
700 total_num_blocks += num_blocks
701
702 return total_num_blocks
703
704 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassanib44f73b2017-12-15 17:45:49 +0000705 """Specific checks for REPLACE/REPLACE_BZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800706
707 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700708 op: The operation object from the manifest.
709 data_length: The length of the data blob associated with the operation.
710 total_dst_blocks: Total number of blocks in dst_extents.
711 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800712
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800713 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700714 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800715 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700716 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800717 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700718 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800719
Gilad Arnoldcb638912013-06-24 04:57:11 -0700720 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800721 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700722 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800723
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800724 if op.type == common.OpType.REPLACE:
725 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
726 self.block_size,
727 op_name + '.data_length', 'dst')
728 else:
729 # Check: data_length must be smaller than the alotted dst blocks.
730 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700731 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800732 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700733 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800734 (op_name, data_length, total_dst_blocks, self.block_size))
735
736 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
737 total_dst_blocks, op_name):
738 """Specific checks for MOVE operations.
739
740 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700741 op: The operation object from the manifest.
742 data_offset: The offset of a data blob for the operation.
743 total_src_blocks: Total number of blocks in src_extents.
744 total_dst_blocks: Total number of blocks in dst_extents.
745 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800746
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800747 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700748 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800749 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700750 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800751 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700752 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800753
Gilad Arnoldcb638912013-06-24 04:57:11 -0700754 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800755 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700756 raise error.PayloadError(
757 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800758 (op_name, total_src_blocks, total_dst_blocks))
759
Gilad Arnoldcb638912013-06-24 04:57:11 -0700760 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800761 i = 0
762 src_extent_iter = iter(op.src_extents)
763 dst_extent_iter = iter(op.dst_extents)
764 src_extent = dst_extent = None
765 src_idx = src_num = dst_idx = dst_num = 0
766 while i < total_src_blocks:
767 # Get the next source extent, if needed.
768 if not src_extent:
769 try:
770 src_extent = src_extent_iter.next()
771 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700772 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
773 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800774 src_idx = src_extent.start_block
775 src_num = src_extent.num_blocks
776
777 # Get the next dest extent, if needed.
778 if not dst_extent:
779 try:
780 dst_extent = dst_extent_iter.next()
781 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700782 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
783 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800784 dst_idx = dst_extent.start_block
785 dst_num = dst_extent.num_blocks
786
Allie Woodb065e132015-04-24 10:20:27 -0700787 # Check: start block is not 0. See crbug/480751; there are still versions
788 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
789 # so we need to fail payloads that try to MOVE to/from block 0.
790 if src_idx == 0 or dst_idx == 0:
791 raise error.PayloadError(
792 '%s: MOVE operation cannot have extent with start block 0' %
793 op_name)
794
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700795 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700796 raise error.PayloadError(
797 '%s: src/dst block number %d is the same (%d).' %
798 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800799
800 advance = min(src_num, dst_num)
801 i += advance
802
803 src_idx += advance
804 src_num -= advance
805 if src_num == 0:
806 src_extent = None
807
808 dst_idx += advance
809 dst_num -= advance
810 if dst_num == 0:
811 dst_extent = None
812
Gilad Arnold5502b562013-03-08 13:22:31 -0800813 # Make sure we've exhausted all src/dst extents.
814 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700815 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800816 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700817 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800818
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700819 def _CheckZeroOperation(self, op, op_name):
820 """Specific checks for ZERO operations.
821
822 Args:
823 op: The operation object from the manifest.
824 op_name: Operation name for error reporting.
825
826 Raises:
827 error.PayloadError if any check fails.
828 """
829 # Check: Does not contain src extents, data_length and data_offset.
830 if op.src_extents:
831 raise error.PayloadError('%s: contains src_extents.' % op_name)
832 if op.data_length:
833 raise error.PayloadError('%s: contains data_length.' % op_name)
834 if op.data_offset:
835 raise error.PayloadError('%s: contains data_offset.' % op_name)
836
Amin Hassaniefa62d92017-11-09 13:46:56 -0800837 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
838 """Specific checks for BSDIFF, SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
839 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800840
841 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800842 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700843 data_length: The length of the data blob associated with the operation.
844 total_dst_blocks: Total number of blocks in dst_extents.
845 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800846
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800847 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700848 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800849 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800850 # Check: data_{offset,length} present.
851 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700852 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800853
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800854 # Check: data_length is strictly smaller than the alotted dst blocks.
855 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700856 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800857 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700858 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800859 (op_name, data_length, total_dst_blocks, self.block_size,
860 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800861
Amin Hassaniefa62d92017-11-09 13:46:56 -0800862 # Check the existence of src_length and dst_length for legacy bsdiffs.
863 if (op.type == common.OpType.BSDIFF or
864 (op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3)):
865 if not op.HasField('src_length') or not op.HasField('dst_length'):
866 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
867 else:
868 if op.HasField('src_length') or op.HasField('dst_length'):
869 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
870
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800871 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
872 total_dst_blocks, op_name):
873 """Specific checks for SOURCE_COPY.
874
875 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800876 data_offset: The offset of a data blob for the operation.
877 total_src_blocks: Total number of blocks in src_extents.
878 total_dst_blocks: Total number of blocks in dst_extents.
879 op_name: Operation name for error reporting.
880
881 Raises:
882 error.PayloadError if any check fails.
883 """
884 # Check: No data_{offset,length}.
885 if data_offset is not None:
886 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
887
888 # Check: total_src_blocks == total_dst_blocks.
889 if total_src_blocks != total_dst_blocks:
890 raise error.PayloadError(
891 '%s: total src blocks (%d) != total dst blocks (%d).' %
892 (op_name, total_src_blocks, total_dst_blocks))
893
Sen Jiangd6122bb2015-12-11 10:27:04 -0800894 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800895 """Specific checks for SOURCE_* operations.
896
897 Args:
898 op: The operation object from the manifest.
899 total_src_blocks: Total number of blocks in src_extents.
900 op_name: Operation name for error reporting.
901
902 Raises:
903 error.PayloadError if any check fails.
904 """
905 # Check: total_src_blocks != 0.
906 if total_src_blocks == 0:
907 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
908
Sen Jiang92161a72016-06-28 16:09:38 -0700909 # Check: src_sha256_hash present in minor version >= 3.
910 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800911 raise error.PayloadError('%s: source hash missing.' % op_name)
912
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800913 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700914 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700915 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800916 """Checks a single update operation.
917
918 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700919 op: The operation object.
920 op_name: Operation name string for error reporting.
921 is_last: Whether this is the last operation in the sequence.
922 old_block_counters: Arrays of block read counters.
923 new_block_counters: Arrays of block write counters.
924 old_usable_size: The overall usable size for src data in bytes.
925 new_usable_size: The overall usable size for dst data in bytes.
926 prev_data_offset: Offset of last used data bytes.
927 allow_signature: Whether this may be a signature operation.
928 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800929
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800930 Returns:
931 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800932
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800933 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700934 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800935 """
936 # Check extents.
937 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700938 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800939 op_name + '.src_extents', allow_pseudo=True)
940 allow_signature_in_extents = (allow_signature and is_last and
941 op.type == common.OpType.REPLACE)
942 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700943 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700944 op_name + '.dst_extents',
945 allow_pseudo=(not self.check_dst_pseudo_extents),
946 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800947
948 # Check: data_offset present <==> data_length present.
949 data_offset = self._CheckOptionalField(op, 'data_offset', None)
950 data_length = self._CheckOptionalField(op, 'data_length', None)
951 self._CheckPresentIff(data_offset, data_length, 'data_offset',
952 'data_length', op_name)
953
Gilad Arnoldcb638912013-06-24 04:57:11 -0700954 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800955 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700956 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800957
958 # Check {src,dst}_length, if present.
959 if op.HasField('src_length'):
960 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
961 if op.HasField('dst_length'):
962 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
963
964 if op.HasField('data_sha256_hash'):
965 blob_hash_counts['hashed'] += 1
966
Gilad Arnoldcb638912013-06-24 04:57:11 -0700967 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800968 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700969 raise error.PayloadError(
970 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800971 op_name)
972
Gilad Arnoldcb638912013-06-24 04:57:11 -0700973 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800974 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
975 data_length))
976 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700977 raise error.PayloadError(
978 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700979 (op_name, common.FormatSha256(op.data_sha256_hash),
980 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800981 elif data_offset is not None:
982 if allow_signature_in_extents:
983 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700984 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800985 blob_hash_counts['unhashed'] += 1
986 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700987 raise error.PayloadError('%s: unhashed operation not allowed.' %
988 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800989
990 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700991 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800992 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700993 raise error.PayloadError(
994 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800995 (op_name, data_offset, prev_data_offset))
996
997 # Type-specific checks.
998 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
999 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001000 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001001 self._CheckMoveOperation(op, data_offset, total_src_blocks,
1002 total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001003 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
1004 self._CheckZeroOperation(op, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001005 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001006 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001007 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001008 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
1009 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001010 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001011 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001012 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001013 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani90c57d72018-02-07 16:21:33 -08001014 elif op.type == common.OpType.BROTLI_BSDIFF and self.minor_version >= 4:
1015 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
1016 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
1017 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 5:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001018 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001019 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001020 else:
Allie Wood7cf9f132015-02-26 14:28:19 -08001021 raise error.PayloadError(
1022 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001023 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001024 return data_length if data_length is not None else 0
1025
Gilad Arnold382df5c2013-05-03 12:49:28 -07001026 def _SizeToNumBlocks(self, size):
1027 """Returns the number of blocks needed to contain a given byte size."""
1028 return (size + self.block_size - 1) / self.block_size
1029
1030 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001031 """Returns a freshly initialized array of block counters.
1032
Gilad Arnoldcb638912013-06-24 04:57:11 -07001033 Note that the generated array is not portable as is due to byte-ordering
1034 issues, hence it should not be serialized.
1035
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001036 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001037 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001038
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001039 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001040 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001041 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001042 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001043 return array.array('H',
1044 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001045
Gilad Arnold382df5c2013-05-03 12:49:28 -07001046 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001047 new_fs_size, old_usable_size, new_usable_size,
1048 prev_data_offset, allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001049 """Checks a sequence of update operations.
1050
1051 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001052 operations: The sequence of operations to check.
1053 report: The report object to add to.
1054 base_name: The name of the operation block.
1055 old_fs_size: The old filesystem size in bytes.
1056 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001057 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001058 new_usable_size: The overall usable size of the new partition in bytes.
1059 prev_data_offset: Offset of last used data bytes.
1060 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001061
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001062 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001063 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001064
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001065 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001066 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001067 """
1068 # The total size of data blobs used by operations scanned thus far.
1069 total_data_used = 0
1070 # Counts of specific operation types.
1071 op_counts = {
1072 common.OpType.REPLACE: 0,
1073 common.OpType.REPLACE_BZ: 0,
1074 common.OpType.MOVE: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001075 common.OpType.ZERO: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001076 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001077 common.OpType.SOURCE_COPY: 0,
1078 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001079 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001080 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001081 }
1082 # Total blob sizes for each operation type.
1083 op_blob_totals = {
1084 common.OpType.REPLACE: 0,
1085 common.OpType.REPLACE_BZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001086 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001087 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001088 # SOURCE_COPY operations don't have blobs.
1089 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001090 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001091 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001092 }
1093 # Counts of hashed vs unhashed operations.
1094 blob_hash_counts = {
1095 'hashed': 0,
1096 'unhashed': 0,
1097 }
1098 if allow_signature:
1099 blob_hash_counts['signature'] = 0
1100
1101 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001102 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001103 if old_fs_size else None)
1104 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001105
1106 # Process and verify each operation.
1107 op_num = 0
1108 for op, op_name in common.OperationIter(operations, base_name):
1109 op_num += 1
1110
Gilad Arnoldcb638912013-06-24 04:57:11 -07001111 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001112 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001113 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001114 op_counts[op.type] += 1
1115
1116 is_last = op_num == len(operations)
1117 curr_data_used = self._CheckOperation(
1118 op, op_name, is_last, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001119 old_usable_size, new_usable_size,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001120 prev_data_offset + total_data_used, allow_signature,
1121 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001122 if curr_data_used:
1123 op_blob_totals[op.type] += curr_data_used
1124 total_data_used += curr_data_used
1125
1126 # Report totals and breakdown statistics.
1127 report.AddField('total operations', op_num)
1128 report.AddField(
1129 None,
1130 histogram.Histogram.FromCountDict(op_counts,
1131 key_names=common.OpType.NAMES),
1132 indent=1)
1133 report.AddField('total blobs', sum(blob_hash_counts.values()))
1134 report.AddField(None,
1135 histogram.Histogram.FromCountDict(blob_hash_counts),
1136 indent=1)
1137 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1138 report.AddField(
1139 None,
1140 histogram.Histogram.FromCountDict(op_blob_totals,
1141 formatter=_AddHumanReadableSize,
1142 key_names=common.OpType.NAMES),
1143 indent=1)
1144
1145 # Report read/write histograms.
1146 if old_block_counters:
1147 report.AddField('block read hist',
1148 histogram.Histogram.FromKeyList(old_block_counters),
1149 linebreak=True, indent=1)
1150
Gilad Arnold382df5c2013-05-03 12:49:28 -07001151 new_write_hist = histogram.Histogram.FromKeyList(
1152 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1153 report.AddField('block write hist', new_write_hist, linebreak=True,
1154 indent=1)
1155
Gilad Arnoldcb638912013-06-24 04:57:11 -07001156 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001157 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001158 raise error.PayloadError(
1159 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001160 base_name)
1161
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001162 return total_data_used
1163
1164 def _CheckSignatures(self, report, pubkey_file_name):
1165 """Checks a payload's signature block."""
1166 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1167 sigs = update_metadata_pb2.Signatures()
1168 sigs.ParseFromString(sigs_raw)
1169 report.AddSection('signatures')
1170
Gilad Arnoldcb638912013-06-24 04:57:11 -07001171 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001172 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001173 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001174
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001175 last_ops_section = (self.payload.manifest.kernel_install_operations or
1176 self.payload.manifest.install_operations)
1177 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001178 # Check: signatures_{offset,size} must match the last (fake) operation.
1179 if not (fake_sig_op.type == common.OpType.REPLACE and
1180 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001181 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001182 raise error.PayloadError(
1183 'Signatures_{offset,size} (%d+%d) does not match last operation '
1184 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001185 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1186 fake_sig_op.data_length))
1187
1188 # Compute the checksum of all data up to signature blob.
1189 # TODO(garnold) we're re-reading the whole data section into a string
1190 # just to compute the checksum; instead, we could do it incrementally as
1191 # we read the blobs one-by-one, under the assumption that we're reading
1192 # them in order (which currently holds). This should be reconsidered.
1193 payload_hasher = self.payload.manifest_hasher.copy()
1194 common.Read(self.payload.payload_file, self.sigs_offset,
1195 offset=self.payload.data_offset, hasher=payload_hasher)
1196
1197 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1198 sig_report = report.AddSubReport(sig_name)
1199
Gilad Arnoldcb638912013-06-24 04:57:11 -07001200 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001201 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1202 self._CheckMandatoryField(sig, 'data', None, sig_name)
1203 sig_report.AddField('data len', len(sig.data))
1204
Gilad Arnoldcb638912013-06-24 04:57:11 -07001205 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001206 if sig.version == 1:
1207 self._CheckSha256Signature(sig.data, pubkey_file_name,
1208 payload_hasher.digest(), sig_name)
1209 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001210 raise error.PayloadError('Unknown signature version (%d).' %
1211 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001212
1213 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001214 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001215 """Checker entry point, invoking all checks.
1216
1217 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001218 pubkey_file_name: Public key used for signature verification.
1219 metadata_sig_file: Metadata signature, if verification is desired.
Gilad Arnold06eea332015-07-13 18:06:33 -07001220 rootfs_part_size: The size of rootfs partitions in bytes (default: infer
1221 based on payload type and version).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001222 kernel_part_size: The size of kernel partitions in bytes (default: use
1223 reported filesystem size).
1224 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001225
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001226 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001227 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001228 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001229 if not pubkey_file_name:
1230 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1231
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001232 report = _PayloadReport()
1233
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001234 # Get payload file size.
1235 self.payload.payload_file.seek(0, 2)
1236 payload_file_size = self.payload.payload_file.tell()
1237 self.payload.ResetFile()
1238
1239 try:
1240 # Check metadata signature (if provided).
1241 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001242 metadata_sig = base64.b64decode(metadata_sig_file.read())
1243 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1244 self.payload.manifest_hasher.digest(),
1245 'metadata signature')
1246
Gilad Arnoldcb638912013-06-24 04:57:11 -07001247 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001248 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001249 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001250 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001251 raise error.PayloadError('Unknown payload version (%d).' %
1252 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001253 report.AddField('version', self.payload.header.version)
1254 report.AddField('manifest len', self.payload.header.manifest_len)
1255
Gilad Arnoldcb638912013-06-24 04:57:11 -07001256 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001257 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001258 assert self.payload_type, 'payload type should be known by now'
1259
Gilad Arnold06eea332015-07-13 18:06:33 -07001260 # Infer the usable partition size when validating rootfs operations:
1261 # - If rootfs partition size was provided, use that.
1262 # - Otherwise, if this is an older delta (minor version < 2), stick with
1263 # a known constant size. This is necessary because older deltas may
1264 # exceed the filesystem size when moving data blocks around.
1265 # - Otherwise, use the encoded filesystem size.
1266 new_rootfs_usable_size = self.new_rootfs_fs_size
Amin Hassaniae853742017-10-11 10:27:27 -07001267 old_rootfs_usable_size = self.old_rootfs_fs_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001268 if rootfs_part_size:
1269 new_rootfs_usable_size = rootfs_part_size
Amin Hassaniae853742017-10-11 10:27:27 -07001270 old_rootfs_usable_size = rootfs_part_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001271 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
1272 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Amin Hassaniae853742017-10-11 10:27:27 -07001273 old_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Gilad Arnold06eea332015-07-13 18:06:33 -07001274
Gilad Arnoldcb638912013-06-24 04:57:11 -07001275 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001276 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1277 # no explicit size provided *and* the partition size is not embedded in
1278 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001279 report.AddSection('rootfs operations')
1280 total_blob_size = self._CheckOperations(
1281 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001282 'install_operations', self.old_rootfs_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001283 self.new_rootfs_fs_size, old_rootfs_usable_size,
1284 new_rootfs_usable_size, 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001285
Gilad Arnoldcb638912013-06-24 04:57:11 -07001286 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001287 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001288 report.AddSection('kernel operations')
1289 total_blob_size += self._CheckOperations(
1290 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001291 'kernel_install_operations', self.old_kernel_fs_size,
1292 self.new_kernel_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001293 kernel_part_size if kernel_part_size else self.old_kernel_fs_size,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001294 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1295 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001296
Gilad Arnoldcb638912013-06-24 04:57:11 -07001297 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001298 used_payload_size = self.payload.data_offset + total_blob_size
1299 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001300 raise error.PayloadError(
1301 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001302 (used_payload_size, payload_file_size))
1303
Gilad Arnoldcb638912013-06-24 04:57:11 -07001304 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001305 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001306 self._CheckSignatures(report, pubkey_file_name)
1307
Gilad Arnoldcb638912013-06-24 04:57:11 -07001308 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001309 report.AddSection('summary')
1310 report.AddField('update type', self.payload_type)
1311
1312 report.Finalize()
1313 finally:
1314 if report_out_file:
1315 report.Dump(report_out_file)