blob: c710c47ba41e8837a7190782d8226677dbb68527 [file] [log] [blame]
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Verifying the integrity of a Chrome OS update payload.
6
7This module is used internally by the main Payload class for verifying the
8integrity of an update payload. The interface for invoking the checks is as
9follows:
10
11 checker = PayloadChecker(payload)
12 checker.Run(...)
Gilad Arnold553b0ec2013-01-26 01:00:39 -080013"""
14
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080015from __future__ import print_function
16
Gilad Arnold553b0ec2013-01-26 01:00:39 -080017import array
18import base64
19import hashlib
Gilad Arnoldcb638912013-06-24 04:57:11 -070020import itertools
Gilad Arnold9b90c932013-05-22 17:12:56 -070021import os
Gilad Arnold553b0ec2013-01-26 01:00:39 -080022import subprocess
23
24import common
Gilad Arnoldcb638912013-06-24 04:57:11 -070025import error
Gilad Arnold553b0ec2013-01-26 01:00:39 -080026import format_utils
27import histogram
28import update_metadata_pb2
29
30
31#
Gilad Arnold9b90c932013-05-22 17:12:56 -070032# Constants.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080033#
Gilad Arnoldcb638912013-06-24 04:57:11 -070034
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070035_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents'
36_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
37_CHECK_PAYLOAD_SIG = 'payload-sig'
38CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070039 _CHECK_DST_PSEUDO_EXTENTS,
40 _CHECK_MOVE_SAME_SRC_DST_BLOCK,
41 _CHECK_PAYLOAD_SIG,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070042)
43
Gilad Arnold553b0ec2013-01-26 01:00:39 -080044_TYPE_FULL = 'full'
45_TYPE_DELTA = 'delta'
46
47_DEFAULT_BLOCK_SIZE = 4096
48
Gilad Arnold9b90c932013-05-22 17:12:56 -070049_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem'
50_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
51 _DEFAULT_PUBKEY_BASE_NAME)
52
Gilad Arnold0d575cd2015-07-13 17:29:21 -070053# Supported minor version map to payload types allowed to be using them.
54_SUPPORTED_MINOR_VERSIONS = {
55 0: (_TYPE_FULL,),
56 1: (_TYPE_DELTA,),
57 2: (_TYPE_DELTA,),
Sen Jiang912c4df2015-12-10 12:17:13 -080058 3: (_TYPE_DELTA,),
Sen Jiang92161a72016-06-28 16:09:38 -070059 4: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070060}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080061
Gilad Arnold06eea332015-07-13 18:06:33 -070062_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
63
Gilad Arnold553b0ec2013-01-26 01:00:39 -080064#
65# Helper functions.
66#
Gilad Arnoldcb638912013-06-24 04:57:11 -070067
Gilad Arnold553b0ec2013-01-26 01:00:39 -080068def _IsPowerOfTwo(val):
69 """Returns True iff val is a power of two."""
70 return val > 0 and (val & (val - 1)) == 0
71
72
73def _AddFormat(format_func, value):
74 """Adds a custom formatted representation to ordinary string representation.
75
76 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070077 format_func: A value formatter.
78 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080079
Gilad Arnold553b0ec2013-01-26 01:00:39 -080080 Returns:
81 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080082 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070083 ret = str(value)
84 formatted_str = format_func(value)
85 if formatted_str:
86 ret += ' (%s)' % formatted_str
87 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -080088
89
90def _AddHumanReadableSize(size):
91 """Adds a human readable representation to a byte size value."""
92 return _AddFormat(format_utils.BytesToHumanReadable, size)
93
94
95#
96# Payload report generator.
97#
Gilad Arnoldcb638912013-06-24 04:57:11 -070098
Gilad Arnold553b0ec2013-01-26 01:00:39 -080099class _PayloadReport(object):
100 """A payload report generator.
101
102 A report is essentially a sequence of nodes, which represent data points. It
103 is initialized to have a "global", untitled section. A node may be a
104 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800105 """
106
Gilad Arnoldcb638912013-06-24 04:57:11 -0700107 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800108 class Node(object):
109 """A report node interface."""
110
111 @staticmethod
112 def _Indent(indent, line):
113 """Indents a line by a given indentation amount.
114
115 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700116 indent: The indentation amount.
117 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800118
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800119 Returns:
120 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800121 """
122 return '%*s%s' % (indent, '', line)
123
124 def GenerateLines(self, base_indent, sub_indent, curr_section):
125 """Generates the report lines for this node.
126
127 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700128 base_indent: Base indentation for each line.
129 sub_indent: Additional indentation for sub-nodes.
130 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800131
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800132 Returns:
133 A pair consisting of a list of properly indented report lines and a new
134 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800135 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700136 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800137
138 class FieldNode(Node):
139 """A field report node, representing a (name, value) pair."""
140
141 def __init__(self, name, value, linebreak, indent):
142 super(_PayloadReport.FieldNode, self).__init__()
143 self.name = name
144 self.value = value
145 self.linebreak = linebreak
146 self.indent = indent
147
148 def GenerateLines(self, base_indent, sub_indent, curr_section):
149 """Generates a properly formatted 'name : value' entry."""
150 report_output = ''
151 if self.name:
152 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
153 value_lines = str(self.value).splitlines()
154 if self.linebreak and self.name:
155 report_output += '\n' + '\n'.join(
156 ['%*s%s' % (self.indent, '', line) for line in value_lines])
157 else:
158 if self.name:
159 report_output += ' '
160 report_output += '%*s' % (self.indent, '')
161 cont_line_indent = len(report_output)
162 indented_value_lines = [value_lines[0]]
163 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
164 for line in value_lines[1:]])
165 report_output += '\n'.join(indented_value_lines)
166
167 report_lines = [self._Indent(base_indent, line + '\n')
168 for line in report_output.split('\n')]
169 return report_lines, curr_section
170
171 class SubReportNode(Node):
172 """A sub-report node, representing a nested report."""
173
174 def __init__(self, title, report):
175 super(_PayloadReport.SubReportNode, self).__init__()
176 self.title = title
177 self.report = report
178
179 def GenerateLines(self, base_indent, sub_indent, curr_section):
180 """Recurse with indentation."""
181 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
182 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
183 sub_indent))
184 return report_lines, curr_section
185
186 class SectionNode(Node):
187 """A section header node."""
188
189 def __init__(self, title=None):
190 super(_PayloadReport.SectionNode, self).__init__()
191 self.title = title
192 self.max_field_name_len = 0
193
194 def GenerateLines(self, base_indent, sub_indent, curr_section):
195 """Dump a title line, return self as the (new) current section."""
196 report_lines = []
197 if self.title:
198 report_lines.append(self._Indent(base_indent,
199 '=== %s ===\n' % self.title))
200 return report_lines, self
201
202 def __init__(self):
203 self.report = []
204 self.last_section = self.global_section = self.SectionNode()
205 self.is_finalized = False
206
207 def GenerateLines(self, base_indent, sub_indent):
208 """Generates the lines in the report, properly indented.
209
210 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700211 base_indent: The indentation used for root-level report lines.
212 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800213
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800214 Returns:
215 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800216 """
217 report_lines = []
218 curr_section = self.global_section
219 for node in self.report:
220 node_report_lines, curr_section = node.GenerateLines(
221 base_indent, sub_indent, curr_section)
222 report_lines.extend(node_report_lines)
223
224 return report_lines
225
226 def Dump(self, out_file, base_indent=0, sub_indent=2):
227 """Dumps the report to a file.
228
229 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700230 out_file: File object to output the content to.
231 base_indent: Base indentation for report lines.
232 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800233 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800234 report_lines = self.GenerateLines(base_indent, sub_indent)
235 if report_lines and not self.is_finalized:
236 report_lines.append('(incomplete report)\n')
237
238 for line in report_lines:
239 out_file.write(line)
240
241 def AddField(self, name, value, linebreak=False, indent=0):
242 """Adds a field/value pair to the payload report.
243
244 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700245 name: The field's name.
246 value: The field's value.
247 linebreak: Whether the value should be printed on a new line.
248 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800249 """
250 assert not self.is_finalized
251 if name and self.last_section.max_field_name_len < len(name):
252 self.last_section.max_field_name_len = len(name)
253 self.report.append(self.FieldNode(name, value, linebreak, indent))
254
255 def AddSubReport(self, title):
256 """Adds and returns a sub-report with a title."""
257 assert not self.is_finalized
258 sub_report = self.SubReportNode(title, type(self)())
259 self.report.append(sub_report)
260 return sub_report.report
261
262 def AddSection(self, title):
263 """Adds a new section title."""
264 assert not self.is_finalized
265 self.last_section = self.SectionNode(title)
266 self.report.append(self.last_section)
267
268 def Finalize(self):
269 """Seals the report, marking it as complete."""
270 self.is_finalized = True
271
272
273#
274# Payload verification.
275#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700276
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800277class PayloadChecker(object):
278 """Checking the integrity of an update payload.
279
280 This is a short-lived object whose purpose is to isolate the logic used for
281 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800282 """
283
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700284 def __init__(self, payload, assert_type=None, block_size=0,
285 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700286 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700287
288 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700289 payload: The payload object to check.
290 assert_type: Assert that payload is either 'full' or 'delta' (optional).
291 block_size: Expected filesystem / payload block size (optional).
292 allow_unhashed: Allow operations with unhashed data blobs.
293 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700294 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700295 if not payload.is_init:
296 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700297
298 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800299 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700300 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
301 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700302 raise error.PayloadError(
303 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700304 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700305 raise error.PayloadError('Invalid assert_type value (%r).' %
306 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700307 self.payload_type = assert_type
308 self.allow_unhashed = allow_unhashed
309
310 # Disable specific tests.
311 self.check_dst_pseudo_extents = (
312 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
313 self.check_move_same_src_dst_block = (
314 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
315 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800316
317 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800318 self.sigs_offset = 0
319 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700320 self.old_rootfs_fs_size = 0
321 self.old_kernel_fs_size = 0
322 self.new_rootfs_fs_size = 0
323 self.new_kernel_fs_size = 0
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700324 self.minor_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800325
326 @staticmethod
327 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
328 msg_name=None, linebreak=False, indent=0):
329 """Adds an element from a protobuf message to the payload report.
330
331 Checks to see whether a message contains a given element, and if so adds
332 the element value to the provided report. A missing mandatory element
333 causes an exception to be raised.
334
335 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700336 msg: The message containing the element.
337 name: The name of the element.
338 report: A report object to add the element name/value to.
339 is_mandatory: Whether or not this element must be present.
340 is_submsg: Whether this element is itself a message.
341 convert: A function for converting the element value for reporting.
342 msg_name: The name of the message object (for error reporting).
343 linebreak: Whether the value report should induce a line break.
344 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800345
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800346 Returns:
347 A pair consisting of the element value and the generated sub-report for
348 it (if the element is a sub-message, None otherwise). If the element is
349 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800350
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800351 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700352 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800353 """
354 if not msg.HasField(name):
355 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700356 raise error.PayloadError('%smissing mandatory %s %r.' %
357 (msg_name + ' ' if msg_name else '',
358 'sub-message' if is_submsg else 'field',
359 name))
360 return None, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800361
362 value = getattr(msg, name)
363 if is_submsg:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700364 return value, report and report.AddSubReport(name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800365 else:
366 if report:
367 report.AddField(name, convert(value), linebreak=linebreak,
368 indent=indent)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700369 return value, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800370
371 @staticmethod
372 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
373 linebreak=False, indent=0):
374 """Adds a mandatory field; returning first component from _CheckElem."""
375 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
376 convert=convert, msg_name=msg_name,
377 linebreak=linebreak, indent=indent)[0]
378
379 @staticmethod
380 def _CheckOptionalField(msg, field_name, report, convert=str,
381 linebreak=False, indent=0):
382 """Adds an optional field; returning first component from _CheckElem."""
383 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
384 convert=convert, linebreak=linebreak,
385 indent=indent)[0]
386
387 @staticmethod
388 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
389 """Adds a mandatory sub-message; wrapper for _CheckElem."""
390 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
391 msg_name)
392
393 @staticmethod
394 def _CheckOptionalSubMsg(msg, submsg_name, report):
395 """Adds an optional sub-message; wrapper for _CheckElem."""
396 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
397
398 @staticmethod
399 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
400 """Checks that val1 is None iff val2 is None.
401
402 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700403 val1: first value to be compared.
404 val2: second value to be compared.
405 name1: name of object holding the first value.
406 name2: name of object holding the second value.
407 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800408
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800409 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700410 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800411 """
412 if None in (val1, val2) and val1 is not val2:
413 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700414 raise error.PayloadError('%r present without %r%s.' %
415 (present, missing,
416 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800417
418 @staticmethod
419 def _Run(cmd, send_data=None):
420 """Runs a subprocess, returns its output.
421
422 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700423 cmd: Sequence of command-line argument for invoking the subprocess.
424 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800425
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800426 Returns:
427 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800428 """
429 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
430 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700431 try:
432 result = run_process.communicate(input=send_data)
433 finally:
434 exit_code = run_process.wait()
435
436 if exit_code:
437 raise RuntimeError('Subprocess %r failed with code %r.' %
438 (cmd, exit_code))
439
440 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800441
442 @staticmethod
443 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
444 """Verifies an actual hash against a signed one.
445
446 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700447 sig_data: The raw signature data.
448 pubkey_file_name: Public key used for verifying signature.
449 actual_hash: The actual hash digest.
450 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800451
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800452 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700453 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800454 """
455 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700456 raise error.PayloadError(
457 '%s: signature size (%d) not as expected (256).' %
458 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800459 signed_data, _ = PayloadChecker._Run(
460 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
461 send_data=sig_data)
462
Gilad Arnold5502b562013-03-08 13:22:31 -0800463 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700464 raise error.PayloadError('%s: unexpected signed data length (%d).' %
465 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800466
Gilad Arnold5502b562013-03-08 13:22:31 -0800467 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700468 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
469 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800470
Gilad Arnold5502b562013-03-08 13:22:31 -0800471 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800472 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700473 raise error.PayloadError(
474 '%s: signed hash (%s) different from actual (%s).' %
475 (sig_name, common.FormatSha256(signed_hash),
476 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800477
478 @staticmethod
479 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
480 block_name=None):
481 """Checks that a given length fits given block space.
482
483 This ensures that the number of blocks allocated is appropriate for the
484 length of the data residing in these blocks.
485
486 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700487 length: The actual length of the data.
488 num_blocks: The number of blocks allocated for it.
489 block_size: The size of each block in bytes.
490 length_name: Name of length (used for error reporting).
491 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800492
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800493 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700494 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800495 """
496 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700497 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700498 raise error.PayloadError(
499 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800500 (length_name, length, block_name or '', num_blocks, block_size))
501
502 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700503 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700504 raise error.PayloadError(
505 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800506 (length_name, length, block_name or '', num_blocks - 1, block_size))
507
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700508 def _CheckManifestMinorVersion(self, report):
509 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800510
511 Args:
512 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800513
514 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700515 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800516 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700517 self.minor_version = self._CheckOptionalField(self.payload.manifest,
518 'minor_version', report)
519 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
520 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800521 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700522 'Minor version %d not compatible with payload type %s.' %
523 (self.minor_version, self.payload_type))
524 elif self.minor_version is None:
525 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800526 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700527 raise error.PayloadError('Unsupported minor version: %d' %
528 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800529
Gilad Arnold382df5c2013-05-03 12:49:28 -0700530 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800531 """Checks the payload manifest.
532
533 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700534 report: A report object to add to.
535 rootfs_part_size: Size of the rootfs partition in bytes.
536 kernel_part_size: Size of the kernel partition in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800537
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800538 Returns:
539 A tuple consisting of the partition block size used during the update
540 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800541
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800542 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700543 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800544 """
545 manifest = self.payload.manifest
546 report.AddSection('manifest')
547
548 # Check: block_size must exist and match the expected value.
549 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
550 report, 'manifest')
551 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700552 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
553 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800554
555 # Check: signatures_offset <==> signatures_size.
556 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
557 report)
558 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
559 report)
560 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
561 'signatures_offset', 'signatures_size', 'manifest')
562
563 # Check: old_kernel_info <==> old_rootfs_info.
564 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
565 'old_kernel_info', report)
566 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
567 'old_rootfs_info', report)
568 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
569 'old_rootfs_info', 'manifest')
570 if oki_msg: # equivalently, ori_msg
571 # Assert/mark delta payload.
572 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700573 raise error.PayloadError(
574 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800575 self.payload_type = _TYPE_DELTA
576
577 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700578 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800579 oki_msg, 'size', oki_report, 'old_kernel_info')
580 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
581 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700582 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800583 ori_msg, 'size', ori_report, 'old_rootfs_info')
584 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
585 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700586
587 # Check: old_{kernel,rootfs} size must fit in respective partition.
588 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700589 raise error.PayloadError(
590 'Old kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700591 (self.old_kernel_fs_size, kernel_part_size))
592 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700593 raise error.PayloadError(
594 'Old rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700595 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800596 else:
597 # Assert/mark full payload.
598 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700599 raise error.PayloadError(
600 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800601 self.payload_type = _TYPE_FULL
602
603 # Check: new_kernel_info present; contains {size, hash}.
604 nki_msg, nki_report = self._CheckMandatorySubMsg(
605 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700606 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800607 nki_msg, 'size', nki_report, 'new_kernel_info')
608 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
609 convert=common.FormatSha256)
610
611 # Check: new_rootfs_info present; contains {size, hash}.
612 nri_msg, nri_report = self._CheckMandatorySubMsg(
613 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700614 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800615 nri_msg, 'size', nri_report, 'new_rootfs_info')
616 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
617 convert=common.FormatSha256)
618
Gilad Arnold382df5c2013-05-03 12:49:28 -0700619 # Check: new_{kernel,rootfs} size must fit in respective partition.
620 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700621 raise error.PayloadError(
622 'New kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700623 (self.new_kernel_fs_size, kernel_part_size))
624 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700625 raise error.PayloadError(
626 'New rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700627 (self.new_rootfs_fs_size, rootfs_part_size))
628
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800629 # Check: minor_version makes sense for the payload type. This check should
630 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700631 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800632
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800633 def _CheckLength(self, length, total_blocks, op_name, length_name):
634 """Checks whether a length matches the space designated in extents.
635
636 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700637 length: The total length of the data.
638 total_blocks: The total number of blocks in extents.
639 op_name: Operation name (for error reporting).
640 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800641
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800642 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700643 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800644 """
645 # Check: length is non-zero.
646 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700647 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800648
649 # Check that length matches number of blocks.
650 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
651 '%s: %s' % (op_name, length_name))
652
Gilad Arnold382df5c2013-05-03 12:49:28 -0700653 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800654 allow_pseudo=False, allow_signature=False):
655 """Checks a sequence of extents.
656
657 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700658 extents: The sequence of extents to check.
659 usable_size: The usable size of the partition to which the extents apply.
660 block_counters: Array of counters corresponding to the number of blocks.
661 name: The name of the extent block.
662 allow_pseudo: Whether or not pseudo block numbers are allowed.
663 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800664
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800665 Returns:
666 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800667
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800668 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700669 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800670 """
671 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800672 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700673 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800674 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
675 None, ex_name)
676 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
677 ex_name)
678 end_block = start_block + num_blocks
679
680 # Check: num_blocks > 0.
681 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700682 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800683
684 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700685 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700686 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700687 raise error.PayloadError(
688 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700689 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800690
691 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700692 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800693 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800694 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
695 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
696 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700697 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800698
699 total_num_blocks += num_blocks
700
701 return total_num_blocks
702
703 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
704 """Specific checks for REPLACE/REPLACE_BZ operations.
705
706 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700707 op: The operation object from the manifest.
708 data_length: The length of the data blob associated with the operation.
709 total_dst_blocks: Total number of blocks in dst_extents.
710 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800711
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800712 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700713 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800714 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700715 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800716 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700717 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800718
Gilad Arnoldcb638912013-06-24 04:57:11 -0700719 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800720 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700721 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800722
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800723 if op.type == common.OpType.REPLACE:
724 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
725 self.block_size,
726 op_name + '.data_length', 'dst')
727 else:
728 # Check: data_length must be smaller than the alotted dst blocks.
729 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700730 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800731 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700732 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800733 (op_name, data_length, total_dst_blocks, self.block_size))
734
735 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
736 total_dst_blocks, op_name):
737 """Specific checks for MOVE operations.
738
739 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700740 op: The operation object from the manifest.
741 data_offset: The offset of a data blob for the operation.
742 total_src_blocks: Total number of blocks in src_extents.
743 total_dst_blocks: Total number of blocks in dst_extents.
744 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800745
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800746 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700747 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800748 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700749 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800750 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700751 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800752
Gilad Arnoldcb638912013-06-24 04:57:11 -0700753 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800754 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700755 raise error.PayloadError(
756 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800757 (op_name, total_src_blocks, total_dst_blocks))
758
Gilad Arnoldcb638912013-06-24 04:57:11 -0700759 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800760 i = 0
761 src_extent_iter = iter(op.src_extents)
762 dst_extent_iter = iter(op.dst_extents)
763 src_extent = dst_extent = None
764 src_idx = src_num = dst_idx = dst_num = 0
765 while i < total_src_blocks:
766 # Get the next source extent, if needed.
767 if not src_extent:
768 try:
769 src_extent = src_extent_iter.next()
770 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700771 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
772 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800773 src_idx = src_extent.start_block
774 src_num = src_extent.num_blocks
775
776 # Get the next dest extent, if needed.
777 if not dst_extent:
778 try:
779 dst_extent = dst_extent_iter.next()
780 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700781 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
782 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800783 dst_idx = dst_extent.start_block
784 dst_num = dst_extent.num_blocks
785
Allie Woodb065e132015-04-24 10:20:27 -0700786 # Check: start block is not 0. See crbug/480751; there are still versions
787 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
788 # so we need to fail payloads that try to MOVE to/from block 0.
789 if src_idx == 0 or dst_idx == 0:
790 raise error.PayloadError(
791 '%s: MOVE operation cannot have extent with start block 0' %
792 op_name)
793
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700794 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700795 raise error.PayloadError(
796 '%s: src/dst block number %d is the same (%d).' %
797 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800798
799 advance = min(src_num, dst_num)
800 i += advance
801
802 src_idx += advance
803 src_num -= advance
804 if src_num == 0:
805 src_extent = None
806
807 dst_idx += advance
808 dst_num -= advance
809 if dst_num == 0:
810 dst_extent = None
811
Gilad Arnold5502b562013-03-08 13:22:31 -0800812 # Make sure we've exhausted all src/dst extents.
813 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700814 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800815 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700816 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800817
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700818 def _CheckZeroOperation(self, op, op_name):
819 """Specific checks for ZERO operations.
820
821 Args:
822 op: The operation object from the manifest.
823 op_name: Operation name for error reporting.
824
825 Raises:
826 error.PayloadError if any check fails.
827 """
828 # Check: Does not contain src extents, data_length and data_offset.
829 if op.src_extents:
830 raise error.PayloadError('%s: contains src_extents.' % op_name)
831 if op.data_length:
832 raise error.PayloadError('%s: contains data_length.' % op_name)
833 if op.data_offset:
834 raise error.PayloadError('%s: contains data_offset.' % op_name)
835
Amin Hassaniefa62d92017-11-09 13:46:56 -0800836 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
837 """Specific checks for BSDIFF, SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
838 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800839
840 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800841 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700842 data_length: The length of the data blob associated with the operation.
843 total_dst_blocks: Total number of blocks in dst_extents.
844 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800845
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800846 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700847 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800848 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800849 # Check: data_{offset,length} present.
850 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700851 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800852
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800853 # Check: data_length is strictly smaller than the alotted dst blocks.
854 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700855 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800856 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700857 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800858 (op_name, data_length, total_dst_blocks, self.block_size,
859 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800860
Amin Hassaniefa62d92017-11-09 13:46:56 -0800861 # Check the existence of src_length and dst_length for legacy bsdiffs.
862 if (op.type == common.OpType.BSDIFF or
863 (op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3)):
864 if not op.HasField('src_length') or not op.HasField('dst_length'):
865 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
866 else:
867 if op.HasField('src_length') or op.HasField('dst_length'):
868 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
869
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800870 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
871 total_dst_blocks, op_name):
872 """Specific checks for SOURCE_COPY.
873
874 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800875 data_offset: The offset of a data blob for the operation.
876 total_src_blocks: Total number of blocks in src_extents.
877 total_dst_blocks: Total number of blocks in dst_extents.
878 op_name: Operation name for error reporting.
879
880 Raises:
881 error.PayloadError if any check fails.
882 """
883 # Check: No data_{offset,length}.
884 if data_offset is not None:
885 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
886
887 # Check: total_src_blocks == total_dst_blocks.
888 if total_src_blocks != total_dst_blocks:
889 raise error.PayloadError(
890 '%s: total src blocks (%d) != total dst blocks (%d).' %
891 (op_name, total_src_blocks, total_dst_blocks))
892
Sen Jiangd6122bb2015-12-11 10:27:04 -0800893 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800894 """Specific checks for SOURCE_* operations.
895
896 Args:
897 op: The operation object from the manifest.
898 total_src_blocks: Total number of blocks in src_extents.
899 op_name: Operation name for error reporting.
900
901 Raises:
902 error.PayloadError if any check fails.
903 """
904 # Check: total_src_blocks != 0.
905 if total_src_blocks == 0:
906 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
907
Sen Jiang92161a72016-06-28 16:09:38 -0700908 # Check: src_sha256_hash present in minor version >= 3.
909 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800910 raise error.PayloadError('%s: source hash missing.' % op_name)
911
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800912 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700913 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700914 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800915 """Checks a single update operation.
916
917 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700918 op: The operation object.
919 op_name: Operation name string for error reporting.
920 is_last: Whether this is the last operation in the sequence.
921 old_block_counters: Arrays of block read counters.
922 new_block_counters: Arrays of block write counters.
923 old_usable_size: The overall usable size for src data in bytes.
924 new_usable_size: The overall usable size for dst data in bytes.
925 prev_data_offset: Offset of last used data bytes.
926 allow_signature: Whether this may be a signature operation.
927 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800928
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800929 Returns:
930 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800931
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800932 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700933 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800934 """
935 # Check extents.
936 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700937 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800938 op_name + '.src_extents', allow_pseudo=True)
939 allow_signature_in_extents = (allow_signature and is_last and
940 op.type == common.OpType.REPLACE)
941 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700942 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700943 op_name + '.dst_extents',
944 allow_pseudo=(not self.check_dst_pseudo_extents),
945 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800946
947 # Check: data_offset present <==> data_length present.
948 data_offset = self._CheckOptionalField(op, 'data_offset', None)
949 data_length = self._CheckOptionalField(op, 'data_length', None)
950 self._CheckPresentIff(data_offset, data_length, 'data_offset',
951 'data_length', op_name)
952
Gilad Arnoldcb638912013-06-24 04:57:11 -0700953 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800954 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700955 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800956
957 # Check {src,dst}_length, if present.
958 if op.HasField('src_length'):
959 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
960 if op.HasField('dst_length'):
961 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
962
963 if op.HasField('data_sha256_hash'):
964 blob_hash_counts['hashed'] += 1
965
Gilad Arnoldcb638912013-06-24 04:57:11 -0700966 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800967 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700968 raise error.PayloadError(
969 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800970 op_name)
971
Gilad Arnoldcb638912013-06-24 04:57:11 -0700972 # Check: Hash verifies correctly.
973 # pylint cannot find the method in hashlib, for some reason.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800974 # pylint: disable=E1101
975 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
976 data_length))
977 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700978 raise error.PayloadError(
979 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700980 (op_name, common.FormatSha256(op.data_sha256_hash),
981 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800982 elif data_offset is not None:
983 if allow_signature_in_extents:
984 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700985 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800986 blob_hash_counts['unhashed'] += 1
987 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700988 raise error.PayloadError('%s: unhashed operation not allowed.' %
989 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800990
991 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700992 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800993 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700994 raise error.PayloadError(
995 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800996 (op_name, data_offset, prev_data_offset))
997
998 # Type-specific checks.
999 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
1000 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001001 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001002 self._CheckMoveOperation(op, data_offset, total_src_blocks,
1003 total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001004 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
1005 self._CheckZeroOperation(op, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001006 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001007 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001008 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001009 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
1010 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001011 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001012 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001013 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001014 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassaniefa62d92017-11-09 13:46:56 -08001015 elif (op.type in (common.OpType.PUFFDIFF, common.OpType.BROTLI_BSDIFF) and
1016 self.minor_version >= 4):
1017 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001018 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001019 else:
Allie Wood7cf9f132015-02-26 14:28:19 -08001020 raise error.PayloadError(
1021 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001022 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001023 return data_length if data_length is not None else 0
1024
Gilad Arnold382df5c2013-05-03 12:49:28 -07001025 def _SizeToNumBlocks(self, size):
1026 """Returns the number of blocks needed to contain a given byte size."""
1027 return (size + self.block_size - 1) / self.block_size
1028
1029 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001030 """Returns a freshly initialized array of block counters.
1031
Gilad Arnoldcb638912013-06-24 04:57:11 -07001032 Note that the generated array is not portable as is due to byte-ordering
1033 issues, hence it should not be serialized.
1034
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001035 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001036 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001037
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001038 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001039 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001040 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001041 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001042 return array.array('H',
1043 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001044
Gilad Arnold382df5c2013-05-03 12:49:28 -07001045 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001046 new_fs_size, old_usable_size, new_usable_size,
1047 prev_data_offset, allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001048 """Checks a sequence of update operations.
1049
1050 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001051 operations: The sequence of operations to check.
1052 report: The report object to add to.
1053 base_name: The name of the operation block.
1054 old_fs_size: The old filesystem size in bytes.
1055 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001056 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001057 new_usable_size: The overall usable size of the new partition in bytes.
1058 prev_data_offset: Offset of last used data bytes.
1059 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001060
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001061 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001062 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001063
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001064 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001065 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001066 """
1067 # The total size of data blobs used by operations scanned thus far.
1068 total_data_used = 0
1069 # Counts of specific operation types.
1070 op_counts = {
1071 common.OpType.REPLACE: 0,
1072 common.OpType.REPLACE_BZ: 0,
1073 common.OpType.MOVE: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001074 common.OpType.ZERO: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001075 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001076 common.OpType.SOURCE_COPY: 0,
1077 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001078 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001079 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001080 }
1081 # Total blob sizes for each operation type.
1082 op_blob_totals = {
1083 common.OpType.REPLACE: 0,
1084 common.OpType.REPLACE_BZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001085 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001086 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001087 # SOURCE_COPY operations don't have blobs.
1088 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001089 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001090 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001091 }
1092 # Counts of hashed vs unhashed operations.
1093 blob_hash_counts = {
1094 'hashed': 0,
1095 'unhashed': 0,
1096 }
1097 if allow_signature:
1098 blob_hash_counts['signature'] = 0
1099
1100 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001101 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001102 if old_fs_size else None)
1103 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001104
1105 # Process and verify each operation.
1106 op_num = 0
1107 for op, op_name in common.OperationIter(operations, base_name):
1108 op_num += 1
1109
Gilad Arnoldcb638912013-06-24 04:57:11 -07001110 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001111 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001112 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001113 op_counts[op.type] += 1
1114
1115 is_last = op_num == len(operations)
1116 curr_data_used = self._CheckOperation(
1117 op, op_name, is_last, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001118 old_usable_size, new_usable_size,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001119 prev_data_offset + total_data_used, allow_signature,
1120 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001121 if curr_data_used:
1122 op_blob_totals[op.type] += curr_data_used
1123 total_data_used += curr_data_used
1124
1125 # Report totals and breakdown statistics.
1126 report.AddField('total operations', op_num)
1127 report.AddField(
1128 None,
1129 histogram.Histogram.FromCountDict(op_counts,
1130 key_names=common.OpType.NAMES),
1131 indent=1)
1132 report.AddField('total blobs', sum(blob_hash_counts.values()))
1133 report.AddField(None,
1134 histogram.Histogram.FromCountDict(blob_hash_counts),
1135 indent=1)
1136 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1137 report.AddField(
1138 None,
1139 histogram.Histogram.FromCountDict(op_blob_totals,
1140 formatter=_AddHumanReadableSize,
1141 key_names=common.OpType.NAMES),
1142 indent=1)
1143
1144 # Report read/write histograms.
1145 if old_block_counters:
1146 report.AddField('block read hist',
1147 histogram.Histogram.FromKeyList(old_block_counters),
1148 linebreak=True, indent=1)
1149
Gilad Arnold382df5c2013-05-03 12:49:28 -07001150 new_write_hist = histogram.Histogram.FromKeyList(
1151 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1152 report.AddField('block write hist', new_write_hist, linebreak=True,
1153 indent=1)
1154
Gilad Arnoldcb638912013-06-24 04:57:11 -07001155 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001156 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001157 raise error.PayloadError(
1158 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001159 base_name)
1160
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001161 return total_data_used
1162
1163 def _CheckSignatures(self, report, pubkey_file_name):
1164 """Checks a payload's signature block."""
1165 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1166 sigs = update_metadata_pb2.Signatures()
1167 sigs.ParseFromString(sigs_raw)
1168 report.AddSection('signatures')
1169
Gilad Arnoldcb638912013-06-24 04:57:11 -07001170 # Check: At least one signature present.
1171 # pylint cannot see through the protobuf object, it seems.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001172 # pylint: disable=E1101
1173 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001174 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001175
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001176 last_ops_section = (self.payload.manifest.kernel_install_operations or
1177 self.payload.manifest.install_operations)
1178 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001179 # Check: signatures_{offset,size} must match the last (fake) operation.
1180 if not (fake_sig_op.type == common.OpType.REPLACE and
1181 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001182 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001183 raise error.PayloadError(
1184 'Signatures_{offset,size} (%d+%d) does not match last operation '
1185 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001186 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1187 fake_sig_op.data_length))
1188
1189 # Compute the checksum of all data up to signature blob.
1190 # TODO(garnold) we're re-reading the whole data section into a string
1191 # just to compute the checksum; instead, we could do it incrementally as
1192 # we read the blobs one-by-one, under the assumption that we're reading
1193 # them in order (which currently holds). This should be reconsidered.
1194 payload_hasher = self.payload.manifest_hasher.copy()
1195 common.Read(self.payload.payload_file, self.sigs_offset,
1196 offset=self.payload.data_offset, hasher=payload_hasher)
1197
1198 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1199 sig_report = report.AddSubReport(sig_name)
1200
Gilad Arnoldcb638912013-06-24 04:57:11 -07001201 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001202 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1203 self._CheckMandatoryField(sig, 'data', None, sig_name)
1204 sig_report.AddField('data len', len(sig.data))
1205
Gilad Arnoldcb638912013-06-24 04:57:11 -07001206 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001207 if sig.version == 1:
1208 self._CheckSha256Signature(sig.data, pubkey_file_name,
1209 payload_hasher.digest(), sig_name)
1210 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001211 raise error.PayloadError('Unknown signature version (%d).' %
1212 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001213
1214 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001215 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001216 """Checker entry point, invoking all checks.
1217
1218 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001219 pubkey_file_name: Public key used for signature verification.
1220 metadata_sig_file: Metadata signature, if verification is desired.
Gilad Arnold06eea332015-07-13 18:06:33 -07001221 rootfs_part_size: The size of rootfs partitions in bytes (default: infer
1222 based on payload type and version).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001223 kernel_part_size: The size of kernel partitions in bytes (default: use
1224 reported filesystem size).
1225 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001226
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001227 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001228 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001229 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001230 if not pubkey_file_name:
1231 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1232
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001233 report = _PayloadReport()
1234
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001235 # Get payload file size.
1236 self.payload.payload_file.seek(0, 2)
1237 payload_file_size = self.payload.payload_file.tell()
1238 self.payload.ResetFile()
1239
1240 try:
1241 # Check metadata signature (if provided).
1242 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001243 metadata_sig = base64.b64decode(metadata_sig_file.read())
1244 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1245 self.payload.manifest_hasher.digest(),
1246 'metadata signature')
1247
Gilad Arnoldcb638912013-06-24 04:57:11 -07001248 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001249 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001250 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001251 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001252 raise error.PayloadError('Unknown payload version (%d).' %
1253 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001254 report.AddField('version', self.payload.header.version)
1255 report.AddField('manifest len', self.payload.header.manifest_len)
1256
Gilad Arnoldcb638912013-06-24 04:57:11 -07001257 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001258 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001259 assert self.payload_type, 'payload type should be known by now'
1260
Gilad Arnold06eea332015-07-13 18:06:33 -07001261 # Infer the usable partition size when validating rootfs operations:
1262 # - If rootfs partition size was provided, use that.
1263 # - Otherwise, if this is an older delta (minor version < 2), stick with
1264 # a known constant size. This is necessary because older deltas may
1265 # exceed the filesystem size when moving data blocks around.
1266 # - Otherwise, use the encoded filesystem size.
1267 new_rootfs_usable_size = self.new_rootfs_fs_size
Amin Hassaniae853742017-10-11 10:27:27 -07001268 old_rootfs_usable_size = self.old_rootfs_fs_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001269 if rootfs_part_size:
1270 new_rootfs_usable_size = rootfs_part_size
Amin Hassaniae853742017-10-11 10:27:27 -07001271 old_rootfs_usable_size = rootfs_part_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001272 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
1273 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Amin Hassaniae853742017-10-11 10:27:27 -07001274 old_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Gilad Arnold06eea332015-07-13 18:06:33 -07001275
Gilad Arnoldcb638912013-06-24 04:57:11 -07001276 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001277 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1278 # no explicit size provided *and* the partition size is not embedded in
1279 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001280 report.AddSection('rootfs operations')
1281 total_blob_size = self._CheckOperations(
1282 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001283 'install_operations', self.old_rootfs_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001284 self.new_rootfs_fs_size, old_rootfs_usable_size,
1285 new_rootfs_usable_size, 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001286
Gilad Arnoldcb638912013-06-24 04:57:11 -07001287 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001288 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001289 report.AddSection('kernel operations')
1290 total_blob_size += self._CheckOperations(
1291 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001292 'kernel_install_operations', self.old_kernel_fs_size,
1293 self.new_kernel_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001294 kernel_part_size if kernel_part_size else self.old_kernel_fs_size,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001295 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1296 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001297
Gilad Arnoldcb638912013-06-24 04:57:11 -07001298 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001299 used_payload_size = self.payload.data_offset + total_blob_size
1300 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001301 raise error.PayloadError(
1302 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001303 (used_payload_size, payload_file_size))
1304
Gilad Arnoldcb638912013-06-24 04:57:11 -07001305 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001306 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001307 self._CheckSignatures(report, pubkey_file_name)
1308
Gilad Arnoldcb638912013-06-24 04:57:11 -07001309 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001310 report.AddSection('summary')
1311 report.AddField('update type', self.payload_type)
1312
1313 report.Finalize()
1314 finally:
1315 if report_out_file:
1316 report.Dump(report_out_file)