blob: 6d17fbe57eb745c80b14c9ea20d62738c8dd2307 [file] [log] [blame]
Amin Hassanif94b6432018-01-26 17:39:47 -08001#
2# Copyright (C) 2013 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
Gilad Arnold553b0ec2013-01-26 01:00:39 -080016
17"""Verifying the integrity of a Chrome OS update payload.
18
19This module is used internally by the main Payload class for verifying the
20integrity of an update payload. The interface for invoking the checks is as
21follows:
22
23 checker = PayloadChecker(payload)
24 checker.Run(...)
Gilad Arnold553b0ec2013-01-26 01:00:39 -080025"""
26
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080027from __future__ import print_function
28
Gilad Arnold553b0ec2013-01-26 01:00:39 -080029import array
30import base64
Tudor Brindus8d05a7e2018-06-14 11:18:18 -070031import collections
Gilad Arnold553b0ec2013-01-26 01:00:39 -080032import hashlib
Gilad Arnoldcb638912013-06-24 04:57:11 -070033import itertools
Gilad Arnold9b90c932013-05-22 17:12:56 -070034import os
Gilad Arnold553b0ec2013-01-26 01:00:39 -080035import subprocess
36
Amin Hassanib05a65a2017-12-18 15:15:32 -080037from update_payload import common
38from update_payload import error
39from update_payload import format_utils
40from update_payload import histogram
41from update_payload import update_metadata_pb2
Gilad Arnold553b0ec2013-01-26 01:00:39 -080042
43
44#
Gilad Arnold9b90c932013-05-22 17:12:56 -070045# Constants.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080046#
Gilad Arnoldcb638912013-06-24 04:57:11 -070047
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070048_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents'
49_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
50_CHECK_PAYLOAD_SIG = 'payload-sig'
51CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070052 _CHECK_DST_PSEUDO_EXTENTS,
53 _CHECK_MOVE_SAME_SRC_DST_BLOCK,
54 _CHECK_PAYLOAD_SIG,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070055)
56
Gilad Arnold553b0ec2013-01-26 01:00:39 -080057_TYPE_FULL = 'full'
58_TYPE_DELTA = 'delta'
59
60_DEFAULT_BLOCK_SIZE = 4096
61
Gilad Arnold9b90c932013-05-22 17:12:56 -070062_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem'
63_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
64 _DEFAULT_PUBKEY_BASE_NAME)
65
Gilad Arnold0d575cd2015-07-13 17:29:21 -070066# Supported minor version map to payload types allowed to be using them.
67_SUPPORTED_MINOR_VERSIONS = {
68 0: (_TYPE_FULL,),
69 1: (_TYPE_DELTA,),
70 2: (_TYPE_DELTA,),
Sen Jiang912c4df2015-12-10 12:17:13 -080071 3: (_TYPE_DELTA,),
Sen Jiang92161a72016-06-28 16:09:38 -070072 4: (_TYPE_DELTA,),
Amin Hassani77d7cbc2018-02-07 16:21:33 -080073 5: (_TYPE_DELTA,),
Amin Hassanief1af272019-01-10 14:04:27 -080074 6: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070075}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080076
Gilad Arnold06eea332015-07-13 18:06:33 -070077_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
78
Gilad Arnold553b0ec2013-01-26 01:00:39 -080079#
80# Helper functions.
81#
Gilad Arnoldcb638912013-06-24 04:57:11 -070082
Gilad Arnold553b0ec2013-01-26 01:00:39 -080083def _IsPowerOfTwo(val):
84 """Returns True iff val is a power of two."""
85 return val > 0 and (val & (val - 1)) == 0
86
87
88def _AddFormat(format_func, value):
89 """Adds a custom formatted representation to ordinary string representation.
90
91 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070092 format_func: A value formatter.
93 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080094
Gilad Arnold553b0ec2013-01-26 01:00:39 -080095 Returns:
96 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080097 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070098 ret = str(value)
99 formatted_str = format_func(value)
100 if formatted_str:
101 ret += ' (%s)' % formatted_str
102 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800103
104
105def _AddHumanReadableSize(size):
106 """Adds a human readable representation to a byte size value."""
107 return _AddFormat(format_utils.BytesToHumanReadable, size)
108
109
110#
111# Payload report generator.
112#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700113
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800114class _PayloadReport(object):
115 """A payload report generator.
116
117 A report is essentially a sequence of nodes, which represent data points. It
118 is initialized to have a "global", untitled section. A node may be a
119 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800120 """
121
Gilad Arnoldcb638912013-06-24 04:57:11 -0700122 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800123 class Node(object):
124 """A report node interface."""
125
126 @staticmethod
127 def _Indent(indent, line):
128 """Indents a line by a given indentation amount.
129
130 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700131 indent: The indentation amount.
132 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800133
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800134 Returns:
135 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800136 """
137 return '%*s%s' % (indent, '', line)
138
139 def GenerateLines(self, base_indent, sub_indent, curr_section):
140 """Generates the report lines for this node.
141
142 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700143 base_indent: Base indentation for each line.
144 sub_indent: Additional indentation for sub-nodes.
145 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800146
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800147 Returns:
148 A pair consisting of a list of properly indented report lines and a new
149 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800150 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700151 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800152
153 class FieldNode(Node):
154 """A field report node, representing a (name, value) pair."""
155
156 def __init__(self, name, value, linebreak, indent):
157 super(_PayloadReport.FieldNode, self).__init__()
158 self.name = name
159 self.value = value
160 self.linebreak = linebreak
161 self.indent = indent
162
163 def GenerateLines(self, base_indent, sub_indent, curr_section):
164 """Generates a properly formatted 'name : value' entry."""
165 report_output = ''
166 if self.name:
167 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
168 value_lines = str(self.value).splitlines()
169 if self.linebreak and self.name:
170 report_output += '\n' + '\n'.join(
171 ['%*s%s' % (self.indent, '', line) for line in value_lines])
172 else:
173 if self.name:
174 report_output += ' '
175 report_output += '%*s' % (self.indent, '')
176 cont_line_indent = len(report_output)
177 indented_value_lines = [value_lines[0]]
178 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
179 for line in value_lines[1:]])
180 report_output += '\n'.join(indented_value_lines)
181
182 report_lines = [self._Indent(base_indent, line + '\n')
183 for line in report_output.split('\n')]
184 return report_lines, curr_section
185
186 class SubReportNode(Node):
187 """A sub-report node, representing a nested report."""
188
189 def __init__(self, title, report):
190 super(_PayloadReport.SubReportNode, self).__init__()
191 self.title = title
192 self.report = report
193
194 def GenerateLines(self, base_indent, sub_indent, curr_section):
195 """Recurse with indentation."""
196 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
197 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
198 sub_indent))
199 return report_lines, curr_section
200
201 class SectionNode(Node):
202 """A section header node."""
203
204 def __init__(self, title=None):
205 super(_PayloadReport.SectionNode, self).__init__()
206 self.title = title
207 self.max_field_name_len = 0
208
209 def GenerateLines(self, base_indent, sub_indent, curr_section):
210 """Dump a title line, return self as the (new) current section."""
211 report_lines = []
212 if self.title:
213 report_lines.append(self._Indent(base_indent,
214 '=== %s ===\n' % self.title))
215 return report_lines, self
216
217 def __init__(self):
218 self.report = []
219 self.last_section = self.global_section = self.SectionNode()
220 self.is_finalized = False
221
222 def GenerateLines(self, base_indent, sub_indent):
223 """Generates the lines in the report, properly indented.
224
225 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700226 base_indent: The indentation used for root-level report lines.
227 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800228
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800229 Returns:
230 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800231 """
232 report_lines = []
233 curr_section = self.global_section
234 for node in self.report:
235 node_report_lines, curr_section = node.GenerateLines(
236 base_indent, sub_indent, curr_section)
237 report_lines.extend(node_report_lines)
238
239 return report_lines
240
241 def Dump(self, out_file, base_indent=0, sub_indent=2):
242 """Dumps the report to a file.
243
244 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700245 out_file: File object to output the content to.
246 base_indent: Base indentation for report lines.
247 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800248 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800249 report_lines = self.GenerateLines(base_indent, sub_indent)
250 if report_lines and not self.is_finalized:
251 report_lines.append('(incomplete report)\n')
252
253 for line in report_lines:
254 out_file.write(line)
255
256 def AddField(self, name, value, linebreak=False, indent=0):
257 """Adds a field/value pair to the payload report.
258
259 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700260 name: The field's name.
261 value: The field's value.
262 linebreak: Whether the value should be printed on a new line.
263 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800264 """
265 assert not self.is_finalized
266 if name and self.last_section.max_field_name_len < len(name):
267 self.last_section.max_field_name_len = len(name)
268 self.report.append(self.FieldNode(name, value, linebreak, indent))
269
270 def AddSubReport(self, title):
271 """Adds and returns a sub-report with a title."""
272 assert not self.is_finalized
273 sub_report = self.SubReportNode(title, type(self)())
274 self.report.append(sub_report)
275 return sub_report.report
276
277 def AddSection(self, title):
278 """Adds a new section title."""
279 assert not self.is_finalized
280 self.last_section = self.SectionNode(title)
281 self.report.append(self.last_section)
282
283 def Finalize(self):
284 """Seals the report, marking it as complete."""
285 self.is_finalized = True
286
287
288#
289# Payload verification.
290#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700291
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800292class PayloadChecker(object):
293 """Checking the integrity of an update payload.
294
295 This is a short-lived object whose purpose is to isolate the logic used for
296 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800297 """
298
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700299 def __init__(self, payload, assert_type=None, block_size=0,
300 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700301 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700302
303 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700304 payload: The payload object to check.
305 assert_type: Assert that payload is either 'full' or 'delta' (optional).
306 block_size: Expected filesystem / payload block size (optional).
307 allow_unhashed: Allow operations with unhashed data blobs.
308 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700309 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700310 if not payload.is_init:
311 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700312
313 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800314 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700315 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
316 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700317 raise error.PayloadError(
318 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700319 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700320 raise error.PayloadError('Invalid assert_type value (%r).' %
321 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700322 self.payload_type = assert_type
323 self.allow_unhashed = allow_unhashed
324
325 # Disable specific tests.
326 self.check_dst_pseudo_extents = (
327 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
328 self.check_move_same_src_dst_block = (
329 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
330 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800331
332 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800333 self.sigs_offset = 0
334 self.sigs_size = 0
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700335 self.old_part_info = {}
336 self.new_part_info = {}
337 self.new_fs_sizes = collections.defaultdict(int)
338 self.old_fs_sizes = collections.defaultdict(int)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700339 self.minor_version = None
Tudor Brindus40506cd2018-06-18 20:18:17 -0700340 self.major_version = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800341
342 @staticmethod
343 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
344 msg_name=None, linebreak=False, indent=0):
345 """Adds an element from a protobuf message to the payload report.
346
347 Checks to see whether a message contains a given element, and if so adds
348 the element value to the provided report. A missing mandatory element
349 causes an exception to be raised.
350
351 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700352 msg: The message containing the element.
353 name: The name of the element.
354 report: A report object to add the element name/value to.
355 is_mandatory: Whether or not this element must be present.
356 is_submsg: Whether this element is itself a message.
357 convert: A function for converting the element value for reporting.
358 msg_name: The name of the message object (for error reporting).
359 linebreak: Whether the value report should induce a line break.
360 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800361
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800362 Returns:
363 A pair consisting of the element value and the generated sub-report for
364 it (if the element is a sub-message, None otherwise). If the element is
365 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800366
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800367 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700368 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800369 """
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700370 element_result = collections.namedtuple('element_result', ['msg', 'report'])
371
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800372 if not msg.HasField(name):
373 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700374 raise error.PayloadError('%smissing mandatory %s %r.' %
375 (msg_name + ' ' if msg_name else '',
376 'sub-message' if is_submsg else 'field',
377 name))
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700378 return element_result(None, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800379
380 value = getattr(msg, name)
381 if is_submsg:
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700382 return element_result(value, report and report.AddSubReport(name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800383 else:
384 if report:
385 report.AddField(name, convert(value), linebreak=linebreak,
386 indent=indent)
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700387 return element_result(value, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800388
389 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700390 def _CheckRepeatedElemNotPresent(msg, field_name, msg_name):
391 """Checks that a repeated element is not specified in the message.
392
393 Args:
394 msg: The message containing the element.
395 field_name: The name of the element.
396 msg_name: The name of the message object (for error reporting).
397
398 Raises:
399 error.PayloadError if the repeated element is present or non-empty.
400 """
401 if getattr(msg, field_name, None):
402 raise error.PayloadError('%sfield %r not empty.' %
403 (msg_name + ' ' if msg_name else '', field_name))
404
405 @staticmethod
406 def _CheckElemNotPresent(msg, field_name, msg_name):
407 """Checks that an element is not specified in the message.
408
409 Args:
410 msg: The message containing the element.
411 field_name: The name of the element.
412 msg_name: The name of the message object (for error reporting).
413
414 Raises:
415 error.PayloadError if the repeated element is present.
416 """
417 if msg.HasField(field_name):
418 raise error.PayloadError('%sfield %r exists.' %
419 (msg_name + ' ' if msg_name else '', field_name))
420
421 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800422 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
423 linebreak=False, indent=0):
424 """Adds a mandatory field; returning first component from _CheckElem."""
425 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
426 convert=convert, msg_name=msg_name,
427 linebreak=linebreak, indent=indent)[0]
428
429 @staticmethod
430 def _CheckOptionalField(msg, field_name, report, convert=str,
431 linebreak=False, indent=0):
432 """Adds an optional field; returning first component from _CheckElem."""
433 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
434 convert=convert, linebreak=linebreak,
435 indent=indent)[0]
436
437 @staticmethod
438 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
439 """Adds a mandatory sub-message; wrapper for _CheckElem."""
440 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
441 msg_name)
442
443 @staticmethod
444 def _CheckOptionalSubMsg(msg, submsg_name, report):
445 """Adds an optional sub-message; wrapper for _CheckElem."""
446 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
447
448 @staticmethod
449 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
450 """Checks that val1 is None iff val2 is None.
451
452 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700453 val1: first value to be compared.
454 val2: second value to be compared.
455 name1: name of object holding the first value.
456 name2: name of object holding the second value.
457 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800458
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800459 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700460 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800461 """
462 if None in (val1, val2) and val1 is not val2:
463 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700464 raise error.PayloadError('%r present without %r%s.' %
465 (present, missing,
466 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800467
468 @staticmethod
Tudor Brindus40506cd2018-06-18 20:18:17 -0700469 def _CheckPresentIffMany(vals, name, obj_name):
470 """Checks that a set of vals and names imply every other element.
471
472 Args:
473 vals: The set of values to be compared.
474 name: The name of the objects holding the corresponding value.
475 obj_name: Name of the object containing these values.
476
477 Raises:
478 error.PayloadError if assertion does not hold.
479 """
480 if any(vals) and not all(vals):
481 raise error.PayloadError('%r is not present in all values%s.' %
482 (name, ' in ' + obj_name if obj_name else ''))
483
484 @staticmethod
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800485 def _Run(cmd, send_data=None):
486 """Runs a subprocess, returns its output.
487
488 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700489 cmd: Sequence of command-line argument for invoking the subprocess.
490 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800491
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800492 Returns:
493 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800494 """
495 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
496 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700497 try:
498 result = run_process.communicate(input=send_data)
499 finally:
500 exit_code = run_process.wait()
501
502 if exit_code:
503 raise RuntimeError('Subprocess %r failed with code %r.' %
504 (cmd, exit_code))
505
506 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800507
508 @staticmethod
509 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
510 """Verifies an actual hash against a signed one.
511
512 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700513 sig_data: The raw signature data.
514 pubkey_file_name: Public key used for verifying signature.
515 actual_hash: The actual hash digest.
516 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800517
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800518 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700519 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800520 """
521 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700522 raise error.PayloadError(
523 '%s: signature size (%d) not as expected (256).' %
524 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800525 signed_data, _ = PayloadChecker._Run(
526 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
527 send_data=sig_data)
528
Gilad Arnold5502b562013-03-08 13:22:31 -0800529 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700530 raise error.PayloadError('%s: unexpected signed data length (%d).' %
531 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800532
Gilad Arnold5502b562013-03-08 13:22:31 -0800533 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700534 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
535 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800536
Gilad Arnold5502b562013-03-08 13:22:31 -0800537 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800538 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700539 raise error.PayloadError(
540 '%s: signed hash (%s) different from actual (%s).' %
541 (sig_name, common.FormatSha256(signed_hash),
542 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800543
544 @staticmethod
545 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
546 block_name=None):
547 """Checks that a given length fits given block space.
548
549 This ensures that the number of blocks allocated is appropriate for the
550 length of the data residing in these blocks.
551
552 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700553 length: The actual length of the data.
554 num_blocks: The number of blocks allocated for it.
555 block_size: The size of each block in bytes.
556 length_name: Name of length (used for error reporting).
557 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800558
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800559 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700560 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800561 """
562 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700563 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700564 raise error.PayloadError(
565 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800566 (length_name, length, block_name or '', num_blocks, block_size))
567
568 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700569 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700570 raise error.PayloadError(
571 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800572 (length_name, length, block_name or '', num_blocks - 1, block_size))
573
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700574 def _CheckManifestMinorVersion(self, report):
575 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800576
577 Args:
578 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800579
580 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700581 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800582 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700583 self.minor_version = self._CheckOptionalField(self.payload.manifest,
584 'minor_version', report)
585 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
586 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800587 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700588 'Minor version %d not compatible with payload type %s.' %
589 (self.minor_version, self.payload_type))
590 elif self.minor_version is None:
591 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800592 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700593 raise error.PayloadError('Unsupported minor version: %d' %
594 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800595
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700596 def _CheckManifest(self, report, part_sizes=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800597 """Checks the payload manifest.
598
599 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700600 report: A report object to add to.
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700601 part_sizes: Map of partition label to partition size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800602
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800603 Returns:
604 A tuple consisting of the partition block size used during the update
605 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800606
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800607 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700608 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800609 """
Tudor Brindus40506cd2018-06-18 20:18:17 -0700610 self.major_version = self.payload.header.version
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700611
Tudor Brindus40506cd2018-06-18 20:18:17 -0700612 part_sizes = collections.defaultdict(int, part_sizes)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800613 manifest = self.payload.manifest
614 report.AddSection('manifest')
615
616 # Check: block_size must exist and match the expected value.
617 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
618 report, 'manifest')
619 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700620 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
621 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800622
623 # Check: signatures_offset <==> signatures_size.
624 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
625 report)
626 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
627 report)
628 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
629 'signatures_offset', 'signatures_size', 'manifest')
630
Amin Hassani8ea19572018-12-05 12:06:21 -0800631 if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
Tudor Brindusb220d662018-07-10 23:55:51 -0700632 for real_name, proto_name in common.CROS_PARTITIONS:
633 self.old_part_info[real_name] = self._CheckOptionalSubMsg(
634 manifest, 'old_%s_info' % proto_name, report)
635 self.new_part_info[real_name] = self._CheckMandatorySubMsg(
636 manifest, 'new_%s_info' % proto_name, report, 'manifest')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700637
Tudor Brindus40506cd2018-06-18 20:18:17 -0700638 # Check: old_kernel_info <==> old_rootfs_info.
639 self._CheckPresentIff(self.old_part_info[common.KERNEL].msg,
640 self.old_part_info[common.ROOTFS].msg,
641 'old_kernel_info', 'old_rootfs_info', 'manifest')
642 else:
643 for part in manifest.partitions:
644 name = part.partition_name
645 self.old_part_info[name] = self._CheckOptionalSubMsg(
646 part, 'old_partition_info', report)
647 self.new_part_info[name] = self._CheckMandatorySubMsg(
648 part, 'new_partition_info', report, 'manifest.partitions')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700649
Tudor Brindus40506cd2018-06-18 20:18:17 -0700650 # Check: Old-style partition infos should not be specified.
Tudor Brindusb220d662018-07-10 23:55:51 -0700651 for _, part in common.CROS_PARTITIONS:
Tudor Brindus40506cd2018-06-18 20:18:17 -0700652 self._CheckElemNotPresent(manifest, 'old_%s_info' % part, 'manifest')
653 self._CheckElemNotPresent(manifest, 'new_%s_info' % part, 'manifest')
654
655 # Check: If old_partition_info is specified anywhere, it must be
656 # specified everywhere.
657 old_part_msgs = [part.msg for part in self.old_part_info.values() if part]
658 self._CheckPresentIffMany(old_part_msgs, 'old_partition_info',
659 'manifest.partitions')
660
661 is_delta = any(part and part.msg for part in self.old_part_info.values())
662 if is_delta:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800663 # Assert/mark delta payload.
664 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700665 raise error.PayloadError(
666 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800667 self.payload_type = _TYPE_DELTA
668
Tudor Brindus40506cd2018-06-18 20:18:17 -0700669 for part, (msg, part_report) in self.old_part_info.iteritems():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700670 # Check: {size, hash} present in old_{kernel,rootfs}_info.
671 field = 'old_%s_info' % part
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700672 self.old_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
Tudor Brindus40506cd2018-06-18 20:18:17 -0700673 part_report, field)
674 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700675 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700676
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700677 # Check: old_{kernel,rootfs} size must fit in respective partition.
678 if self.old_fs_sizes[part] > part_sizes[part] > 0:
679 raise error.PayloadError(
680 'Old %s content (%d) exceed partition size (%d).' %
681 (part, self.old_fs_sizes[part], part_sizes[part]))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800682 else:
683 # Assert/mark full payload.
684 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700685 raise error.PayloadError(
686 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800687 self.payload_type = _TYPE_FULL
688
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700689 # Check: new_{kernel,rootfs}_info present; contains {size, hash}.
Tudor Brindus40506cd2018-06-18 20:18:17 -0700690 for part, (msg, part_report) in self.new_part_info.iteritems():
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700691 field = 'new_%s_info' % part
Tudor Brindus40506cd2018-06-18 20:18:17 -0700692 self.new_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
693 part_report, field)
694 self._CheckMandatoryField(msg, 'hash', part_report, field,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700695 convert=common.FormatSha256)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800696
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700697 # Check: new_{kernel,rootfs} size must fit in respective partition.
698 if self.new_fs_sizes[part] > part_sizes[part] > 0:
699 raise error.PayloadError(
700 'New %s content (%d) exceed partition size (%d).' %
701 (part, self.new_fs_sizes[part], part_sizes[part]))
Gilad Arnold382df5c2013-05-03 12:49:28 -0700702
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800703 # Check: minor_version makes sense for the payload type. This check should
704 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700705 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800706
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800707 def _CheckLength(self, length, total_blocks, op_name, length_name):
708 """Checks whether a length matches the space designated in extents.
709
710 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700711 length: The total length of the data.
712 total_blocks: The total number of blocks in extents.
713 op_name: Operation name (for error reporting).
714 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800715
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800716 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700717 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800718 """
719 # Check: length is non-zero.
720 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700721 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800722
723 # Check that length matches number of blocks.
724 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
725 '%s: %s' % (op_name, length_name))
726
Gilad Arnold382df5c2013-05-03 12:49:28 -0700727 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800728 allow_pseudo=False, allow_signature=False):
729 """Checks a sequence of extents.
730
731 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700732 extents: The sequence of extents to check.
733 usable_size: The usable size of the partition to which the extents apply.
734 block_counters: Array of counters corresponding to the number of blocks.
735 name: The name of the extent block.
736 allow_pseudo: Whether or not pseudo block numbers are allowed.
737 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800738
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800739 Returns:
740 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800741
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800742 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700743 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800744 """
745 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800746 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700747 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800748 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
749 None, ex_name)
750 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
751 ex_name)
752 end_block = start_block + num_blocks
753
754 # Check: num_blocks > 0.
755 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700756 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800757
758 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700759 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700760 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700761 raise error.PayloadError(
762 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700763 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800764
765 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700766 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800767 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800768 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
769 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
770 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700771 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800772
773 total_num_blocks += num_blocks
774
775 return total_num_blocks
776
777 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0de7f782017-12-07 12:13:03 -0800778 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800779
780 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700781 op: The operation object from the manifest.
782 data_length: The length of the data blob associated with the operation.
783 total_dst_blocks: Total number of blocks in dst_extents.
784 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800785
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800786 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700787 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800788 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700789 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800790 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700791 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800792
Gilad Arnoldcb638912013-06-24 04:57:11 -0700793 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800794 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700795 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800796
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800797 if op.type == common.OpType.REPLACE:
798 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
799 self.block_size,
800 op_name + '.data_length', 'dst')
801 else:
Sen Jiang771f6482018-04-04 17:59:10 -0700802 # Check: data_length must be smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800803 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700804 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800805 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700806 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800807 (op_name, data_length, total_dst_blocks, self.block_size))
808
809 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
810 total_dst_blocks, op_name):
811 """Specific checks for MOVE operations.
812
813 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700814 op: The operation object from the manifest.
815 data_offset: The offset of a data blob for the operation.
816 total_src_blocks: Total number of blocks in src_extents.
817 total_dst_blocks: Total number of blocks in dst_extents.
818 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800819
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800820 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700821 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800822 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700823 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800824 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700825 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800826
Gilad Arnoldcb638912013-06-24 04:57:11 -0700827 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800828 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700829 raise error.PayloadError(
830 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800831 (op_name, total_src_blocks, total_dst_blocks))
832
Gilad Arnoldcb638912013-06-24 04:57:11 -0700833 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800834 i = 0
835 src_extent_iter = iter(op.src_extents)
836 dst_extent_iter = iter(op.dst_extents)
837 src_extent = dst_extent = None
838 src_idx = src_num = dst_idx = dst_num = 0
839 while i < total_src_blocks:
840 # Get the next source extent, if needed.
841 if not src_extent:
842 try:
843 src_extent = src_extent_iter.next()
844 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700845 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
846 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800847 src_idx = src_extent.start_block
848 src_num = src_extent.num_blocks
849
850 # Get the next dest extent, if needed.
851 if not dst_extent:
852 try:
853 dst_extent = dst_extent_iter.next()
854 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700855 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
856 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800857 dst_idx = dst_extent.start_block
858 dst_num = dst_extent.num_blocks
859
Allie Woodb065e132015-04-24 10:20:27 -0700860 # Check: start block is not 0. See crbug/480751; there are still versions
861 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
862 # so we need to fail payloads that try to MOVE to/from block 0.
863 if src_idx == 0 or dst_idx == 0:
864 raise error.PayloadError(
865 '%s: MOVE operation cannot have extent with start block 0' %
866 op_name)
867
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700868 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700869 raise error.PayloadError(
870 '%s: src/dst block number %d is the same (%d).' %
871 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800872
873 advance = min(src_num, dst_num)
874 i += advance
875
876 src_idx += advance
877 src_num -= advance
878 if src_num == 0:
879 src_extent = None
880
881 dst_idx += advance
882 dst_num -= advance
883 if dst_num == 0:
884 dst_extent = None
885
Gilad Arnold5502b562013-03-08 13:22:31 -0800886 # Make sure we've exhausted all src/dst extents.
887 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700888 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800889 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700890 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800891
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700892 def _CheckZeroOperation(self, op, op_name):
893 """Specific checks for ZERO operations.
894
895 Args:
896 op: The operation object from the manifest.
897 op_name: Operation name for error reporting.
898
899 Raises:
900 error.PayloadError if any check fails.
901 """
902 # Check: Does not contain src extents, data_length and data_offset.
903 if op.src_extents:
904 raise error.PayloadError('%s: contains src_extents.' % op_name)
905 if op.data_length:
906 raise error.PayloadError('%s: contains data_length.' % op_name)
907 if op.data_offset:
908 raise error.PayloadError('%s: contains data_offset.' % op_name)
909
Amin Hassaniefa62d92017-11-09 13:46:56 -0800910 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
911 """Specific checks for BSDIFF, SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
912 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800913
914 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800915 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700916 data_length: The length of the data blob associated with the operation.
917 total_dst_blocks: Total number of blocks in dst_extents.
918 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800919
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800920 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700921 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800922 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800923 # Check: data_{offset,length} present.
924 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700925 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800926
Sen Jiang771f6482018-04-04 17:59:10 -0700927 # Check: data_length is strictly smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800928 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700929 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800930 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700931 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800932 (op_name, data_length, total_dst_blocks, self.block_size,
933 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800934
Amin Hassaniefa62d92017-11-09 13:46:56 -0800935 # Check the existence of src_length and dst_length for legacy bsdiffs.
936 if (op.type == common.OpType.BSDIFF or
937 (op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3)):
938 if not op.HasField('src_length') or not op.HasField('dst_length'):
939 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
940 else:
941 if op.HasField('src_length') or op.HasField('dst_length'):
942 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
943
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800944 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
945 total_dst_blocks, op_name):
946 """Specific checks for SOURCE_COPY.
947
948 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800949 data_offset: The offset of a data blob for the operation.
950 total_src_blocks: Total number of blocks in src_extents.
951 total_dst_blocks: Total number of blocks in dst_extents.
952 op_name: Operation name for error reporting.
953
954 Raises:
955 error.PayloadError if any check fails.
956 """
957 # Check: No data_{offset,length}.
958 if data_offset is not None:
959 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
960
961 # Check: total_src_blocks == total_dst_blocks.
962 if total_src_blocks != total_dst_blocks:
963 raise error.PayloadError(
964 '%s: total src blocks (%d) != total dst blocks (%d).' %
965 (op_name, total_src_blocks, total_dst_blocks))
966
Sen Jiangd6122bb2015-12-11 10:27:04 -0800967 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800968 """Specific checks for SOURCE_* operations.
969
970 Args:
971 op: The operation object from the manifest.
972 total_src_blocks: Total number of blocks in src_extents.
973 op_name: Operation name for error reporting.
974
975 Raises:
976 error.PayloadError if any check fails.
977 """
978 # Check: total_src_blocks != 0.
979 if total_src_blocks == 0:
980 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
981
Sen Jiang92161a72016-06-28 16:09:38 -0700982 # Check: src_sha256_hash present in minor version >= 3.
983 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800984 raise error.PayloadError('%s: source hash missing.' % op_name)
985
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800986 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700987 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700988 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800989 """Checks a single update operation.
990
991 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700992 op: The operation object.
993 op_name: Operation name string for error reporting.
994 is_last: Whether this is the last operation in the sequence.
995 old_block_counters: Arrays of block read counters.
996 new_block_counters: Arrays of block write counters.
997 old_usable_size: The overall usable size for src data in bytes.
998 new_usable_size: The overall usable size for dst data in bytes.
999 prev_data_offset: Offset of last used data bytes.
1000 allow_signature: Whether this may be a signature operation.
1001 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001002
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001003 Returns:
1004 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001005
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001006 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001007 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001008 """
1009 # Check extents.
1010 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -07001011 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001012 op_name + '.src_extents', allow_pseudo=True)
1013 allow_signature_in_extents = (allow_signature and is_last and
1014 op.type == common.OpType.REPLACE)
1015 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -07001016 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001017 op_name + '.dst_extents',
1018 allow_pseudo=(not self.check_dst_pseudo_extents),
1019 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001020
1021 # Check: data_offset present <==> data_length present.
1022 data_offset = self._CheckOptionalField(op, 'data_offset', None)
1023 data_length = self._CheckOptionalField(op, 'data_length', None)
1024 self._CheckPresentIff(data_offset, data_length, 'data_offset',
1025 'data_length', op_name)
1026
Gilad Arnoldcb638912013-06-24 04:57:11 -07001027 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001028 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001029 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001030
1031 # Check {src,dst}_length, if present.
1032 if op.HasField('src_length'):
1033 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
1034 if op.HasField('dst_length'):
1035 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
1036
1037 if op.HasField('data_sha256_hash'):
1038 blob_hash_counts['hashed'] += 1
1039
Gilad Arnoldcb638912013-06-24 04:57:11 -07001040 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001041 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001042 raise error.PayloadError(
1043 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001044 op_name)
1045
Gilad Arnoldcb638912013-06-24 04:57:11 -07001046 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001047 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
1048 data_length))
1049 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001050 raise error.PayloadError(
1051 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -07001052 (op_name, common.FormatSha256(op.data_sha256_hash),
1053 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001054 elif data_offset is not None:
1055 if allow_signature_in_extents:
1056 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001057 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001058 blob_hash_counts['unhashed'] += 1
1059 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001060 raise error.PayloadError('%s: unhashed operation not allowed.' %
1061 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001062
1063 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001064 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001065 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001066 raise error.PayloadError(
1067 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001068 (op_name, data_offset, prev_data_offset))
1069
1070 # Type-specific checks.
1071 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
1072 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani8ea19572018-12-05 12:06:21 -08001073 elif (op.type == common.OpType.REPLACE_XZ and
1074 (self.minor_version >= 3 or
1075 self.major_version >= common.BRILLO_MAJOR_PAYLOAD_VERSION)):
Amin Hassani0de7f782017-12-07 12:13:03 -08001076 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001077 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001078 self._CheckMoveOperation(op, data_offset, total_src_blocks,
1079 total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001080 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
1081 self._CheckZeroOperation(op, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001082 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001083 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001084 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001085 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
1086 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001087 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001088 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001089 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001090 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani77d7cbc2018-02-07 16:21:33 -08001091 elif op.type == common.OpType.BROTLI_BSDIFF and self.minor_version >= 4:
1092 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
1093 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
1094 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 5:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001095 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001096 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001097 else:
Allie Wood7cf9f132015-02-26 14:28:19 -08001098 raise error.PayloadError(
1099 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001100 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001101 return data_length if data_length is not None else 0
1102
Gilad Arnold382df5c2013-05-03 12:49:28 -07001103 def _SizeToNumBlocks(self, size):
1104 """Returns the number of blocks needed to contain a given byte size."""
1105 return (size + self.block_size - 1) / self.block_size
1106
1107 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001108 """Returns a freshly initialized array of block counters.
1109
Gilad Arnoldcb638912013-06-24 04:57:11 -07001110 Note that the generated array is not portable as is due to byte-ordering
1111 issues, hence it should not be serialized.
1112
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001113 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001114 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001115
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001116 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001117 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001118 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001119 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001120 return array.array('H',
1121 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001122
Gilad Arnold382df5c2013-05-03 12:49:28 -07001123 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001124 new_fs_size, old_usable_size, new_usable_size,
1125 prev_data_offset, allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001126 """Checks a sequence of update operations.
1127
1128 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001129 operations: The sequence of operations to check.
1130 report: The report object to add to.
1131 base_name: The name of the operation block.
1132 old_fs_size: The old filesystem size in bytes.
1133 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001134 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001135 new_usable_size: The overall usable size of the new partition in bytes.
1136 prev_data_offset: Offset of last used data bytes.
1137 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001138
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001139 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001140 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001141
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001142 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001143 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001144 """
1145 # The total size of data blobs used by operations scanned thus far.
1146 total_data_used = 0
1147 # Counts of specific operation types.
1148 op_counts = {
1149 common.OpType.REPLACE: 0,
1150 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001151 common.OpType.REPLACE_XZ: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001152 common.OpType.MOVE: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001153 common.OpType.ZERO: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001154 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001155 common.OpType.SOURCE_COPY: 0,
1156 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001157 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001158 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001159 }
1160 # Total blob sizes for each operation type.
1161 op_blob_totals = {
1162 common.OpType.REPLACE: 0,
1163 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001164 common.OpType.REPLACE_XZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001165 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001166 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001167 # SOURCE_COPY operations don't have blobs.
1168 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001169 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001170 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001171 }
1172 # Counts of hashed vs unhashed operations.
1173 blob_hash_counts = {
1174 'hashed': 0,
1175 'unhashed': 0,
1176 }
1177 if allow_signature:
1178 blob_hash_counts['signature'] = 0
1179
1180 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001181 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001182 if old_fs_size else None)
1183 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001184
1185 # Process and verify each operation.
1186 op_num = 0
1187 for op, op_name in common.OperationIter(operations, base_name):
1188 op_num += 1
1189
Gilad Arnoldcb638912013-06-24 04:57:11 -07001190 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001191 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001192 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001193 op_counts[op.type] += 1
1194
1195 is_last = op_num == len(operations)
1196 curr_data_used = self._CheckOperation(
1197 op, op_name, is_last, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001198 old_usable_size, new_usable_size,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001199 prev_data_offset + total_data_used, allow_signature,
1200 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001201 if curr_data_used:
1202 op_blob_totals[op.type] += curr_data_used
1203 total_data_used += curr_data_used
1204
1205 # Report totals and breakdown statistics.
1206 report.AddField('total operations', op_num)
1207 report.AddField(
1208 None,
1209 histogram.Histogram.FromCountDict(op_counts,
1210 key_names=common.OpType.NAMES),
1211 indent=1)
1212 report.AddField('total blobs', sum(blob_hash_counts.values()))
1213 report.AddField(None,
1214 histogram.Histogram.FromCountDict(blob_hash_counts),
1215 indent=1)
1216 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1217 report.AddField(
1218 None,
1219 histogram.Histogram.FromCountDict(op_blob_totals,
1220 formatter=_AddHumanReadableSize,
1221 key_names=common.OpType.NAMES),
1222 indent=1)
1223
1224 # Report read/write histograms.
1225 if old_block_counters:
1226 report.AddField('block read hist',
1227 histogram.Histogram.FromKeyList(old_block_counters),
1228 linebreak=True, indent=1)
1229
Gilad Arnold382df5c2013-05-03 12:49:28 -07001230 new_write_hist = histogram.Histogram.FromKeyList(
1231 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1232 report.AddField('block write hist', new_write_hist, linebreak=True,
1233 indent=1)
1234
Gilad Arnoldcb638912013-06-24 04:57:11 -07001235 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001236 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001237 raise error.PayloadError(
1238 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001239 base_name)
1240
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001241 return total_data_used
1242
1243 def _CheckSignatures(self, report, pubkey_file_name):
1244 """Checks a payload's signature block."""
1245 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1246 sigs = update_metadata_pb2.Signatures()
1247 sigs.ParseFromString(sigs_raw)
1248 report.AddSection('signatures')
1249
Gilad Arnoldcb638912013-06-24 04:57:11 -07001250 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001251 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001252 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001253
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001254 last_ops_section = (self.payload.manifest.kernel_install_operations or
1255 self.payload.manifest.install_operations)
Amin Hassani8ea19572018-12-05 12:06:21 -08001256
1257 # Only major version 1 has the fake signature OP at the end.
1258 if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
1259 fake_sig_op = last_ops_section[-1]
1260 # Check: signatures_{offset,size} must match the last (fake) operation.
1261 if not (fake_sig_op.type == common.OpType.REPLACE and
1262 self.sigs_offset == fake_sig_op.data_offset and
1263 self.sigs_size == fake_sig_op.data_length):
1264 raise error.PayloadError('Signatures_{offset,size} (%d+%d) does not'
1265 ' match last operation (%d+%d).' %
1266 (self.sigs_offset, self.sigs_size,
1267 fake_sig_op.data_offset,
1268 fake_sig_op.data_length))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001269
1270 # Compute the checksum of all data up to signature blob.
1271 # TODO(garnold) we're re-reading the whole data section into a string
1272 # just to compute the checksum; instead, we could do it incrementally as
1273 # we read the blobs one-by-one, under the assumption that we're reading
1274 # them in order (which currently holds). This should be reconsidered.
1275 payload_hasher = self.payload.manifest_hasher.copy()
1276 common.Read(self.payload.payload_file, self.sigs_offset,
1277 offset=self.payload.data_offset, hasher=payload_hasher)
1278
1279 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1280 sig_report = report.AddSubReport(sig_name)
1281
Gilad Arnoldcb638912013-06-24 04:57:11 -07001282 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001283 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1284 self._CheckMandatoryField(sig, 'data', None, sig_name)
1285 sig_report.AddField('data len', len(sig.data))
1286
Gilad Arnoldcb638912013-06-24 04:57:11 -07001287 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001288 if sig.version == 1:
1289 self._CheckSha256Signature(sig.data, pubkey_file_name,
1290 payload_hasher.digest(), sig_name)
1291 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001292 raise error.PayloadError('Unknown signature version (%d).' %
1293 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001294
Amin Hassania86b1082018-03-08 15:48:59 -08001295 def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0,
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001296 part_sizes=None, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001297 """Checker entry point, invoking all checks.
1298
1299 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001300 pubkey_file_name: Public key used for signature verification.
1301 metadata_sig_file: Metadata signature, if verification is desired.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001302 metadata_size: Metadata size, if verification is desired.
1303 part_sizes: Mapping of partition label to size in bytes (default: infer
1304 based on payload type and version or filesystem).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001305 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001306
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001307 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001308 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001309 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001310 if not pubkey_file_name:
1311 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1312
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001313 report = _PayloadReport()
1314
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001315 # Get payload file size.
1316 self.payload.payload_file.seek(0, 2)
1317 payload_file_size = self.payload.payload_file.tell()
1318 self.payload.ResetFile()
1319
1320 try:
Amin Hassania86b1082018-03-08 15:48:59 -08001321 # Check metadata_size (if provided).
Amin Hassani8ea19572018-12-05 12:06:21 -08001322 if metadata_size and self.payload.metadata_size != metadata_size:
Amin Hassania86b1082018-03-08 15:48:59 -08001323 raise error.PayloadError('Invalid payload metadata size in payload(%d) '
Amin Hassani8ea19572018-12-05 12:06:21 -08001324 'vs given(%d)' % (self.payload.metadata_size,
Amin Hassania86b1082018-03-08 15:48:59 -08001325 metadata_size))
1326
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001327 # Check metadata signature (if provided).
1328 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001329 metadata_sig = base64.b64decode(metadata_sig_file.read())
1330 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1331 self.payload.manifest_hasher.digest(),
1332 'metadata signature')
1333
Gilad Arnoldcb638912013-06-24 04:57:11 -07001334 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001335 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001336 # Check: Payload version is valid.
Tudor Brindus40506cd2018-06-18 20:18:17 -07001337 if self.payload.header.version not in (1, 2):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001338 raise error.PayloadError('Unknown payload version (%d).' %
1339 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001340 report.AddField('version', self.payload.header.version)
1341 report.AddField('manifest len', self.payload.header.manifest_len)
1342
Gilad Arnoldcb638912013-06-24 04:57:11 -07001343 # Part 2: Check the manifest.
Tudor Brindus2d22c1a2018-06-15 13:07:13 -07001344 self._CheckManifest(report, part_sizes)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001345 assert self.payload_type, 'payload type should be known by now'
1346
Tudor Brindus40506cd2018-06-18 20:18:17 -07001347 manifest = self.payload.manifest
Gilad Arnold06eea332015-07-13 18:06:33 -07001348
Tudor Brindus40506cd2018-06-18 20:18:17 -07001349 # Part 3: Examine partition operations.
1350 install_operations = []
Amin Hassani8ea19572018-12-05 12:06:21 -08001351 if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
Tudor Brindus40506cd2018-06-18 20:18:17 -07001352 # partitions field should not ever exist in major version 1 payloads
1353 self._CheckRepeatedElemNotPresent(manifest, 'partitions', 'manifest')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001354
Tudor Brindus40506cd2018-06-18 20:18:17 -07001355 install_operations.append((common.ROOTFS, manifest.install_operations))
1356 install_operations.append((common.KERNEL,
1357 manifest.kernel_install_operations))
1358
1359 else:
1360 self._CheckRepeatedElemNotPresent(manifest, 'install_operations',
1361 'manifest')
1362 self._CheckRepeatedElemNotPresent(manifest, 'kernel_install_operations',
1363 'manifest')
1364
1365 for update in manifest.partitions:
1366 install_operations.append((update.partition_name, update.operations))
1367
1368 total_blob_size = 0
1369 for part, operations in install_operations:
1370 report.AddSection('%s operations' % part)
1371
1372 new_fs_usable_size = self.new_fs_sizes[part]
1373 old_fs_usable_size = self.old_fs_sizes[part]
1374
1375 if part_sizes.get(part, None):
1376 new_fs_usable_size = old_fs_usable_size = part_sizes[part]
1377 # Infer the usable partition size when validating rootfs operations:
1378 # - If rootfs partition size was provided, use that.
1379 # - Otherwise, if this is an older delta (minor version < 2), stick with
1380 # a known constant size. This is necessary because older deltas may
1381 # exceed the filesystem size when moving data blocks around.
1382 # - Otherwise, use the encoded filesystem size.
1383 elif self.payload_type == _TYPE_DELTA and part == common.ROOTFS and \
1384 self.minor_version in (None, 1):
1385 new_fs_usable_size = old_fs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
1386
1387 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1388 # no explicit size provided *and* the partition size is not embedded in
1389 # the payload; see issue for more details.
1390 total_blob_size += self._CheckOperations(
1391 operations, report, '%s_install_operations' % part,
1392 self.old_fs_sizes[part], self.new_fs_sizes[part],
1393 old_fs_usable_size, new_fs_usable_size, total_blob_size,
Amin Hassani8ea19572018-12-05 12:06:21 -08001394 (self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION
1395 and part == common.KERNEL))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001396
Gilad Arnoldcb638912013-06-24 04:57:11 -07001397 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001398 used_payload_size = self.payload.data_offset + total_blob_size
Amin Hassani8ea19572018-12-05 12:06:21 -08001399 # Major versions 2 and higher have a signature at the end, so it should be
1400 # considered in the total size of the image.
Amin Hassani72b80ed2018-12-12 23:15:30 -08001401 if (self.major_version >= common.BRILLO_MAJOR_PAYLOAD_VERSION and
1402 self.sigs_size):
Amin Hassani8ea19572018-12-05 12:06:21 -08001403 used_payload_size += self.sigs_size
1404
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001405 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001406 raise error.PayloadError(
1407 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001408 (used_payload_size, payload_file_size))
1409
Tudor Brindus40506cd2018-06-18 20:18:17 -07001410 # Part 4: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001411 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001412 self._CheckSignatures(report, pubkey_file_name)
1413
Tudor Brindus40506cd2018-06-18 20:18:17 -07001414 # Part 5: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001415 report.AddSection('summary')
1416 report.AddField('update type', self.payload_type)
1417
1418 report.Finalize()
1419 finally:
1420 if report_out_file:
1421 report.Dump(report_out_file)