blob: e9e99583c70adf12ec2370b7f468a48cebf1453a [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
31import hashlib
Gilad Arnoldcb638912013-06-24 04:57:11 -070032import itertools
Gilad Arnold9b90c932013-05-22 17:12:56 -070033import os
Gilad Arnold553b0ec2013-01-26 01:00:39 -080034import subprocess
35
Amin Hassanib05a65a2017-12-18 15:15:32 -080036from update_payload import common
37from update_payload import error
38from update_payload import format_utils
39from update_payload import histogram
40from update_payload import update_metadata_pb2
Gilad Arnold553b0ec2013-01-26 01:00:39 -080041
42
43#
Gilad Arnold9b90c932013-05-22 17:12:56 -070044# Constants.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080045#
Gilad Arnoldcb638912013-06-24 04:57:11 -070046
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070047_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents'
48_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
49_CHECK_PAYLOAD_SIG = 'payload-sig'
50CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070051 _CHECK_DST_PSEUDO_EXTENTS,
52 _CHECK_MOVE_SAME_SRC_DST_BLOCK,
53 _CHECK_PAYLOAD_SIG,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070054)
55
Gilad Arnold553b0ec2013-01-26 01:00:39 -080056_TYPE_FULL = 'full'
57_TYPE_DELTA = 'delta'
58
59_DEFAULT_BLOCK_SIZE = 4096
60
Gilad Arnold9b90c932013-05-22 17:12:56 -070061_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem'
62_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
63 _DEFAULT_PUBKEY_BASE_NAME)
64
Gilad Arnold0d575cd2015-07-13 17:29:21 -070065# Supported minor version map to payload types allowed to be using them.
66_SUPPORTED_MINOR_VERSIONS = {
67 0: (_TYPE_FULL,),
68 1: (_TYPE_DELTA,),
69 2: (_TYPE_DELTA,),
Sen Jiang912c4df2015-12-10 12:17:13 -080070 3: (_TYPE_DELTA,),
Sen Jiang92161a72016-06-28 16:09:38 -070071 4: (_TYPE_DELTA,),
Amin Hassani77d7cbc2018-02-07 16:21:33 -080072 5: (_TYPE_DELTA,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070073}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080074
Gilad Arnold06eea332015-07-13 18:06:33 -070075_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
76
Gilad Arnold553b0ec2013-01-26 01:00:39 -080077#
78# Helper functions.
79#
Gilad Arnoldcb638912013-06-24 04:57:11 -070080
Gilad Arnold553b0ec2013-01-26 01:00:39 -080081def _IsPowerOfTwo(val):
82 """Returns True iff val is a power of two."""
83 return val > 0 and (val & (val - 1)) == 0
84
85
86def _AddFormat(format_func, value):
87 """Adds a custom formatted representation to ordinary string representation.
88
89 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070090 format_func: A value formatter.
91 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080092
Gilad Arnold553b0ec2013-01-26 01:00:39 -080093 Returns:
94 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080095 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070096 ret = str(value)
97 formatted_str = format_func(value)
98 if formatted_str:
99 ret += ' (%s)' % formatted_str
100 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800101
102
103def _AddHumanReadableSize(size):
104 """Adds a human readable representation to a byte size value."""
105 return _AddFormat(format_utils.BytesToHumanReadable, size)
106
107
108#
109# Payload report generator.
110#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700111
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800112class _PayloadReport(object):
113 """A payload report generator.
114
115 A report is essentially a sequence of nodes, which represent data points. It
116 is initialized to have a "global", untitled section. A node may be a
117 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800118 """
119
Gilad Arnoldcb638912013-06-24 04:57:11 -0700120 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800121 class Node(object):
122 """A report node interface."""
123
124 @staticmethod
125 def _Indent(indent, line):
126 """Indents a line by a given indentation amount.
127
128 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700129 indent: The indentation amount.
130 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800131
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800132 Returns:
133 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800134 """
135 return '%*s%s' % (indent, '', line)
136
137 def GenerateLines(self, base_indent, sub_indent, curr_section):
138 """Generates the report lines for this node.
139
140 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700141 base_indent: Base indentation for each line.
142 sub_indent: Additional indentation for sub-nodes.
143 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800144
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800145 Returns:
146 A pair consisting of a list of properly indented report lines and a new
147 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800148 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700149 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800150
151 class FieldNode(Node):
152 """A field report node, representing a (name, value) pair."""
153
154 def __init__(self, name, value, linebreak, indent):
155 super(_PayloadReport.FieldNode, self).__init__()
156 self.name = name
157 self.value = value
158 self.linebreak = linebreak
159 self.indent = indent
160
161 def GenerateLines(self, base_indent, sub_indent, curr_section):
162 """Generates a properly formatted 'name : value' entry."""
163 report_output = ''
164 if self.name:
165 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
166 value_lines = str(self.value).splitlines()
167 if self.linebreak and self.name:
168 report_output += '\n' + '\n'.join(
169 ['%*s%s' % (self.indent, '', line) for line in value_lines])
170 else:
171 if self.name:
172 report_output += ' '
173 report_output += '%*s' % (self.indent, '')
174 cont_line_indent = len(report_output)
175 indented_value_lines = [value_lines[0]]
176 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
177 for line in value_lines[1:]])
178 report_output += '\n'.join(indented_value_lines)
179
180 report_lines = [self._Indent(base_indent, line + '\n')
181 for line in report_output.split('\n')]
182 return report_lines, curr_section
183
184 class SubReportNode(Node):
185 """A sub-report node, representing a nested report."""
186
187 def __init__(self, title, report):
188 super(_PayloadReport.SubReportNode, self).__init__()
189 self.title = title
190 self.report = report
191
192 def GenerateLines(self, base_indent, sub_indent, curr_section):
193 """Recurse with indentation."""
194 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
195 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
196 sub_indent))
197 return report_lines, curr_section
198
199 class SectionNode(Node):
200 """A section header node."""
201
202 def __init__(self, title=None):
203 super(_PayloadReport.SectionNode, self).__init__()
204 self.title = title
205 self.max_field_name_len = 0
206
207 def GenerateLines(self, base_indent, sub_indent, curr_section):
208 """Dump a title line, return self as the (new) current section."""
209 report_lines = []
210 if self.title:
211 report_lines.append(self._Indent(base_indent,
212 '=== %s ===\n' % self.title))
213 return report_lines, self
214
215 def __init__(self):
216 self.report = []
217 self.last_section = self.global_section = self.SectionNode()
218 self.is_finalized = False
219
220 def GenerateLines(self, base_indent, sub_indent):
221 """Generates the lines in the report, properly indented.
222
223 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700224 base_indent: The indentation used for root-level report lines.
225 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800226
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800227 Returns:
228 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800229 """
230 report_lines = []
231 curr_section = self.global_section
232 for node in self.report:
233 node_report_lines, curr_section = node.GenerateLines(
234 base_indent, sub_indent, curr_section)
235 report_lines.extend(node_report_lines)
236
237 return report_lines
238
239 def Dump(self, out_file, base_indent=0, sub_indent=2):
240 """Dumps the report to a file.
241
242 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700243 out_file: File object to output the content to.
244 base_indent: Base indentation for report lines.
245 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800246 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800247 report_lines = self.GenerateLines(base_indent, sub_indent)
248 if report_lines and not self.is_finalized:
249 report_lines.append('(incomplete report)\n')
250
251 for line in report_lines:
252 out_file.write(line)
253
254 def AddField(self, name, value, linebreak=False, indent=0):
255 """Adds a field/value pair to the payload report.
256
257 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700258 name: The field's name.
259 value: The field's value.
260 linebreak: Whether the value should be printed on a new line.
261 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800262 """
263 assert not self.is_finalized
264 if name and self.last_section.max_field_name_len < len(name):
265 self.last_section.max_field_name_len = len(name)
266 self.report.append(self.FieldNode(name, value, linebreak, indent))
267
268 def AddSubReport(self, title):
269 """Adds and returns a sub-report with a title."""
270 assert not self.is_finalized
271 sub_report = self.SubReportNode(title, type(self)())
272 self.report.append(sub_report)
273 return sub_report.report
274
275 def AddSection(self, title):
276 """Adds a new section title."""
277 assert not self.is_finalized
278 self.last_section = self.SectionNode(title)
279 self.report.append(self.last_section)
280
281 def Finalize(self):
282 """Seals the report, marking it as complete."""
283 self.is_finalized = True
284
285
286#
287# Payload verification.
288#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700289
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800290class PayloadChecker(object):
291 """Checking the integrity of an update payload.
292
293 This is a short-lived object whose purpose is to isolate the logic used for
294 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800295 """
296
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700297 def __init__(self, payload, assert_type=None, block_size=0,
298 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700299 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700300
301 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700302 payload: The payload object to check.
303 assert_type: Assert that payload is either 'full' or 'delta' (optional).
304 block_size: Expected filesystem / payload block size (optional).
305 allow_unhashed: Allow operations with unhashed data blobs.
306 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700307 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700308 if not payload.is_init:
309 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700310
311 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800312 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700313 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
314 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700315 raise error.PayloadError(
316 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700317 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700318 raise error.PayloadError('Invalid assert_type value (%r).' %
319 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700320 self.payload_type = assert_type
321 self.allow_unhashed = allow_unhashed
322
323 # Disable specific tests.
324 self.check_dst_pseudo_extents = (
325 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
326 self.check_move_same_src_dst_block = (
327 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
328 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800329
330 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800331 self.sigs_offset = 0
332 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700333 self.old_rootfs_fs_size = 0
334 self.old_kernel_fs_size = 0
335 self.new_rootfs_fs_size = 0
336 self.new_kernel_fs_size = 0
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700337 self.minor_version = None
Amin Hassani0de7f782017-12-07 12:13:03 -0800338 # TODO(*): When fixing crbug.com/794404, the major version should be
Sen Jiang771f6482018-04-04 17:59:10 -0700339 # correctly handled in update_payload scripts. So stop forcing
Amin Hassani0de7f782017-12-07 12:13:03 -0800340 # major_verions=1 here and set it to the correct value.
341 self.major_version = 1
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800342
343 @staticmethod
344 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
345 msg_name=None, linebreak=False, indent=0):
346 """Adds an element from a protobuf message to the payload report.
347
348 Checks to see whether a message contains a given element, and if so adds
349 the element value to the provided report. A missing mandatory element
350 causes an exception to be raised.
351
352 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700353 msg: The message containing the element.
354 name: The name of the element.
355 report: A report object to add the element name/value to.
356 is_mandatory: Whether or not this element must be present.
357 is_submsg: Whether this element is itself a message.
358 convert: A function for converting the element value for reporting.
359 msg_name: The name of the message object (for error reporting).
360 linebreak: Whether the value report should induce a line break.
361 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800362
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800363 Returns:
364 A pair consisting of the element value and the generated sub-report for
365 it (if the element is a sub-message, None otherwise). If the element is
366 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800367
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800368 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700369 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800370 """
371 if not msg.HasField(name):
372 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700373 raise error.PayloadError('%smissing mandatory %s %r.' %
374 (msg_name + ' ' if msg_name else '',
375 'sub-message' if is_submsg else 'field',
376 name))
377 return None, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800378
379 value = getattr(msg, name)
380 if is_submsg:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700381 return value, report and report.AddSubReport(name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800382 else:
383 if report:
384 report.AddField(name, convert(value), linebreak=linebreak,
385 indent=indent)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700386 return value, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800387
388 @staticmethod
389 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
390 linebreak=False, indent=0):
391 """Adds a mandatory field; returning first component from _CheckElem."""
392 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
393 convert=convert, msg_name=msg_name,
394 linebreak=linebreak, indent=indent)[0]
395
396 @staticmethod
397 def _CheckOptionalField(msg, field_name, report, convert=str,
398 linebreak=False, indent=0):
399 """Adds an optional field; returning first component from _CheckElem."""
400 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
401 convert=convert, linebreak=linebreak,
402 indent=indent)[0]
403
404 @staticmethod
405 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
406 """Adds a mandatory sub-message; wrapper for _CheckElem."""
407 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
408 msg_name)
409
410 @staticmethod
411 def _CheckOptionalSubMsg(msg, submsg_name, report):
412 """Adds an optional sub-message; wrapper for _CheckElem."""
413 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
414
415 @staticmethod
416 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
417 """Checks that val1 is None iff val2 is None.
418
419 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700420 val1: first value to be compared.
421 val2: second value to be compared.
422 name1: name of object holding the first value.
423 name2: name of object holding the second value.
424 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800425
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800426 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700427 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800428 """
429 if None in (val1, val2) and val1 is not val2:
430 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700431 raise error.PayloadError('%r present without %r%s.' %
432 (present, missing,
433 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800434
435 @staticmethod
436 def _Run(cmd, send_data=None):
437 """Runs a subprocess, returns its output.
438
439 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700440 cmd: Sequence of command-line argument for invoking the subprocess.
441 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800442
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800443 Returns:
444 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800445 """
446 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
447 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700448 try:
449 result = run_process.communicate(input=send_data)
450 finally:
451 exit_code = run_process.wait()
452
453 if exit_code:
454 raise RuntimeError('Subprocess %r failed with code %r.' %
455 (cmd, exit_code))
456
457 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800458
459 @staticmethod
460 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
461 """Verifies an actual hash against a signed one.
462
463 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700464 sig_data: The raw signature data.
465 pubkey_file_name: Public key used for verifying signature.
466 actual_hash: The actual hash digest.
467 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800468
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800469 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700470 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800471 """
472 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700473 raise error.PayloadError(
474 '%s: signature size (%d) not as expected (256).' %
475 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800476 signed_data, _ = PayloadChecker._Run(
477 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
478 send_data=sig_data)
479
Gilad Arnold5502b562013-03-08 13:22:31 -0800480 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700481 raise error.PayloadError('%s: unexpected signed data length (%d).' %
482 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800483
Gilad Arnold5502b562013-03-08 13:22:31 -0800484 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700485 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
486 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800487
Gilad Arnold5502b562013-03-08 13:22:31 -0800488 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800489 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700490 raise error.PayloadError(
491 '%s: signed hash (%s) different from actual (%s).' %
492 (sig_name, common.FormatSha256(signed_hash),
493 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800494
495 @staticmethod
496 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
497 block_name=None):
498 """Checks that a given length fits given block space.
499
500 This ensures that the number of blocks allocated is appropriate for the
501 length of the data residing in these blocks.
502
503 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700504 length: The actual length of the data.
505 num_blocks: The number of blocks allocated for it.
506 block_size: The size of each block in bytes.
507 length_name: Name of length (used for error reporting).
508 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800509
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800510 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700511 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800512 """
513 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700514 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700515 raise error.PayloadError(
516 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800517 (length_name, length, block_name or '', num_blocks, block_size))
518
519 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700520 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700521 raise error.PayloadError(
522 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800523 (length_name, length, block_name or '', num_blocks - 1, block_size))
524
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700525 def _CheckManifestMinorVersion(self, report):
526 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800527
528 Args:
529 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800530
531 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700532 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800533 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700534 self.minor_version = self._CheckOptionalField(self.payload.manifest,
535 'minor_version', report)
536 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
537 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800538 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700539 'Minor version %d not compatible with payload type %s.' %
540 (self.minor_version, self.payload_type))
541 elif self.minor_version is None:
542 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800543 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700544 raise error.PayloadError('Unsupported minor version: %d' %
545 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800546
Gilad Arnold382df5c2013-05-03 12:49:28 -0700547 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800548 """Checks the payload manifest.
549
550 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700551 report: A report object to add to.
552 rootfs_part_size: Size of the rootfs partition in bytes.
553 kernel_part_size: Size of the kernel partition in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800554
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800555 Returns:
556 A tuple consisting of the partition block size used during the update
557 (integer), the signatures block offset and size.
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 any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800561 """
562 manifest = self.payload.manifest
563 report.AddSection('manifest')
564
565 # Check: block_size must exist and match the expected value.
566 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
567 report, 'manifest')
568 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700569 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
570 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800571
572 # Check: signatures_offset <==> signatures_size.
573 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
574 report)
575 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
576 report)
577 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
578 'signatures_offset', 'signatures_size', 'manifest')
579
580 # Check: old_kernel_info <==> old_rootfs_info.
581 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
582 'old_kernel_info', report)
583 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
584 'old_rootfs_info', report)
585 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
586 'old_rootfs_info', 'manifest')
587 if oki_msg: # equivalently, ori_msg
588 # Assert/mark delta payload.
589 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700590 raise error.PayloadError(
591 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800592 self.payload_type = _TYPE_DELTA
593
594 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700595 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800596 oki_msg, 'size', oki_report, 'old_kernel_info')
597 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
598 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700599 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800600 ori_msg, 'size', ori_report, 'old_rootfs_info')
601 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
602 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700603
604 # Check: old_{kernel,rootfs} size must fit in respective partition.
605 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700606 raise error.PayloadError(
607 'Old kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700608 (self.old_kernel_fs_size, kernel_part_size))
609 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700610 raise error.PayloadError(
611 'Old rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700612 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800613 else:
614 # Assert/mark full payload.
615 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700616 raise error.PayloadError(
617 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800618 self.payload_type = _TYPE_FULL
619
620 # Check: new_kernel_info present; contains {size, hash}.
621 nki_msg, nki_report = self._CheckMandatorySubMsg(
622 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700623 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800624 nki_msg, 'size', nki_report, 'new_kernel_info')
625 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
626 convert=common.FormatSha256)
627
628 # Check: new_rootfs_info present; contains {size, hash}.
629 nri_msg, nri_report = self._CheckMandatorySubMsg(
630 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700631 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800632 nri_msg, 'size', nri_report, 'new_rootfs_info')
633 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
634 convert=common.FormatSha256)
635
Gilad Arnold382df5c2013-05-03 12:49:28 -0700636 # Check: new_{kernel,rootfs} size must fit in respective partition.
637 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700638 raise error.PayloadError(
639 'New kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700640 (self.new_kernel_fs_size, kernel_part_size))
641 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700642 raise error.PayloadError(
643 'New rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700644 (self.new_rootfs_fs_size, rootfs_part_size))
645
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800646 # Check: minor_version makes sense for the payload type. This check should
647 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700648 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800649
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800650 def _CheckLength(self, length, total_blocks, op_name, length_name):
651 """Checks whether a length matches the space designated in extents.
652
653 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700654 length: The total length of the data.
655 total_blocks: The total number of blocks in extents.
656 op_name: Operation name (for error reporting).
657 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800658
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800659 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700660 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800661 """
662 # Check: length is non-zero.
663 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700664 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800665
666 # Check that length matches number of blocks.
667 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
668 '%s: %s' % (op_name, length_name))
669
Gilad Arnold382df5c2013-05-03 12:49:28 -0700670 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800671 allow_pseudo=False, allow_signature=False):
672 """Checks a sequence of extents.
673
674 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700675 extents: The sequence of extents to check.
676 usable_size: The usable size of the partition to which the extents apply.
677 block_counters: Array of counters corresponding to the number of blocks.
678 name: The name of the extent block.
679 allow_pseudo: Whether or not pseudo block numbers are allowed.
680 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800681
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800682 Returns:
683 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800684
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800685 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700686 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800687 """
688 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800689 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700690 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800691 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
692 None, ex_name)
693 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
694 ex_name)
695 end_block = start_block + num_blocks
696
697 # Check: num_blocks > 0.
698 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700699 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800700
701 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700702 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700703 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700704 raise error.PayloadError(
705 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700706 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800707
708 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700709 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800710 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800711 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
712 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
713 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700714 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800715
716 total_num_blocks += num_blocks
717
718 return total_num_blocks
719
720 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0de7f782017-12-07 12:13:03 -0800721 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800722
723 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700724 op: The operation object from the manifest.
725 data_length: The length of the data blob associated with the operation.
726 total_dst_blocks: Total number of blocks in dst_extents.
727 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800728
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800729 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700730 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800731 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700732 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800733 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700734 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800735
Gilad Arnoldcb638912013-06-24 04:57:11 -0700736 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800737 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700738 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800739
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800740 if op.type == common.OpType.REPLACE:
741 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
742 self.block_size,
743 op_name + '.data_length', 'dst')
744 else:
Sen Jiang771f6482018-04-04 17:59:10 -0700745 # Check: data_length must be smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800746 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700747 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800748 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700749 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800750 (op_name, data_length, total_dst_blocks, self.block_size))
751
752 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
753 total_dst_blocks, op_name):
754 """Specific checks for MOVE operations.
755
756 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700757 op: The operation object from the manifest.
758 data_offset: The offset of a data blob for the operation.
759 total_src_blocks: Total number of blocks in src_extents.
760 total_dst_blocks: Total number of blocks in dst_extents.
761 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800762
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800763 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700764 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800765 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700766 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800767 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700768 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800769
Gilad Arnoldcb638912013-06-24 04:57:11 -0700770 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800771 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700772 raise error.PayloadError(
773 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800774 (op_name, total_src_blocks, total_dst_blocks))
775
Gilad Arnoldcb638912013-06-24 04:57:11 -0700776 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800777 i = 0
778 src_extent_iter = iter(op.src_extents)
779 dst_extent_iter = iter(op.dst_extents)
780 src_extent = dst_extent = None
781 src_idx = src_num = dst_idx = dst_num = 0
782 while i < total_src_blocks:
783 # Get the next source extent, if needed.
784 if not src_extent:
785 try:
786 src_extent = src_extent_iter.next()
787 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700788 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
789 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800790 src_idx = src_extent.start_block
791 src_num = src_extent.num_blocks
792
793 # Get the next dest extent, if needed.
794 if not dst_extent:
795 try:
796 dst_extent = dst_extent_iter.next()
797 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700798 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
799 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800800 dst_idx = dst_extent.start_block
801 dst_num = dst_extent.num_blocks
802
Allie Woodb065e132015-04-24 10:20:27 -0700803 # Check: start block is not 0. See crbug/480751; there are still versions
804 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
805 # so we need to fail payloads that try to MOVE to/from block 0.
806 if src_idx == 0 or dst_idx == 0:
807 raise error.PayloadError(
808 '%s: MOVE operation cannot have extent with start block 0' %
809 op_name)
810
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700811 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700812 raise error.PayloadError(
813 '%s: src/dst block number %d is the same (%d).' %
814 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800815
816 advance = min(src_num, dst_num)
817 i += advance
818
819 src_idx += advance
820 src_num -= advance
821 if src_num == 0:
822 src_extent = None
823
824 dst_idx += advance
825 dst_num -= advance
826 if dst_num == 0:
827 dst_extent = None
828
Gilad Arnold5502b562013-03-08 13:22:31 -0800829 # Make sure we've exhausted all src/dst extents.
830 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700831 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800832 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700833 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800834
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700835 def _CheckZeroOperation(self, op, op_name):
836 """Specific checks for ZERO operations.
837
838 Args:
839 op: The operation object from the manifest.
840 op_name: Operation name for error reporting.
841
842 Raises:
843 error.PayloadError if any check fails.
844 """
845 # Check: Does not contain src extents, data_length and data_offset.
846 if op.src_extents:
847 raise error.PayloadError('%s: contains src_extents.' % op_name)
848 if op.data_length:
849 raise error.PayloadError('%s: contains data_length.' % op_name)
850 if op.data_offset:
851 raise error.PayloadError('%s: contains data_offset.' % op_name)
852
Amin Hassaniefa62d92017-11-09 13:46:56 -0800853 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
854 """Specific checks for BSDIFF, SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
855 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800856
857 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800858 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700859 data_length: The length of the data blob associated with the operation.
860 total_dst_blocks: Total number of blocks in dst_extents.
861 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800862
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800863 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700864 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800865 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800866 # Check: data_{offset,length} present.
867 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700868 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800869
Sen Jiang771f6482018-04-04 17:59:10 -0700870 # Check: data_length is strictly smaller than the allotted dst blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800871 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700872 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800873 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700874 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800875 (op_name, data_length, total_dst_blocks, self.block_size,
876 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800877
Amin Hassaniefa62d92017-11-09 13:46:56 -0800878 # Check the existence of src_length and dst_length for legacy bsdiffs.
879 if (op.type == common.OpType.BSDIFF or
880 (op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3)):
881 if not op.HasField('src_length') or not op.HasField('dst_length'):
882 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
883 else:
884 if op.HasField('src_length') or op.HasField('dst_length'):
885 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
886
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800887 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
888 total_dst_blocks, op_name):
889 """Specific checks for SOURCE_COPY.
890
891 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800892 data_offset: The offset of a data blob for the operation.
893 total_src_blocks: Total number of blocks in src_extents.
894 total_dst_blocks: Total number of blocks in dst_extents.
895 op_name: Operation name for error reporting.
896
897 Raises:
898 error.PayloadError if any check fails.
899 """
900 # Check: No data_{offset,length}.
901 if data_offset is not None:
902 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
903
904 # Check: total_src_blocks == total_dst_blocks.
905 if total_src_blocks != total_dst_blocks:
906 raise error.PayloadError(
907 '%s: total src blocks (%d) != total dst blocks (%d).' %
908 (op_name, total_src_blocks, total_dst_blocks))
909
Sen Jiangd6122bb2015-12-11 10:27:04 -0800910 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800911 """Specific checks for SOURCE_* operations.
912
913 Args:
914 op: The operation object from the manifest.
915 total_src_blocks: Total number of blocks in src_extents.
916 op_name: Operation name for error reporting.
917
918 Raises:
919 error.PayloadError if any check fails.
920 """
921 # Check: total_src_blocks != 0.
922 if total_src_blocks == 0:
923 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
924
Sen Jiang92161a72016-06-28 16:09:38 -0700925 # Check: src_sha256_hash present in minor version >= 3.
926 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800927 raise error.PayloadError('%s: source hash missing.' % op_name)
928
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800929 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700930 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700931 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800932 """Checks a single update operation.
933
934 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700935 op: The operation object.
936 op_name: Operation name string for error reporting.
937 is_last: Whether this is the last operation in the sequence.
938 old_block_counters: Arrays of block read counters.
939 new_block_counters: Arrays of block write counters.
940 old_usable_size: The overall usable size for src data in bytes.
941 new_usable_size: The overall usable size for dst data in bytes.
942 prev_data_offset: Offset of last used data bytes.
943 allow_signature: Whether this may be a signature operation.
944 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800945
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800946 Returns:
947 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800948
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800949 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700950 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800951 """
952 # Check extents.
953 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700954 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800955 op_name + '.src_extents', allow_pseudo=True)
956 allow_signature_in_extents = (allow_signature and is_last and
957 op.type == common.OpType.REPLACE)
958 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700959 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700960 op_name + '.dst_extents',
961 allow_pseudo=(not self.check_dst_pseudo_extents),
962 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800963
964 # Check: data_offset present <==> data_length present.
965 data_offset = self._CheckOptionalField(op, 'data_offset', None)
966 data_length = self._CheckOptionalField(op, 'data_length', None)
967 self._CheckPresentIff(data_offset, data_length, 'data_offset',
968 'data_length', op_name)
969
Gilad Arnoldcb638912013-06-24 04:57:11 -0700970 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800971 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700972 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800973
974 # Check {src,dst}_length, if present.
975 if op.HasField('src_length'):
976 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
977 if op.HasField('dst_length'):
978 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
979
980 if op.HasField('data_sha256_hash'):
981 blob_hash_counts['hashed'] += 1
982
Gilad Arnoldcb638912013-06-24 04:57:11 -0700983 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800984 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700985 raise error.PayloadError(
986 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800987 op_name)
988
Gilad Arnoldcb638912013-06-24 04:57:11 -0700989 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800990 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
991 data_length))
992 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700993 raise error.PayloadError(
994 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700995 (op_name, common.FormatSha256(op.data_sha256_hash),
996 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800997 elif data_offset is not None:
998 if allow_signature_in_extents:
999 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001000 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001001 blob_hash_counts['unhashed'] += 1
1002 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001003 raise error.PayloadError('%s: unhashed operation not allowed.' %
1004 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001005
1006 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001007 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001008 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001009 raise error.PayloadError(
1010 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001011 (op_name, data_offset, prev_data_offset))
1012
1013 # Type-specific checks.
1014 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
1015 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani0de7f782017-12-07 12:13:03 -08001016 elif op.type == common.OpType.REPLACE_XZ and (self.minor_version >= 3 or
1017 self.major_version >= 2):
1018 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001019 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001020 self._CheckMoveOperation(op, data_offset, total_src_blocks,
1021 total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001022 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
1023 self._CheckZeroOperation(op, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001024 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
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 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001027 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
1028 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001029 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001030 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001031 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001032 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassani77d7cbc2018-02-07 16:21:33 -08001033 elif op.type == common.OpType.BROTLI_BSDIFF and self.minor_version >= 4:
1034 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
1035 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
1036 elif op.type == common.OpType.PUFFDIFF and self.minor_version >= 5:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001037 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001038 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001039 else:
Allie Wood7cf9f132015-02-26 14:28:19 -08001040 raise error.PayloadError(
1041 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001042 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001043 return data_length if data_length is not None else 0
1044
Gilad Arnold382df5c2013-05-03 12:49:28 -07001045 def _SizeToNumBlocks(self, size):
1046 """Returns the number of blocks needed to contain a given byte size."""
1047 return (size + self.block_size - 1) / self.block_size
1048
1049 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001050 """Returns a freshly initialized array of block counters.
1051
Gilad Arnoldcb638912013-06-24 04:57:11 -07001052 Note that the generated array is not portable as is due to byte-ordering
1053 issues, hence it should not be serialized.
1054
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001055 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001056 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001057
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001058 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001059 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001060 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001061 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001062 return array.array('H',
1063 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001064
Gilad Arnold382df5c2013-05-03 12:49:28 -07001065 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001066 new_fs_size, old_usable_size, new_usable_size,
1067 prev_data_offset, allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001068 """Checks a sequence of update operations.
1069
1070 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001071 operations: The sequence of operations to check.
1072 report: The report object to add to.
1073 base_name: The name of the operation block.
1074 old_fs_size: The old filesystem size in bytes.
1075 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001076 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001077 new_usable_size: The overall usable size of the new partition in bytes.
1078 prev_data_offset: Offset of last used data bytes.
1079 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001080
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001081 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001082 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001083
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001084 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001085 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001086 """
1087 # The total size of data blobs used by operations scanned thus far.
1088 total_data_used = 0
1089 # Counts of specific operation types.
1090 op_counts = {
1091 common.OpType.REPLACE: 0,
1092 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001093 common.OpType.REPLACE_XZ: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001094 common.OpType.MOVE: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001095 common.OpType.ZERO: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001096 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001097 common.OpType.SOURCE_COPY: 0,
1098 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001099 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001100 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001101 }
1102 # Total blob sizes for each operation type.
1103 op_blob_totals = {
1104 common.OpType.REPLACE: 0,
1105 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001106 common.OpType.REPLACE_XZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001107 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001108 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001109 # SOURCE_COPY operations don't have blobs.
1110 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001111 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001112 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001113 }
1114 # Counts of hashed vs unhashed operations.
1115 blob_hash_counts = {
1116 'hashed': 0,
1117 'unhashed': 0,
1118 }
1119 if allow_signature:
1120 blob_hash_counts['signature'] = 0
1121
1122 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001123 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001124 if old_fs_size else None)
1125 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001126
1127 # Process and verify each operation.
1128 op_num = 0
1129 for op, op_name in common.OperationIter(operations, base_name):
1130 op_num += 1
1131
Gilad Arnoldcb638912013-06-24 04:57:11 -07001132 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001133 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001134 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001135 op_counts[op.type] += 1
1136
1137 is_last = op_num == len(operations)
1138 curr_data_used = self._CheckOperation(
1139 op, op_name, is_last, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001140 old_usable_size, new_usable_size,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001141 prev_data_offset + total_data_used, allow_signature,
1142 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001143 if curr_data_used:
1144 op_blob_totals[op.type] += curr_data_used
1145 total_data_used += curr_data_used
1146
1147 # Report totals and breakdown statistics.
1148 report.AddField('total operations', op_num)
1149 report.AddField(
1150 None,
1151 histogram.Histogram.FromCountDict(op_counts,
1152 key_names=common.OpType.NAMES),
1153 indent=1)
1154 report.AddField('total blobs', sum(blob_hash_counts.values()))
1155 report.AddField(None,
1156 histogram.Histogram.FromCountDict(blob_hash_counts),
1157 indent=1)
1158 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1159 report.AddField(
1160 None,
1161 histogram.Histogram.FromCountDict(op_blob_totals,
1162 formatter=_AddHumanReadableSize,
1163 key_names=common.OpType.NAMES),
1164 indent=1)
1165
1166 # Report read/write histograms.
1167 if old_block_counters:
1168 report.AddField('block read hist',
1169 histogram.Histogram.FromKeyList(old_block_counters),
1170 linebreak=True, indent=1)
1171
Gilad Arnold382df5c2013-05-03 12:49:28 -07001172 new_write_hist = histogram.Histogram.FromKeyList(
1173 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1174 report.AddField('block write hist', new_write_hist, linebreak=True,
1175 indent=1)
1176
Gilad Arnoldcb638912013-06-24 04:57:11 -07001177 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001178 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001179 raise error.PayloadError(
1180 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001181 base_name)
1182
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001183 return total_data_used
1184
1185 def _CheckSignatures(self, report, pubkey_file_name):
1186 """Checks a payload's signature block."""
1187 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1188 sigs = update_metadata_pb2.Signatures()
1189 sigs.ParseFromString(sigs_raw)
1190 report.AddSection('signatures')
1191
Gilad Arnoldcb638912013-06-24 04:57:11 -07001192 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001193 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001194 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001195
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001196 last_ops_section = (self.payload.manifest.kernel_install_operations or
1197 self.payload.manifest.install_operations)
1198 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001199 # Check: signatures_{offset,size} must match the last (fake) operation.
1200 if not (fake_sig_op.type == common.OpType.REPLACE and
1201 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001202 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001203 raise error.PayloadError(
1204 'Signatures_{offset,size} (%d+%d) does not match last operation '
1205 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001206 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1207 fake_sig_op.data_length))
1208
1209 # Compute the checksum of all data up to signature blob.
1210 # TODO(garnold) we're re-reading the whole data section into a string
1211 # just to compute the checksum; instead, we could do it incrementally as
1212 # we read the blobs one-by-one, under the assumption that we're reading
1213 # them in order (which currently holds). This should be reconsidered.
1214 payload_hasher = self.payload.manifest_hasher.copy()
1215 common.Read(self.payload.payload_file, self.sigs_offset,
1216 offset=self.payload.data_offset, hasher=payload_hasher)
1217
1218 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1219 sig_report = report.AddSubReport(sig_name)
1220
Gilad Arnoldcb638912013-06-24 04:57:11 -07001221 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001222 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1223 self._CheckMandatoryField(sig, 'data', None, sig_name)
1224 sig_report.AddField('data len', len(sig.data))
1225
Gilad Arnoldcb638912013-06-24 04:57:11 -07001226 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001227 if sig.version == 1:
1228 self._CheckSha256Signature(sig.data, pubkey_file_name,
1229 payload_hasher.digest(), sig_name)
1230 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001231 raise error.PayloadError('Unknown signature version (%d).' %
1232 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001233
1234 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001235 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001236 """Checker entry point, invoking all checks.
1237
1238 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001239 pubkey_file_name: Public key used for signature verification.
1240 metadata_sig_file: Metadata signature, if verification is desired.
Gilad Arnold06eea332015-07-13 18:06:33 -07001241 rootfs_part_size: The size of rootfs partitions in bytes (default: infer
1242 based on payload type and version).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001243 kernel_part_size: The size of kernel partitions in bytes (default: use
1244 reported filesystem size).
1245 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001246
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001247 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001248 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001249 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001250 if not pubkey_file_name:
1251 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1252
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001253 report = _PayloadReport()
1254
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001255 # Get payload file size.
1256 self.payload.payload_file.seek(0, 2)
1257 payload_file_size = self.payload.payload_file.tell()
1258 self.payload.ResetFile()
1259
1260 try:
1261 # Check metadata signature (if provided).
1262 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001263 metadata_sig = base64.b64decode(metadata_sig_file.read())
1264 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1265 self.payload.manifest_hasher.digest(),
1266 'metadata signature')
1267
Gilad Arnoldcb638912013-06-24 04:57:11 -07001268 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001269 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001270 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001271 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001272 raise error.PayloadError('Unknown payload version (%d).' %
1273 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001274 report.AddField('version', self.payload.header.version)
1275 report.AddField('manifest len', self.payload.header.manifest_len)
1276
Gilad Arnoldcb638912013-06-24 04:57:11 -07001277 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001278 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001279 assert self.payload_type, 'payload type should be known by now'
1280
Gilad Arnold06eea332015-07-13 18:06:33 -07001281 # Infer the usable partition size when validating rootfs operations:
1282 # - If rootfs partition size was provided, use that.
1283 # - Otherwise, if this is an older delta (minor version < 2), stick with
1284 # a known constant size. This is necessary because older deltas may
1285 # exceed the filesystem size when moving data blocks around.
1286 # - Otherwise, use the encoded filesystem size.
1287 new_rootfs_usable_size = self.new_rootfs_fs_size
Amin Hassaniae853742017-10-11 10:27:27 -07001288 old_rootfs_usable_size = self.old_rootfs_fs_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001289 if rootfs_part_size:
1290 new_rootfs_usable_size = rootfs_part_size
Amin Hassaniae853742017-10-11 10:27:27 -07001291 old_rootfs_usable_size = rootfs_part_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001292 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
1293 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Amin Hassaniae853742017-10-11 10:27:27 -07001294 old_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Gilad Arnold06eea332015-07-13 18:06:33 -07001295
Gilad Arnoldcb638912013-06-24 04:57:11 -07001296 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001297 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1298 # no explicit size provided *and* the partition size is not embedded in
1299 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001300 report.AddSection('rootfs operations')
1301 total_blob_size = self._CheckOperations(
1302 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001303 'install_operations', self.old_rootfs_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001304 self.new_rootfs_fs_size, old_rootfs_usable_size,
1305 new_rootfs_usable_size, 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001306
Gilad Arnoldcb638912013-06-24 04:57:11 -07001307 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001308 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001309 report.AddSection('kernel operations')
1310 total_blob_size += self._CheckOperations(
1311 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001312 'kernel_install_operations', self.old_kernel_fs_size,
1313 self.new_kernel_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001314 kernel_part_size if kernel_part_size else self.old_kernel_fs_size,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001315 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1316 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001317
Gilad Arnoldcb638912013-06-24 04:57:11 -07001318 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001319 used_payload_size = self.payload.data_offset + total_blob_size
1320 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001321 raise error.PayloadError(
1322 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001323 (used_payload_size, payload_file_size))
1324
Gilad Arnoldcb638912013-06-24 04:57:11 -07001325 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001326 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001327 self._CheckSignatures(report, pubkey_file_name)
1328
Gilad Arnoldcb638912013-06-24 04:57:11 -07001329 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001330 report.AddSection('summary')
1331 report.AddField('update type', self.payload_type)
1332
1333 report.Finalize()
1334 finally:
1335 if report_out_file:
1336 report.Dump(report_out_file)