blob: 49c556fa55a63b36453611a50161e2e857ee4a40 [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,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070074}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080075
Gilad Arnold06eea332015-07-13 18:06:33 -070076_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
77
Gilad Arnold553b0ec2013-01-26 01:00:39 -080078#
79# Helper functions.
80#
Gilad Arnoldcb638912013-06-24 04:57:11 -070081
Gilad Arnold553b0ec2013-01-26 01:00:39 -080082def _IsPowerOfTwo(val):
83 """Returns True iff val is a power of two."""
84 return val > 0 and (val & (val - 1)) == 0
85
86
87def _AddFormat(format_func, value):
88 """Adds a custom formatted representation to ordinary string representation.
89
90 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070091 format_func: A value formatter.
92 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080093
Gilad Arnold553b0ec2013-01-26 01:00:39 -080094 Returns:
95 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080096 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070097 ret = str(value)
98 formatted_str = format_func(value)
99 if formatted_str:
100 ret += ' (%s)' % formatted_str
101 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800102
103
104def _AddHumanReadableSize(size):
105 """Adds a human readable representation to a byte size value."""
106 return _AddFormat(format_utils.BytesToHumanReadable, size)
107
108
109#
110# Payload report generator.
111#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700112
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800113class _PayloadReport(object):
114 """A payload report generator.
115
116 A report is essentially a sequence of nodes, which represent data points. It
117 is initialized to have a "global", untitled section. A node may be a
118 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800119 """
120
Gilad Arnoldcb638912013-06-24 04:57:11 -0700121 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800122 class Node(object):
123 """A report node interface."""
124
125 @staticmethod
126 def _Indent(indent, line):
127 """Indents a line by a given indentation amount.
128
129 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700130 indent: The indentation amount.
131 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800132
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800133 Returns:
134 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800135 """
136 return '%*s%s' % (indent, '', line)
137
138 def GenerateLines(self, base_indent, sub_indent, curr_section):
139 """Generates the report lines for this node.
140
141 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700142 base_indent: Base indentation for each line.
143 sub_indent: Additional indentation for sub-nodes.
144 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800145
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800146 Returns:
147 A pair consisting of a list of properly indented report lines and a new
148 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800149 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700150 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800151
152 class FieldNode(Node):
153 """A field report node, representing a (name, value) pair."""
154
155 def __init__(self, name, value, linebreak, indent):
156 super(_PayloadReport.FieldNode, self).__init__()
157 self.name = name
158 self.value = value
159 self.linebreak = linebreak
160 self.indent = indent
161
162 def GenerateLines(self, base_indent, sub_indent, curr_section):
163 """Generates a properly formatted 'name : value' entry."""
164 report_output = ''
165 if self.name:
166 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
167 value_lines = str(self.value).splitlines()
168 if self.linebreak and self.name:
169 report_output += '\n' + '\n'.join(
170 ['%*s%s' % (self.indent, '', line) for line in value_lines])
171 else:
172 if self.name:
173 report_output += ' '
174 report_output += '%*s' % (self.indent, '')
175 cont_line_indent = len(report_output)
176 indented_value_lines = [value_lines[0]]
177 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
178 for line in value_lines[1:]])
179 report_output += '\n'.join(indented_value_lines)
180
181 report_lines = [self._Indent(base_indent, line + '\n')
182 for line in report_output.split('\n')]
183 return report_lines, curr_section
184
185 class SubReportNode(Node):
186 """A sub-report node, representing a nested report."""
187
188 def __init__(self, title, report):
189 super(_PayloadReport.SubReportNode, self).__init__()
190 self.title = title
191 self.report = report
192
193 def GenerateLines(self, base_indent, sub_indent, curr_section):
194 """Recurse with indentation."""
195 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
196 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
197 sub_indent))
198 return report_lines, curr_section
199
200 class SectionNode(Node):
201 """A section header node."""
202
203 def __init__(self, title=None):
204 super(_PayloadReport.SectionNode, self).__init__()
205 self.title = title
206 self.max_field_name_len = 0
207
208 def GenerateLines(self, base_indent, sub_indent, curr_section):
209 """Dump a title line, return self as the (new) current section."""
210 report_lines = []
211 if self.title:
212 report_lines.append(self._Indent(base_indent,
213 '=== %s ===\n' % self.title))
214 return report_lines, self
215
216 def __init__(self):
217 self.report = []
218 self.last_section = self.global_section = self.SectionNode()
219 self.is_finalized = False
220
221 def GenerateLines(self, base_indent, sub_indent):
222 """Generates the lines in the report, properly indented.
223
224 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700225 base_indent: The indentation used for root-level report lines.
226 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800227
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800228 Returns:
229 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800230 """
231 report_lines = []
232 curr_section = self.global_section
233 for node in self.report:
234 node_report_lines, curr_section = node.GenerateLines(
235 base_indent, sub_indent, curr_section)
236 report_lines.extend(node_report_lines)
237
238 return report_lines
239
240 def Dump(self, out_file, base_indent=0, sub_indent=2):
241 """Dumps the report to a file.
242
243 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700244 out_file: File object to output the content to.
245 base_indent: Base indentation for report lines.
246 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800247 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800248 report_lines = self.GenerateLines(base_indent, sub_indent)
249 if report_lines and not self.is_finalized:
250 report_lines.append('(incomplete report)\n')
251
252 for line in report_lines:
253 out_file.write(line)
254
255 def AddField(self, name, value, linebreak=False, indent=0):
256 """Adds a field/value pair to the payload report.
257
258 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700259 name: The field's name.
260 value: The field's value.
261 linebreak: Whether the value should be printed on a new line.
262 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800263 """
264 assert not self.is_finalized
265 if name and self.last_section.max_field_name_len < len(name):
266 self.last_section.max_field_name_len = len(name)
267 self.report.append(self.FieldNode(name, value, linebreak, indent))
268
269 def AddSubReport(self, title):
270 """Adds and returns a sub-report with a title."""
271 assert not self.is_finalized
272 sub_report = self.SubReportNode(title, type(self)())
273 self.report.append(sub_report)
274 return sub_report.report
275
276 def AddSection(self, title):
277 """Adds a new section title."""
278 assert not self.is_finalized
279 self.last_section = self.SectionNode(title)
280 self.report.append(self.last_section)
281
282 def Finalize(self):
283 """Seals the report, marking it as complete."""
284 self.is_finalized = True
285
286
287#
288# Payload verification.
289#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700290
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800291class PayloadChecker(object):
292 """Checking the integrity of an update payload.
293
294 This is a short-lived object whose purpose is to isolate the logic used for
295 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800296 """
297
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700298 def __init__(self, payload, assert_type=None, block_size=0,
299 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700300 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700301
302 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700303 payload: The payload object to check.
304 assert_type: Assert that payload is either 'full' or 'delta' (optional).
305 block_size: Expected filesystem / payload block size (optional).
306 allow_unhashed: Allow operations with unhashed data blobs.
307 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700308 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700309 if not payload.is_init:
310 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700311
312 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800313 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700314 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
315 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700316 raise error.PayloadError(
317 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700318 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700319 raise error.PayloadError('Invalid assert_type value (%r).' %
320 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700321 self.payload_type = assert_type
322 self.allow_unhashed = allow_unhashed
323
324 # Disable specific tests.
325 self.check_dst_pseudo_extents = (
326 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
327 self.check_move_same_src_dst_block = (
328 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
329 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800330
331 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800332 self.sigs_offset = 0
333 self.sigs_size = 0
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700334 self.old_part_info = {}
335 self.new_part_info = {}
336 self.new_fs_sizes = collections.defaultdict(int)
337 self.old_fs_sizes = collections.defaultdict(int)
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700338 self.minor_version = None
Amin Hassani0de7f782017-12-07 12:13:03 -0800339 # TODO(*): When fixing crbug.com/794404, the major version should be
Sen Jiang771f6482018-04-04 17:59:10 -0700340 # correctly handled in update_payload scripts. So stop forcing
Amin Hassani0de7f782017-12-07 12:13:03 -0800341 # major_verions=1 here and set it to the correct value.
342 self.major_version = 1
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800343
344 @staticmethod
345 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
346 msg_name=None, linebreak=False, indent=0):
347 """Adds an element from a protobuf message to the payload report.
348
349 Checks to see whether a message contains a given element, and if so adds
350 the element value to the provided report. A missing mandatory element
351 causes an exception to be raised.
352
353 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700354 msg: The message containing the element.
355 name: The name of the element.
356 report: A report object to add the element name/value to.
357 is_mandatory: Whether or not this element must be present.
358 is_submsg: Whether this element is itself a message.
359 convert: A function for converting the element value for reporting.
360 msg_name: The name of the message object (for error reporting).
361 linebreak: Whether the value report should induce a line break.
362 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800363
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800364 Returns:
365 A pair consisting of the element value and the generated sub-report for
366 it (if the element is a sub-message, None otherwise). If the element is
367 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800368
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800369 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700370 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800371 """
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700372 element_result = collections.namedtuple('element_result', ['msg', 'report'])
373
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800374 if not msg.HasField(name):
375 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700376 raise error.PayloadError('%smissing mandatory %s %r.' %
377 (msg_name + ' ' if msg_name else '',
378 'sub-message' if is_submsg else 'field',
379 name))
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700380 return element_result(None, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800381
382 value = getattr(msg, name)
383 if is_submsg:
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700384 return element_result(value, report and report.AddSubReport(name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800385 else:
386 if report:
387 report.AddField(name, convert(value), linebreak=linebreak,
388 indent=indent)
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700389 return element_result(value, None)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800390
391 @staticmethod
392 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
393 linebreak=False, indent=0):
394 """Adds a mandatory field; returning first component from _CheckElem."""
395 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
396 convert=convert, msg_name=msg_name,
397 linebreak=linebreak, indent=indent)[0]
398
399 @staticmethod
400 def _CheckOptionalField(msg, field_name, report, convert=str,
401 linebreak=False, indent=0):
402 """Adds an optional field; returning first component from _CheckElem."""
403 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
404 convert=convert, linebreak=linebreak,
405 indent=indent)[0]
406
407 @staticmethod
408 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
409 """Adds a mandatory sub-message; wrapper for _CheckElem."""
410 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
411 msg_name)
412
413 @staticmethod
414 def _CheckOptionalSubMsg(msg, submsg_name, report):
415 """Adds an optional sub-message; wrapper for _CheckElem."""
416 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
417
418 @staticmethod
419 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
420 """Checks that val1 is None iff val2 is None.
421
422 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700423 val1: first value to be compared.
424 val2: second value to be compared.
425 name1: name of object holding the first value.
426 name2: name of object holding the second value.
427 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800428
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800429 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700430 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800431 """
432 if None in (val1, val2) and val1 is not val2:
433 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700434 raise error.PayloadError('%r present without %r%s.' %
435 (present, missing,
436 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800437
438 @staticmethod
439 def _Run(cmd, send_data=None):
440 """Runs a subprocess, returns its output.
441
442 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700443 cmd: Sequence of command-line argument for invoking the subprocess.
444 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800445
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800446 Returns:
447 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800448 """
449 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
450 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700451 try:
452 result = run_process.communicate(input=send_data)
453 finally:
454 exit_code = run_process.wait()
455
456 if exit_code:
457 raise RuntimeError('Subprocess %r failed with code %r.' %
458 (cmd, exit_code))
459
460 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800461
462 @staticmethod
463 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
464 """Verifies an actual hash against a signed one.
465
466 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700467 sig_data: The raw signature data.
468 pubkey_file_name: Public key used for verifying signature.
469 actual_hash: The actual hash digest.
470 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800471
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800472 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700473 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800474 """
475 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700476 raise error.PayloadError(
477 '%s: signature size (%d) not as expected (256).' %
478 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800479 signed_data, _ = PayloadChecker._Run(
480 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
481 send_data=sig_data)
482
Gilad Arnold5502b562013-03-08 13:22:31 -0800483 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700484 raise error.PayloadError('%s: unexpected signed data length (%d).' %
485 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800486
Gilad Arnold5502b562013-03-08 13:22:31 -0800487 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700488 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
489 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800490
Gilad Arnold5502b562013-03-08 13:22:31 -0800491 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800492 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700493 raise error.PayloadError(
494 '%s: signed hash (%s) different from actual (%s).' %
495 (sig_name, common.FormatSha256(signed_hash),
496 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800497
498 @staticmethod
499 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
500 block_name=None):
501 """Checks that a given length fits given block space.
502
503 This ensures that the number of blocks allocated is appropriate for the
504 length of the data residing in these blocks.
505
506 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700507 length: The actual length of the data.
508 num_blocks: The number of blocks allocated for it.
509 block_size: The size of each block in bytes.
510 length_name: Name of length (used for error reporting).
511 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800512
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800513 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700514 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800515 """
516 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700517 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700518 raise error.PayloadError(
519 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800520 (length_name, length, block_name or '', num_blocks, block_size))
521
522 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700523 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700524 raise error.PayloadError(
525 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800526 (length_name, length, block_name or '', num_blocks - 1, block_size))
527
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700528 def _CheckManifestMinorVersion(self, report):
529 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800530
531 Args:
532 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800533
534 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700535 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800536 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700537 self.minor_version = self._CheckOptionalField(self.payload.manifest,
538 'minor_version', report)
539 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
540 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800541 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700542 'Minor version %d not compatible with payload type %s.' %
543 (self.minor_version, self.payload_type))
544 elif self.minor_version is None:
545 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800546 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700547 raise error.PayloadError('Unsupported minor version: %d' %
548 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800549
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700550 def _CheckManifest(self, report, part_sizes=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800551 """Checks the payload manifest.
552
553 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700554 report: A report object to add to.
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700555 part_sizes: Map of partition label to partition size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800556
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800557 Returns:
558 A tuple consisting of the partition block size used during the update
559 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800560
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800561 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700562 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800563 """
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700564 if part_sizes is None:
565 part_sizes = collections.defaultdict(int)
566
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800567 manifest = self.payload.manifest
568 report.AddSection('manifest')
569
570 # Check: block_size must exist and match the expected value.
571 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
572 report, 'manifest')
573 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700574 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
575 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800576
577 # Check: signatures_offset <==> signatures_size.
578 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
579 report)
580 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
581 report)
582 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
583 'signatures_offset', 'signatures_size', 'manifest')
584
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700585 for part in common.CROS_PARTITIONS:
586 self.old_part_info[part] = self._CheckOptionalSubMsg(
587 manifest, 'old_%s_info' % part, report)
588 self.new_part_info[part] = self._CheckMandatorySubMsg(
589 manifest, 'new_%s_info' % part, report, 'manifest')
590
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800591 # Check: old_kernel_info <==> old_rootfs_info.
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700592 self._CheckPresentIff(self.old_part_info[common.KERNEL].msg,
593 self.old_part_info[common.ROOTFS].msg,
594 'old_kernel_info', 'old_rootfs_info', 'manifest')
595
596 if self.old_part_info[common.KERNEL].msg: # equivalently, rootfs msg
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800597 # Assert/mark delta payload.
598 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700599 raise error.PayloadError(
600 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800601 self.payload_type = _TYPE_DELTA
602
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700603 for part in common.CROS_PARTITIONS:
604 # Check: {size, hash} present in old_{kernel,rootfs}_info.
605 field = 'old_%s_info' % part
606 msg, report = self.old_part_info[part]
607 self.old_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
608 report, field)
609 self._CheckMandatoryField(msg, 'hash', report, field,
610 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700611
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700612 # Check: old_{kernel,rootfs} size must fit in respective partition.
613 if self.old_fs_sizes[part] > part_sizes[part] > 0:
614 raise error.PayloadError(
615 'Old %s content (%d) exceed partition size (%d).' %
616 (part, self.old_fs_sizes[part], part_sizes[part]))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800617 else:
618 # Assert/mark full payload.
619 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700620 raise error.PayloadError(
621 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800622 self.payload_type = _TYPE_FULL
623
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700624 # Check: new_{kernel,rootfs}_info present; contains {size, hash}.
625 for part in common.CROS_PARTITIONS:
626 field = 'new_%s_info' % part
627 msg, report = self.new_part_info[part]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800628
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700629 self.new_fs_sizes[part] = self._CheckMandatoryField(msg, 'size', report,
630 field)
631 self._CheckMandatoryField(msg, 'hash', report, field,
632 convert=common.FormatSha256)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800633
Tudor Brindus8d05a7e2018-06-14 11:18:18 -0700634 # Check: new_{kernel,rootfs} size must fit in respective partition.
635 if self.new_fs_sizes[part] > part_sizes[part] > 0:
636 raise error.PayloadError(
637 'New %s content (%d) exceed partition size (%d).' %
638 (part, self.new_fs_sizes[part], part_sizes[part]))
Gilad Arnold382df5c2013-05-03 12:49:28 -0700639
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800640 # Check: minor_version makes sense for the payload type. This check should
641 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700642 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800643
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800644 def _CheckLength(self, length, total_blocks, op_name, length_name):
645 """Checks whether a length matches the space designated in extents.
646
647 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700648 length: The total length of the data.
649 total_blocks: The total number of blocks in extents.
650 op_name: Operation name (for error reporting).
651 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800652
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800653 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700654 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800655 """
656 # Check: length is non-zero.
657 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700658 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800659
660 # Check that length matches number of blocks.
661 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
662 '%s: %s' % (op_name, length_name))
663
Gilad Arnold382df5c2013-05-03 12:49:28 -0700664 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800665 allow_pseudo=False, allow_signature=False):
666 """Checks a sequence of extents.
667
668 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700669 extents: The sequence of extents to check.
670 usable_size: The usable size of the partition to which the extents apply.
671 block_counters: Array of counters corresponding to the number of blocks.
672 name: The name of the extent block.
673 allow_pseudo: Whether or not pseudo block numbers are allowed.
674 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800675
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800676 Returns:
677 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800678
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800679 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700680 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800681 """
682 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800683 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700684 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800685 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
686 None, ex_name)
687 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
688 ex_name)
689 end_block = start_block + num_blocks
690
691 # Check: num_blocks > 0.
692 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700693 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800694
695 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700696 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700697 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700698 raise error.PayloadError(
699 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700700 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800701
702 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700703 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800704 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800705 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
706 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
707 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700708 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800709
710 total_num_blocks += num_blocks
711
712 return total_num_blocks
713
714 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0de7f782017-12-07 12:13:03 -0800715 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800716
717 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700718 op: The operation object from the manifest.
719 data_length: The length of the data blob associated with the operation.
720 total_dst_blocks: Total number of blocks in dst_extents.
721 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800722
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800723 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700724 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800725 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700726 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800727 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700728 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800729
Gilad Arnoldcb638912013-06-24 04:57:11 -0700730 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800731 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700732 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800733
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800734 if op.type == common.OpType.REPLACE:
735 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
736 self.block_size,
737 op_name + '.data_length', 'dst')
738 else:
Sen Jiang771f6482018-04-04 17:59:10 -0700739 # Check: data_length must be smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800740 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700741 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800742 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700743 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800744 (op_name, data_length, total_dst_blocks, self.block_size))
745
746 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
747 total_dst_blocks, op_name):
748 """Specific checks for MOVE operations.
749
750 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700751 op: The operation object from the manifest.
752 data_offset: The offset of a data blob for the operation.
753 total_src_blocks: Total number of blocks in src_extents.
754 total_dst_blocks: Total number of blocks in dst_extents.
755 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800756
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800757 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700758 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800759 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700760 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800761 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700762 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800763
Gilad Arnoldcb638912013-06-24 04:57:11 -0700764 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800765 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700766 raise error.PayloadError(
767 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800768 (op_name, total_src_blocks, total_dst_blocks))
769
Gilad Arnoldcb638912013-06-24 04:57:11 -0700770 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800771 i = 0
772 src_extent_iter = iter(op.src_extents)
773 dst_extent_iter = iter(op.dst_extents)
774 src_extent = dst_extent = None
775 src_idx = src_num = dst_idx = dst_num = 0
776 while i < total_src_blocks:
777 # Get the next source extent, if needed.
778 if not src_extent:
779 try:
780 src_extent = src_extent_iter.next()
781 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700782 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
783 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800784 src_idx = src_extent.start_block
785 src_num = src_extent.num_blocks
786
787 # Get the next dest extent, if needed.
788 if not dst_extent:
789 try:
790 dst_extent = dst_extent_iter.next()
791 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700792 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
793 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800794 dst_idx = dst_extent.start_block
795 dst_num = dst_extent.num_blocks
796
Allie Woodb065e132015-04-24 10:20:27 -0700797 # Check: start block is not 0. See crbug/480751; there are still versions
798 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
799 # so we need to fail payloads that try to MOVE to/from block 0.
800 if src_idx == 0 or dst_idx == 0:
801 raise error.PayloadError(
802 '%s: MOVE operation cannot have extent with start block 0' %
803 op_name)
804
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700805 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700806 raise error.PayloadError(
807 '%s: src/dst block number %d is the same (%d).' %
808 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800809
810 advance = min(src_num, dst_num)
811 i += advance
812
813 src_idx += advance
814 src_num -= advance
815 if src_num == 0:
816 src_extent = None
817
818 dst_idx += advance
819 dst_num -= advance
820 if dst_num == 0:
821 dst_extent = None
822
Gilad Arnold5502b562013-03-08 13:22:31 -0800823 # Make sure we've exhausted all src/dst extents.
824 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700825 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800826 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700827 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800828
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700829 def _CheckZeroOperation(self, op, op_name):
830 """Specific checks for ZERO operations.
831
832 Args:
833 op: The operation object from the manifest.
834 op_name: Operation name for error reporting.
835
836 Raises:
837 error.PayloadError if any check fails.
838 """
839 # Check: Does not contain src extents, data_length and data_offset.
840 if op.src_extents:
841 raise error.PayloadError('%s: contains src_extents.' % op_name)
842 if op.data_length:
843 raise error.PayloadError('%s: contains data_length.' % op_name)
844 if op.data_offset:
845 raise error.PayloadError('%s: contains data_offset.' % op_name)
846
Amin Hassaniefa62d92017-11-09 13:46:56 -0800847 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
848 """Specific checks for BSDIFF, SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
849 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800850
851 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800852 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700853 data_length: The length of the data blob associated with the operation.
854 total_dst_blocks: Total number of blocks in dst_extents.
855 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800856
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800857 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700858 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800859 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800860 # Check: data_{offset,length} present.
861 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700862 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800863
Sen Jiang771f6482018-04-04 17:59:10 -0700864 # Check: data_length is strictly smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800865 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700866 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800867 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700868 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800869 (op_name, data_length, total_dst_blocks, self.block_size,
870 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800871
Amin Hassaniefa62d92017-11-09 13:46:56 -0800872 # Check the existence of src_length and dst_length for legacy bsdiffs.
873 if (op.type == common.OpType.BSDIFF or
874 (op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3)):
875 if not op.HasField('src_length') or not op.HasField('dst_length'):
876 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
877 else:
878 if op.HasField('src_length') or op.HasField('dst_length'):
879 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
880
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800881 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
882 total_dst_blocks, op_name):
883 """Specific checks for SOURCE_COPY.
884
885 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800886 data_offset: The offset of a data blob for the operation.
887 total_src_blocks: Total number of blocks in src_extents.
888 total_dst_blocks: Total number of blocks in dst_extents.
889 op_name: Operation name for error reporting.
890
891 Raises:
892 error.PayloadError if any check fails.
893 """
894 # Check: No data_{offset,length}.
895 if data_offset is not None:
896 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
897
898 # Check: total_src_blocks == total_dst_blocks.
899 if total_src_blocks != total_dst_blocks:
900 raise error.PayloadError(
901 '%s: total src blocks (%d) != total dst blocks (%d).' %
902 (op_name, total_src_blocks, total_dst_blocks))
903
Sen Jiangd6122bb2015-12-11 10:27:04 -0800904 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800905 """Specific checks for SOURCE_* operations.
906
907 Args:
908 op: The operation object from the manifest.
909 total_src_blocks: Total number of blocks in src_extents.
910 op_name: Operation name for error reporting.
911
912 Raises:
913 error.PayloadError if any check fails.
914 """
915 # Check: total_src_blocks != 0.
916 if total_src_blocks == 0:
917 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
918
Sen Jiang92161a72016-06-28 16:09:38 -0700919 # Check: src_sha256_hash present in minor version >= 3.
920 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800921 raise error.PayloadError('%s: source hash missing.' % op_name)
922
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800923 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700924 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700925 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800926 """Checks a single update operation.
927
928 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700929 op: The operation object.
930 op_name: Operation name string for error reporting.
931 is_last: Whether this is the last operation in the sequence.
932 old_block_counters: Arrays of block read counters.
933 new_block_counters: Arrays of block write counters.
934 old_usable_size: The overall usable size for src data in bytes.
935 new_usable_size: The overall usable size for dst data in bytes.
936 prev_data_offset: Offset of last used data bytes.
937 allow_signature: Whether this may be a signature operation.
938 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800939
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800940 Returns:
941 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800942
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800943 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700944 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800945 """
946 # Check extents.
947 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700948 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800949 op_name + '.src_extents', allow_pseudo=True)
950 allow_signature_in_extents = (allow_signature and is_last and
951 op.type == common.OpType.REPLACE)
952 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700953 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700954 op_name + '.dst_extents',
955 allow_pseudo=(not self.check_dst_pseudo_extents),
956 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800957
958 # Check: data_offset present <==> data_length present.
959 data_offset = self._CheckOptionalField(op, 'data_offset', None)
960 data_length = self._CheckOptionalField(op, 'data_length', None)
961 self._CheckPresentIff(data_offset, data_length, 'data_offset',
962 'data_length', op_name)
963
Gilad Arnoldcb638912013-06-24 04:57:11 -0700964 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800965 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700966 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800967
968 # Check {src,dst}_length, if present.
969 if op.HasField('src_length'):
970 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
971 if op.HasField('dst_length'):
972 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
973
974 if op.HasField('data_sha256_hash'):
975 blob_hash_counts['hashed'] += 1
976
Gilad Arnoldcb638912013-06-24 04:57:11 -0700977 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800978 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700979 raise error.PayloadError(
980 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800981 op_name)
982
Gilad Arnoldcb638912013-06-24 04:57:11 -0700983 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800984 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
985 data_length))
986 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700987 raise error.PayloadError(
988 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700989 (op_name, common.FormatSha256(op.data_sha256_hash),
990 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800991 elif data_offset is not None:
992 if allow_signature_in_extents:
993 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700994 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800995 blob_hash_counts['unhashed'] += 1
996 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700997 raise error.PayloadError('%s: unhashed operation not allowed.' %
998 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800999
1000 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001001 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001002 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001003 raise error.PayloadError(
1004 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001005 (op_name, data_offset, prev_data_offset))
1006
1007 # Type-specific checks.
1008 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
1009 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani0de7f782017-12-07 12:13:03 -08001010 elif op.type == common.OpType.REPLACE_XZ and (self.minor_version >= 3 or
1011 self.major_version >= 2):
1012 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001013 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001014 self._CheckMoveOperation(op, data_offset, total_src_blocks,
1015 total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001016 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
1017 self._CheckZeroOperation(op, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001018 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001019 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001020 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001021 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
1022 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001023 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001024 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001025 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001026 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani77d7cbc2018-02-07 16:21:33 -08001027 elif op.type == common.OpType.BROTLI_BSDIFF and self.minor_version >= 4:
1028 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
1029 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
1030 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 5:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001031 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001032 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001033 else:
Allie Wood7cf9f132015-02-26 14:28:19 -08001034 raise error.PayloadError(
1035 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001036 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001037 return data_length if data_length is not None else 0
1038
Gilad Arnold382df5c2013-05-03 12:49:28 -07001039 def _SizeToNumBlocks(self, size):
1040 """Returns the number of blocks needed to contain a given byte size."""
1041 return (size + self.block_size - 1) / self.block_size
1042
1043 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001044 """Returns a freshly initialized array of block counters.
1045
Gilad Arnoldcb638912013-06-24 04:57:11 -07001046 Note that the generated array is not portable as is due to byte-ordering
1047 issues, hence it should not be serialized.
1048
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001049 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001050 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001051
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001052 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001053 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001054 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001055 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001056 return array.array('H',
1057 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001058
Gilad Arnold382df5c2013-05-03 12:49:28 -07001059 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001060 new_fs_size, old_usable_size, new_usable_size,
1061 prev_data_offset, allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001062 """Checks a sequence of update operations.
1063
1064 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001065 operations: The sequence of operations to check.
1066 report: The report object to add to.
1067 base_name: The name of the operation block.
1068 old_fs_size: The old filesystem size in bytes.
1069 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001070 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001071 new_usable_size: The overall usable size of the new partition in bytes.
1072 prev_data_offset: Offset of last used data bytes.
1073 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001074
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001075 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001076 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001077
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001078 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001079 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001080 """
1081 # The total size of data blobs used by operations scanned thus far.
1082 total_data_used = 0
1083 # Counts of specific operation types.
1084 op_counts = {
1085 common.OpType.REPLACE: 0,
1086 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001087 common.OpType.REPLACE_XZ: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001088 common.OpType.MOVE: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001089 common.OpType.ZERO: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001090 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001091 common.OpType.SOURCE_COPY: 0,
1092 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001093 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001094 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001095 }
1096 # Total blob sizes for each operation type.
1097 op_blob_totals = {
1098 common.OpType.REPLACE: 0,
1099 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001100 common.OpType.REPLACE_XZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001101 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001102 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001103 # SOURCE_COPY operations don't have blobs.
1104 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001105 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001106 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001107 }
1108 # Counts of hashed vs unhashed operations.
1109 blob_hash_counts = {
1110 'hashed': 0,
1111 'unhashed': 0,
1112 }
1113 if allow_signature:
1114 blob_hash_counts['signature'] = 0
1115
1116 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001117 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001118 if old_fs_size else None)
1119 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001120
1121 # Process and verify each operation.
1122 op_num = 0
1123 for op, op_name in common.OperationIter(operations, base_name):
1124 op_num += 1
1125
Gilad Arnoldcb638912013-06-24 04:57:11 -07001126 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001127 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001128 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001129 op_counts[op.type] += 1
1130
1131 is_last = op_num == len(operations)
1132 curr_data_used = self._CheckOperation(
1133 op, op_name, is_last, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001134 old_usable_size, new_usable_size,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001135 prev_data_offset + total_data_used, allow_signature,
1136 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001137 if curr_data_used:
1138 op_blob_totals[op.type] += curr_data_used
1139 total_data_used += curr_data_used
1140
1141 # Report totals and breakdown statistics.
1142 report.AddField('total operations', op_num)
1143 report.AddField(
1144 None,
1145 histogram.Histogram.FromCountDict(op_counts,
1146 key_names=common.OpType.NAMES),
1147 indent=1)
1148 report.AddField('total blobs', sum(blob_hash_counts.values()))
1149 report.AddField(None,
1150 histogram.Histogram.FromCountDict(blob_hash_counts),
1151 indent=1)
1152 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1153 report.AddField(
1154 None,
1155 histogram.Histogram.FromCountDict(op_blob_totals,
1156 formatter=_AddHumanReadableSize,
1157 key_names=common.OpType.NAMES),
1158 indent=1)
1159
1160 # Report read/write histograms.
1161 if old_block_counters:
1162 report.AddField('block read hist',
1163 histogram.Histogram.FromKeyList(old_block_counters),
1164 linebreak=True, indent=1)
1165
Gilad Arnold382df5c2013-05-03 12:49:28 -07001166 new_write_hist = histogram.Histogram.FromKeyList(
1167 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1168 report.AddField('block write hist', new_write_hist, linebreak=True,
1169 indent=1)
1170
Gilad Arnoldcb638912013-06-24 04:57:11 -07001171 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001172 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001173 raise error.PayloadError(
1174 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001175 base_name)
1176
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001177 return total_data_used
1178
1179 def _CheckSignatures(self, report, pubkey_file_name):
1180 """Checks a payload's signature block."""
1181 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1182 sigs = update_metadata_pb2.Signatures()
1183 sigs.ParseFromString(sigs_raw)
1184 report.AddSection('signatures')
1185
Gilad Arnoldcb638912013-06-24 04:57:11 -07001186 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001187 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001188 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001189
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001190 last_ops_section = (self.payload.manifest.kernel_install_operations or
1191 self.payload.manifest.install_operations)
1192 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001193 # Check: signatures_{offset,size} must match the last (fake) operation.
1194 if not (fake_sig_op.type == common.OpType.REPLACE and
1195 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001196 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001197 raise error.PayloadError(
1198 'Signatures_{offset,size} (%d+%d) does not match last operation '
1199 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001200 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1201 fake_sig_op.data_length))
1202
1203 # Compute the checksum of all data up to signature blob.
1204 # TODO(garnold) we're re-reading the whole data section into a string
1205 # just to compute the checksum; instead, we could do it incrementally as
1206 # we read the blobs one-by-one, under the assumption that we're reading
1207 # them in order (which currently holds). This should be reconsidered.
1208 payload_hasher = self.payload.manifest_hasher.copy()
1209 common.Read(self.payload.payload_file, self.sigs_offset,
1210 offset=self.payload.data_offset, hasher=payload_hasher)
1211
1212 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1213 sig_report = report.AddSubReport(sig_name)
1214
Gilad Arnoldcb638912013-06-24 04:57:11 -07001215 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001216 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1217 self._CheckMandatoryField(sig, 'data', None, sig_name)
1218 sig_report.AddField('data len', len(sig.data))
1219
Gilad Arnoldcb638912013-06-24 04:57:11 -07001220 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001221 if sig.version == 1:
1222 self._CheckSha256Signature(sig.data, pubkey_file_name,
1223 payload_hasher.digest(), sig_name)
1224 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001225 raise error.PayloadError('Unknown signature version (%d).' %
1226 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001227
Amin Hassania86b1082018-03-08 15:48:59 -08001228 def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001229 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001230 """Checker entry point, invoking all checks.
1231
1232 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001233 pubkey_file_name: Public key used for signature verification.
1234 metadata_sig_file: Metadata signature, if verification is desired.
Amin Hassania86b1082018-03-08 15:48:59 -08001235 metadata_size: metadata size, if verification is desired
Gilad Arnold06eea332015-07-13 18:06:33 -07001236 rootfs_part_size: The size of rootfs partitions in bytes (default: infer
1237 based on payload type and version).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001238 kernel_part_size: The size of kernel partitions in bytes (default: use
1239 reported filesystem size).
1240 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001241
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001242 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001243 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001244 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001245 if not pubkey_file_name:
1246 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1247
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001248 report = _PayloadReport()
1249
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001250 # Get payload file size.
1251 self.payload.payload_file.seek(0, 2)
1252 payload_file_size = self.payload.payload_file.tell()
1253 self.payload.ResetFile()
1254
1255 try:
Amin Hassania86b1082018-03-08 15:48:59 -08001256 # Check metadata_size (if provided).
1257 if metadata_size and self.payload.data_offset != metadata_size:
1258 raise error.PayloadError('Invalid payload metadata size in payload(%d) '
1259 'vs given(%d)' % (self.payload.data_offset,
1260 metadata_size))
1261
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001262 # Check metadata signature (if provided).
1263 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001264 metadata_sig = base64.b64decode(metadata_sig_file.read())
1265 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1266 self.payload.manifest_hasher.digest(),
1267 'metadata signature')
1268
Gilad Arnoldcb638912013-06-24 04:57:11 -07001269 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001270 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001271 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001272 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001273 raise error.PayloadError('Unknown payload version (%d).' %
1274 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001275 report.AddField('version', self.payload.header.version)
1276 report.AddField('manifest len', self.payload.header.manifest_len)
1277
Gilad Arnoldcb638912013-06-24 04:57:11 -07001278 # Part 2: Check the manifest.
Tudor Brindus8d05a7e2018-06-14 11:18:18 -07001279 self._CheckManifest(report, {
1280 common.ROOTFS: rootfs_part_size,
1281 common.KERNEL: kernel_part_size
1282 })
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001283 assert self.payload_type, 'payload type should be known by now'
1284
Gilad Arnold06eea332015-07-13 18:06:33 -07001285 # Infer the usable partition size when validating rootfs operations:
1286 # - If rootfs partition size was provided, use that.
1287 # - Otherwise, if this is an older delta (minor version < 2), stick with
1288 # a known constant size. This is necessary because older deltas may
1289 # exceed the filesystem size when moving data blocks around.
1290 # - Otherwise, use the encoded filesystem size.
Tudor Brindus8d05a7e2018-06-14 11:18:18 -07001291 new_rootfs_usable_size = self.new_fs_sizes[common.ROOTFS]
1292 old_rootfs_usable_size = self.old_fs_sizes[common.ROOTFS]
Gilad Arnold06eea332015-07-13 18:06:33 -07001293 if rootfs_part_size:
1294 new_rootfs_usable_size = rootfs_part_size
Amin Hassaniae853742017-10-11 10:27:27 -07001295 old_rootfs_usable_size = rootfs_part_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001296 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
1297 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Amin Hassaniae853742017-10-11 10:27:27 -07001298 old_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Gilad Arnold06eea332015-07-13 18:06:33 -07001299
Gilad Arnoldcb638912013-06-24 04:57:11 -07001300 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001301 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1302 # no explicit size provided *and* the partition size is not embedded in
1303 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001304 report.AddSection('rootfs operations')
1305 total_blob_size = self._CheckOperations(
1306 self.payload.manifest.install_operations, report,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -07001307 'install_operations', self.old_fs_sizes[common.ROOTFS],
1308 self.new_fs_sizes[common.ROOTFS], old_rootfs_usable_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001309 new_rootfs_usable_size, 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001310
Gilad Arnoldcb638912013-06-24 04:57:11 -07001311 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001312 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001313 report.AddSection('kernel operations')
Tudor Brindus8d05a7e2018-06-14 11:18:18 -07001314 old_kernel_fs_size = self.old_fs_sizes[common.KERNEL]
1315 new_kernel_fs_size = self.new_fs_sizes[common.KERNEL]
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001316 total_blob_size += self._CheckOperations(
1317 self.payload.manifest.kernel_install_operations, report,
Tudor Brindus8d05a7e2018-06-14 11:18:18 -07001318 'kernel_install_operations', old_kernel_fs_size, new_kernel_fs_size,
1319 kernel_part_size if kernel_part_size else old_kernel_fs_size,
1320 kernel_part_size if kernel_part_size else new_kernel_fs_size,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001321 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001322
Gilad Arnoldcb638912013-06-24 04:57:11 -07001323 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001324 used_payload_size = self.payload.data_offset + total_blob_size
1325 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001326 raise error.PayloadError(
1327 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001328 (used_payload_size, payload_file_size))
1329
Gilad Arnoldcb638912013-06-24 04:57:11 -07001330 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001331 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001332 self._CheckSignatures(report, pubkey_file_name)
1333
Gilad Arnoldcb638912013-06-24 04:57:11 -07001334 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001335 report.AddSection('summary')
1336 report.AddField('update type', self.payload_type)
1337
1338 report.Finalize()
1339 finally:
1340 if report_out_file:
1341 report.Dump(report_out_file)