blob: db170119e978515de30bb5e669cb62ce17f67411 [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,),
Gilad Arnold0d575cd2015-07-13 17:29:21 -070072}
Gilad Arnold553b0ec2013-01-26 01:00:39 -080073
Gilad Arnold06eea332015-07-13 18:06:33 -070074_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
75
Gilad Arnold553b0ec2013-01-26 01:00:39 -080076#
77# Helper functions.
78#
Gilad Arnoldcb638912013-06-24 04:57:11 -070079
Gilad Arnold553b0ec2013-01-26 01:00:39 -080080def _IsPowerOfTwo(val):
81 """Returns True iff val is a power of two."""
82 return val > 0 and (val & (val - 1)) == 0
83
84
85def _AddFormat(format_func, value):
86 """Adds a custom formatted representation to ordinary string representation.
87
88 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070089 format_func: A value formatter.
90 value: Value to be formatted and returned.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -080091
Gilad Arnold553b0ec2013-01-26 01:00:39 -080092 Returns:
93 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080094 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070095 ret = str(value)
96 formatted_str = format_func(value)
97 if formatted_str:
98 ret += ' (%s)' % formatted_str
99 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800100
101
102def _AddHumanReadableSize(size):
103 """Adds a human readable representation to a byte size value."""
104 return _AddFormat(format_utils.BytesToHumanReadable, size)
105
106
107#
108# Payload report generator.
109#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700110
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800111class _PayloadReport(object):
112 """A payload report generator.
113
114 A report is essentially a sequence of nodes, which represent data points. It
115 is initialized to have a "global", untitled section. A node may be a
116 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800117 """
118
Gilad Arnoldcb638912013-06-24 04:57:11 -0700119 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800120 class Node(object):
121 """A report node interface."""
122
123 @staticmethod
124 def _Indent(indent, line):
125 """Indents a line by a given indentation amount.
126
127 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700128 indent: The indentation amount.
129 line: The line content (string).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800130
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800131 Returns:
132 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800133 """
134 return '%*s%s' % (indent, '', line)
135
136 def GenerateLines(self, base_indent, sub_indent, curr_section):
137 """Generates the report lines for this node.
138
139 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700140 base_indent: Base indentation for each line.
141 sub_indent: Additional indentation for sub-nodes.
142 curr_section: The current report section object.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800143
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800144 Returns:
145 A pair consisting of a list of properly indented report lines and a new
146 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800147 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700148 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800149
150 class FieldNode(Node):
151 """A field report node, representing a (name, value) pair."""
152
153 def __init__(self, name, value, linebreak, indent):
154 super(_PayloadReport.FieldNode, self).__init__()
155 self.name = name
156 self.value = value
157 self.linebreak = linebreak
158 self.indent = indent
159
160 def GenerateLines(self, base_indent, sub_indent, curr_section):
161 """Generates a properly formatted 'name : value' entry."""
162 report_output = ''
163 if self.name:
164 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
165 value_lines = str(self.value).splitlines()
166 if self.linebreak and self.name:
167 report_output += '\n' + '\n'.join(
168 ['%*s%s' % (self.indent, '', line) for line in value_lines])
169 else:
170 if self.name:
171 report_output += ' '
172 report_output += '%*s' % (self.indent, '')
173 cont_line_indent = len(report_output)
174 indented_value_lines = [value_lines[0]]
175 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
176 for line in value_lines[1:]])
177 report_output += '\n'.join(indented_value_lines)
178
179 report_lines = [self._Indent(base_indent, line + '\n')
180 for line in report_output.split('\n')]
181 return report_lines, curr_section
182
183 class SubReportNode(Node):
184 """A sub-report node, representing a nested report."""
185
186 def __init__(self, title, report):
187 super(_PayloadReport.SubReportNode, self).__init__()
188 self.title = title
189 self.report = report
190
191 def GenerateLines(self, base_indent, sub_indent, curr_section):
192 """Recurse with indentation."""
193 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
194 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
195 sub_indent))
196 return report_lines, curr_section
197
198 class SectionNode(Node):
199 """A section header node."""
200
201 def __init__(self, title=None):
202 super(_PayloadReport.SectionNode, self).__init__()
203 self.title = title
204 self.max_field_name_len = 0
205
206 def GenerateLines(self, base_indent, sub_indent, curr_section):
207 """Dump a title line, return self as the (new) current section."""
208 report_lines = []
209 if self.title:
210 report_lines.append(self._Indent(base_indent,
211 '=== %s ===\n' % self.title))
212 return report_lines, self
213
214 def __init__(self):
215 self.report = []
216 self.last_section = self.global_section = self.SectionNode()
217 self.is_finalized = False
218
219 def GenerateLines(self, base_indent, sub_indent):
220 """Generates the lines in the report, properly indented.
221
222 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700223 base_indent: The indentation used for root-level report lines.
224 sub_indent: The indentation offset used for sub-reports.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800225
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800226 Returns:
227 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800228 """
229 report_lines = []
230 curr_section = self.global_section
231 for node in self.report:
232 node_report_lines, curr_section = node.GenerateLines(
233 base_indent, sub_indent, curr_section)
234 report_lines.extend(node_report_lines)
235
236 return report_lines
237
238 def Dump(self, out_file, base_indent=0, sub_indent=2):
239 """Dumps the report to a file.
240
241 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700242 out_file: File object to output the content to.
243 base_indent: Base indentation for report lines.
244 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800245 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800246 report_lines = self.GenerateLines(base_indent, sub_indent)
247 if report_lines and not self.is_finalized:
248 report_lines.append('(incomplete report)\n')
249
250 for line in report_lines:
251 out_file.write(line)
252
253 def AddField(self, name, value, linebreak=False, indent=0):
254 """Adds a field/value pair to the payload report.
255
256 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700257 name: The field's name.
258 value: The field's value.
259 linebreak: Whether the value should be printed on a new line.
260 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800261 """
262 assert not self.is_finalized
263 if name and self.last_section.max_field_name_len < len(name):
264 self.last_section.max_field_name_len = len(name)
265 self.report.append(self.FieldNode(name, value, linebreak, indent))
266
267 def AddSubReport(self, title):
268 """Adds and returns a sub-report with a title."""
269 assert not self.is_finalized
270 sub_report = self.SubReportNode(title, type(self)())
271 self.report.append(sub_report)
272 return sub_report.report
273
274 def AddSection(self, title):
275 """Adds a new section title."""
276 assert not self.is_finalized
277 self.last_section = self.SectionNode(title)
278 self.report.append(self.last_section)
279
280 def Finalize(self):
281 """Seals the report, marking it as complete."""
282 self.is_finalized = True
283
284
285#
286# Payload verification.
287#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700288
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800289class PayloadChecker(object):
290 """Checking the integrity of an update payload.
291
292 This is a short-lived object whose purpose is to isolate the logic used for
293 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800294 """
295
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700296 def __init__(self, payload, assert_type=None, block_size=0,
297 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700298 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700299
300 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700301 payload: The payload object to check.
302 assert_type: Assert that payload is either 'full' or 'delta' (optional).
303 block_size: Expected filesystem / payload block size (optional).
304 allow_unhashed: Allow operations with unhashed data blobs.
305 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700306 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700307 if not payload.is_init:
308 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700309
310 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800311 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700312 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
313 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700314 raise error.PayloadError(
315 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700316 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700317 raise error.PayloadError('Invalid assert_type value (%r).' %
318 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700319 self.payload_type = assert_type
320 self.allow_unhashed = allow_unhashed
321
322 # Disable specific tests.
323 self.check_dst_pseudo_extents = (
324 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
325 self.check_move_same_src_dst_block = (
326 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
327 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800328
329 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800330 self.sigs_offset = 0
331 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700332 self.old_rootfs_fs_size = 0
333 self.old_kernel_fs_size = 0
334 self.new_rootfs_fs_size = 0
335 self.new_kernel_fs_size = 0
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700336 self.minor_version = None
Amin Hassani0de7f782017-12-07 12:13:03 -0800337 # TODO(*): When fixing crbug.com/794404, the major version should be
338 # correclty handled in update_payload scripts. So stop forcing
339 # major_verions=1 here and set it to the correct value.
340 self.major_version = 1
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800341
342 @staticmethod
343 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
344 msg_name=None, linebreak=False, indent=0):
345 """Adds an element from a protobuf message to the payload report.
346
347 Checks to see whether a message contains a given element, and if so adds
348 the element value to the provided report. A missing mandatory element
349 causes an exception to be raised.
350
351 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700352 msg: The message containing the element.
353 name: The name of the element.
354 report: A report object to add the element name/value to.
355 is_mandatory: Whether or not this element must be present.
356 is_submsg: Whether this element is itself a message.
357 convert: A function for converting the element value for reporting.
358 msg_name: The name of the message object (for error reporting).
359 linebreak: Whether the value report should induce a line break.
360 indent: Amount of indent used for reporting the value.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800361
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800362 Returns:
363 A pair consisting of the element value and the generated sub-report for
364 it (if the element is a sub-message, None otherwise). If the element is
365 missing, returns (None, None).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800366
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800367 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700368 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800369 """
370 if not msg.HasField(name):
371 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700372 raise error.PayloadError('%smissing mandatory %s %r.' %
373 (msg_name + ' ' if msg_name else '',
374 'sub-message' if is_submsg else 'field',
375 name))
376 return None, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800377
378 value = getattr(msg, name)
379 if is_submsg:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700380 return value, report and report.AddSubReport(name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800381 else:
382 if report:
383 report.AddField(name, convert(value), linebreak=linebreak,
384 indent=indent)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700385 return value, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800386
387 @staticmethod
388 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
389 linebreak=False, indent=0):
390 """Adds a mandatory field; returning first component from _CheckElem."""
391 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
392 convert=convert, msg_name=msg_name,
393 linebreak=linebreak, indent=indent)[0]
394
395 @staticmethod
396 def _CheckOptionalField(msg, field_name, report, convert=str,
397 linebreak=False, indent=0):
398 """Adds an optional field; returning first component from _CheckElem."""
399 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
400 convert=convert, linebreak=linebreak,
401 indent=indent)[0]
402
403 @staticmethod
404 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
405 """Adds a mandatory sub-message; wrapper for _CheckElem."""
406 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
407 msg_name)
408
409 @staticmethod
410 def _CheckOptionalSubMsg(msg, submsg_name, report):
411 """Adds an optional sub-message; wrapper for _CheckElem."""
412 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
413
414 @staticmethod
415 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
416 """Checks that val1 is None iff val2 is None.
417
418 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700419 val1: first value to be compared.
420 val2: second value to be compared.
421 name1: name of object holding the first value.
422 name2: name of object holding the second value.
423 obj_name: Name of the object containing these values.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800424
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800425 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700426 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800427 """
428 if None in (val1, val2) and val1 is not val2:
429 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700430 raise error.PayloadError('%r present without %r%s.' %
431 (present, missing,
432 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800433
434 @staticmethod
435 def _Run(cmd, send_data=None):
436 """Runs a subprocess, returns its output.
437
438 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700439 cmd: Sequence of command-line argument for invoking the subprocess.
440 send_data: Data to feed to the process via its stdin.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800441
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800442 Returns:
443 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800444 """
445 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
446 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700447 try:
448 result = run_process.communicate(input=send_data)
449 finally:
450 exit_code = run_process.wait()
451
452 if exit_code:
453 raise RuntimeError('Subprocess %r failed with code %r.' %
454 (cmd, exit_code))
455
456 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800457
458 @staticmethod
459 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
460 """Verifies an actual hash against a signed one.
461
462 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700463 sig_data: The raw signature data.
464 pubkey_file_name: Public key used for verifying signature.
465 actual_hash: The actual hash digest.
466 sig_name: Signature name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800467
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800468 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700469 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800470 """
471 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700472 raise error.PayloadError(
473 '%s: signature size (%d) not as expected (256).' %
474 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800475 signed_data, _ = PayloadChecker._Run(
476 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
477 send_data=sig_data)
478
Gilad Arnold5502b562013-03-08 13:22:31 -0800479 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700480 raise error.PayloadError('%s: unexpected signed data length (%d).' %
481 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800482
Gilad Arnold5502b562013-03-08 13:22:31 -0800483 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700484 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
485 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800486
Gilad Arnold5502b562013-03-08 13:22:31 -0800487 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800488 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700489 raise error.PayloadError(
490 '%s: signed hash (%s) different from actual (%s).' %
491 (sig_name, common.FormatSha256(signed_hash),
492 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800493
494 @staticmethod
495 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
496 block_name=None):
497 """Checks that a given length fits given block space.
498
499 This ensures that the number of blocks allocated is appropriate for the
500 length of the data residing in these blocks.
501
502 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700503 length: The actual length of the data.
504 num_blocks: The number of blocks allocated for it.
505 block_size: The size of each block in bytes.
506 length_name: Name of length (used for error reporting).
507 block_name: Name of block (used for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800508
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800509 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700510 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800511 """
512 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700513 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700514 raise error.PayloadError(
515 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800516 (length_name, length, block_name or '', num_blocks, block_size))
517
518 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700519 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700520 raise error.PayloadError(
521 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800522 (length_name, length, block_name or '', num_blocks - 1, block_size))
523
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700524 def _CheckManifestMinorVersion(self, report):
525 """Checks the payload manifest minor_version field.
Allie Wood7cf9f132015-02-26 14:28:19 -0800526
527 Args:
528 report: The report object to add to.
Allie Wood7cf9f132015-02-26 14:28:19 -0800529
530 Raises:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700531 error.PayloadError if any of the checks fail.
Allie Wood7cf9f132015-02-26 14:28:19 -0800532 """
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700533 self.minor_version = self._CheckOptionalField(self.payload.manifest,
534 'minor_version', report)
535 if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
536 if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
Allie Wood7cf9f132015-02-26 14:28:19 -0800537 raise error.PayloadError(
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700538 'Minor version %d not compatible with payload type %s.' %
539 (self.minor_version, self.payload_type))
540 elif self.minor_version is None:
541 raise error.PayloadError('Minor version is not set.')
Allie Wood7cf9f132015-02-26 14:28:19 -0800542 else:
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700543 raise error.PayloadError('Unsupported minor version: %d' %
544 self.minor_version)
Allie Wood7cf9f132015-02-26 14:28:19 -0800545
Gilad Arnold382df5c2013-05-03 12:49:28 -0700546 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800547 """Checks the payload manifest.
548
549 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700550 report: A report object to add to.
551 rootfs_part_size: Size of the rootfs partition in bytes.
552 kernel_part_size: Size of the kernel partition in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800553
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800554 Returns:
555 A tuple consisting of the partition block size used during the update
556 (integer), the signatures block offset and size.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800557
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800558 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700559 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800560 """
561 manifest = self.payload.manifest
562 report.AddSection('manifest')
563
564 # Check: block_size must exist and match the expected value.
565 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
566 report, 'manifest')
567 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700568 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
569 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800570
571 # Check: signatures_offset <==> signatures_size.
572 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
573 report)
574 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
575 report)
576 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
577 'signatures_offset', 'signatures_size', 'manifest')
578
579 # Check: old_kernel_info <==> old_rootfs_info.
580 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
581 'old_kernel_info', report)
582 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
583 'old_rootfs_info', report)
584 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
585 'old_rootfs_info', 'manifest')
586 if oki_msg: # equivalently, ori_msg
587 # Assert/mark delta payload.
588 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700589 raise error.PayloadError(
590 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800591 self.payload_type = _TYPE_DELTA
592
593 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700594 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800595 oki_msg, 'size', oki_report, 'old_kernel_info')
596 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
597 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700598 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800599 ori_msg, 'size', ori_report, 'old_rootfs_info')
600 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
601 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700602
603 # Check: old_{kernel,rootfs} size must fit in respective partition.
604 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700605 raise error.PayloadError(
606 'Old kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700607 (self.old_kernel_fs_size, kernel_part_size))
608 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700609 raise error.PayloadError(
610 'Old rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700611 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800612 else:
613 # Assert/mark full payload.
614 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700615 raise error.PayloadError(
616 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800617 self.payload_type = _TYPE_FULL
618
619 # Check: new_kernel_info present; contains {size, hash}.
620 nki_msg, nki_report = self._CheckMandatorySubMsg(
621 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700622 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800623 nki_msg, 'size', nki_report, 'new_kernel_info')
624 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
625 convert=common.FormatSha256)
626
627 # Check: new_rootfs_info present; contains {size, hash}.
628 nri_msg, nri_report = self._CheckMandatorySubMsg(
629 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700630 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800631 nri_msg, 'size', nri_report, 'new_rootfs_info')
632 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
633 convert=common.FormatSha256)
634
Gilad Arnold382df5c2013-05-03 12:49:28 -0700635 # Check: new_{kernel,rootfs} size must fit in respective partition.
636 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700637 raise error.PayloadError(
638 'New kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700639 (self.new_kernel_fs_size, kernel_part_size))
640 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700641 raise error.PayloadError(
642 'New rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700643 (self.new_rootfs_fs_size, rootfs_part_size))
644
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800645 # Check: minor_version makes sense for the payload type. This check should
646 # run after the payload type has been set.
Gilad Arnold0d575cd2015-07-13 17:29:21 -0700647 self._CheckManifestMinorVersion(report)
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800648
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800649 def _CheckLength(self, length, total_blocks, op_name, length_name):
650 """Checks whether a length matches the space designated in extents.
651
652 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700653 length: The total length of the data.
654 total_blocks: The total number of blocks in extents.
655 op_name: Operation name (for error reporting).
656 length_name: Length name (for error reporting).
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800657
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800658 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700659 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800660 """
661 # Check: length is non-zero.
662 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700663 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800664
665 # Check that length matches number of blocks.
666 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
667 '%s: %s' % (op_name, length_name))
668
Gilad Arnold382df5c2013-05-03 12:49:28 -0700669 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800670 allow_pseudo=False, allow_signature=False):
671 """Checks a sequence of extents.
672
673 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700674 extents: The sequence of extents to check.
675 usable_size: The usable size of the partition to which the extents apply.
676 block_counters: Array of counters corresponding to the number of blocks.
677 name: The name of the extent block.
678 allow_pseudo: Whether or not pseudo block numbers are allowed.
679 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800680
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800681 Returns:
682 The total number of blocks in the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800683
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800684 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700685 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800686 """
687 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800688 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700689 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800690 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
691 None, ex_name)
692 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
693 ex_name)
694 end_block = start_block + num_blocks
695
696 # Check: num_blocks > 0.
697 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700698 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800699
700 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700701 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700702 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700703 raise error.PayloadError(
704 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700705 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800706
707 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700708 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800709 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800710 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
711 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
712 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700713 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800714
715 total_num_blocks += num_blocks
716
717 return total_num_blocks
718
719 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
Amin Hassani0de7f782017-12-07 12:13:03 -0800720 """Specific checks for REPLACE/REPLACE_BZ/REPLACE_XZ operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800721
722 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700723 op: The operation object from the manifest.
724 data_length: The length of the data blob associated with the operation.
725 total_dst_blocks: Total number of blocks in dst_extents.
726 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800727
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800728 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700729 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800730 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700731 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800732 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700733 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800734
Gilad Arnoldcb638912013-06-24 04:57:11 -0700735 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800736 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700737 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800738
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800739 if op.type == common.OpType.REPLACE:
740 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
741 self.block_size,
742 op_name + '.data_length', 'dst')
743 else:
744 # Check: data_length must be smaller than the alotted dst blocks.
745 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700746 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800747 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700748 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800749 (op_name, data_length, total_dst_blocks, self.block_size))
750
751 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
752 total_dst_blocks, op_name):
753 """Specific checks for MOVE operations.
754
755 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700756 op: The operation object from the manifest.
757 data_offset: The offset of a data blob for the operation.
758 total_src_blocks: Total number of blocks in src_extents.
759 total_dst_blocks: Total number of blocks in dst_extents.
760 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800761
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800762 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700763 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800764 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700765 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800766 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700767 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800768
Gilad Arnoldcb638912013-06-24 04:57:11 -0700769 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800770 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700771 raise error.PayloadError(
772 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800773 (op_name, total_src_blocks, total_dst_blocks))
774
Gilad Arnoldcb638912013-06-24 04:57:11 -0700775 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800776 i = 0
777 src_extent_iter = iter(op.src_extents)
778 dst_extent_iter = iter(op.dst_extents)
779 src_extent = dst_extent = None
780 src_idx = src_num = dst_idx = dst_num = 0
781 while i < total_src_blocks:
782 # Get the next source extent, if needed.
783 if not src_extent:
784 try:
785 src_extent = src_extent_iter.next()
786 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700787 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
788 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800789 src_idx = src_extent.start_block
790 src_num = src_extent.num_blocks
791
792 # Get the next dest extent, if needed.
793 if not dst_extent:
794 try:
795 dst_extent = dst_extent_iter.next()
796 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700797 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
798 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800799 dst_idx = dst_extent.start_block
800 dst_num = dst_extent.num_blocks
801
Allie Woodb065e132015-04-24 10:20:27 -0700802 # Check: start block is not 0. See crbug/480751; there are still versions
803 # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
804 # so we need to fail payloads that try to MOVE to/from block 0.
805 if src_idx == 0 or dst_idx == 0:
806 raise error.PayloadError(
807 '%s: MOVE operation cannot have extent with start block 0' %
808 op_name)
809
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700810 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700811 raise error.PayloadError(
812 '%s: src/dst block number %d is the same (%d).' %
813 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800814
815 advance = min(src_num, dst_num)
816 i += advance
817
818 src_idx += advance
819 src_num -= advance
820 if src_num == 0:
821 src_extent = None
822
823 dst_idx += advance
824 dst_num -= advance
825 if dst_num == 0:
826 dst_extent = None
827
Gilad Arnold5502b562013-03-08 13:22:31 -0800828 # Make sure we've exhausted all src/dst extents.
829 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700830 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800831 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700832 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800833
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700834 def _CheckZeroOperation(self, op, op_name):
835 """Specific checks for ZERO operations.
836
837 Args:
838 op: The operation object from the manifest.
839 op_name: Operation name for error reporting.
840
841 Raises:
842 error.PayloadError if any check fails.
843 """
844 # Check: Does not contain src extents, data_length and data_offset.
845 if op.src_extents:
846 raise error.PayloadError('%s: contains src_extents.' % op_name)
847 if op.data_length:
848 raise error.PayloadError('%s: contains data_length.' % op_name)
849 if op.data_offset:
850 raise error.PayloadError('%s: contains data_offset.' % op_name)
851
Amin Hassaniefa62d92017-11-09 13:46:56 -0800852 def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name):
853 """Specific checks for BSDIFF, SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF
854 operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800855
856 Args:
Amin Hassaniefa62d92017-11-09 13:46:56 -0800857 op: The operation.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700858 data_length: The length of the data blob associated with the operation.
859 total_dst_blocks: Total number of blocks in dst_extents.
860 op_name: Operation name for error reporting.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800861
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800862 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700863 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800864 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800865 # Check: data_{offset,length} present.
866 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700867 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800868
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800869 # Check: data_length is strictly smaller than the alotted dst blocks.
870 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700871 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800872 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700873 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800874 (op_name, data_length, total_dst_blocks, self.block_size,
875 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800876
Amin Hassaniefa62d92017-11-09 13:46:56 -0800877 # Check the existence of src_length and dst_length for legacy bsdiffs.
878 if (op.type == common.OpType.BSDIFF or
879 (op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3)):
880 if not op.HasField('src_length') or not op.HasField('dst_length'):
881 raise error.PayloadError('%s: require {src,dst}_length.' % op_name)
882 else:
883 if op.HasField('src_length') or op.HasField('dst_length'):
884 raise error.PayloadError('%s: unneeded {src,dst}_length.' % op_name)
885
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800886 def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
887 total_dst_blocks, op_name):
888 """Specific checks for SOURCE_COPY.
889
890 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800891 data_offset: The offset of a data blob for the operation.
892 total_src_blocks: Total number of blocks in src_extents.
893 total_dst_blocks: Total number of blocks in dst_extents.
894 op_name: Operation name for error reporting.
895
896 Raises:
897 error.PayloadError if any check fails.
898 """
899 # Check: No data_{offset,length}.
900 if data_offset is not None:
901 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
902
903 # Check: total_src_blocks == total_dst_blocks.
904 if total_src_blocks != total_dst_blocks:
905 raise error.PayloadError(
906 '%s: total src blocks (%d) != total dst blocks (%d).' %
907 (op_name, total_src_blocks, total_dst_blocks))
908
Sen Jiangd6122bb2015-12-11 10:27:04 -0800909 def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
Sen Jiang912c4df2015-12-10 12:17:13 -0800910 """Specific checks for SOURCE_* operations.
911
912 Args:
913 op: The operation object from the manifest.
914 total_src_blocks: Total number of blocks in src_extents.
915 op_name: Operation name for error reporting.
916
917 Raises:
918 error.PayloadError if any check fails.
919 """
920 # Check: total_src_blocks != 0.
921 if total_src_blocks == 0:
922 raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
923
Sen Jiang92161a72016-06-28 16:09:38 -0700924 # Check: src_sha256_hash present in minor version >= 3.
925 if self.minor_version >= 3 and op.src_sha256_hash is None:
Sen Jiang912c4df2015-12-10 12:17:13 -0800926 raise error.PayloadError('%s: source hash missing.' % op_name)
927
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800928 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700929 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700930 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800931 """Checks a single update operation.
932
933 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700934 op: The operation object.
935 op_name: Operation name string for error reporting.
936 is_last: Whether this is the last operation in the sequence.
937 old_block_counters: Arrays of block read counters.
938 new_block_counters: Arrays of block write counters.
939 old_usable_size: The overall usable size for src data in bytes.
940 new_usable_size: The overall usable size for dst data in bytes.
941 prev_data_offset: Offset of last used data bytes.
942 allow_signature: Whether this may be a signature operation.
943 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800944
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800945 Returns:
946 The amount of data blob associated with the operation.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800947
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800948 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700949 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800950 """
951 # Check extents.
952 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700953 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800954 op_name + '.src_extents', allow_pseudo=True)
955 allow_signature_in_extents = (allow_signature and is_last and
956 op.type == common.OpType.REPLACE)
957 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700958 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700959 op_name + '.dst_extents',
960 allow_pseudo=(not self.check_dst_pseudo_extents),
961 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800962
963 # Check: data_offset present <==> data_length present.
964 data_offset = self._CheckOptionalField(op, 'data_offset', None)
965 data_length = self._CheckOptionalField(op, 'data_length', None)
966 self._CheckPresentIff(data_offset, data_length, 'data_offset',
967 'data_length', op_name)
968
Gilad Arnoldcb638912013-06-24 04:57:11 -0700969 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800970 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700971 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800972
973 # Check {src,dst}_length, if present.
974 if op.HasField('src_length'):
975 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
976 if op.HasField('dst_length'):
977 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
978
979 if op.HasField('data_sha256_hash'):
980 blob_hash_counts['hashed'] += 1
981
Gilad Arnoldcb638912013-06-24 04:57:11 -0700982 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800983 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700984 raise error.PayloadError(
985 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800986 op_name)
987
Gilad Arnoldcb638912013-06-24 04:57:11 -0700988 # Check: Hash verifies correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800989 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
990 data_length))
991 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700992 raise error.PayloadError(
993 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700994 (op_name, common.FormatSha256(op.data_sha256_hash),
995 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800996 elif data_offset is not None:
997 if allow_signature_in_extents:
998 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700999 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001000 blob_hash_counts['unhashed'] += 1
1001 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001002 raise error.PayloadError('%s: unhashed operation not allowed.' %
1003 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001004
1005 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001006 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001007 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001008 raise error.PayloadError(
1009 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001010 (op_name, data_offset, prev_data_offset))
1011
1012 # Type-specific checks.
1013 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
1014 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Amin Hassani0de7f782017-12-07 12:13:03 -08001015 elif op.type == common.OpType.REPLACE_XZ and (self.minor_version >= 3 or
1016 self.major_version >= 2):
1017 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001018 elif op.type == common.OpType.MOVE and self.minor_version == 1:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001019 self._CheckMoveOperation(op, data_offset, total_src_blocks,
1020 total_dst_blocks, op_name)
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001021 elif op.type == common.OpType.ZERO and self.minor_version >= 4:
1022 self._CheckZeroOperation(op, op_name)
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001023 elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001024 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001025 elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001026 self._CheckSourceCopyOperation(data_offset, total_src_blocks,
1027 total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001028 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001029 elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
Amin Hassaniefa62d92017-11-09 13:46:56 -08001030 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiang92161a72016-06-28 16:09:38 -07001031 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Amin Hassaniefa62d92017-11-09 13:46:56 -08001032 elif (op.type in (common.OpType.PUFFDIFF, common.OpType.BROTLI_BSDIFF) and
1033 self.minor_version >= 4):
1034 self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -08001035 self._CheckAnySourceOperation(op, total_src_blocks, op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001036 else:
Allie Wood7cf9f132015-02-26 14:28:19 -08001037 raise error.PayloadError(
1038 'Operation %s (type %d) not allowed in minor version %d' %
Gilad Arnold0d575cd2015-07-13 17:29:21 -07001039 (op_name, op.type, self.minor_version))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001040 return data_length if data_length is not None else 0
1041
Gilad Arnold382df5c2013-05-03 12:49:28 -07001042 def _SizeToNumBlocks(self, size):
1043 """Returns the number of blocks needed to contain a given byte size."""
1044 return (size + self.block_size - 1) / self.block_size
1045
1046 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001047 """Returns a freshly initialized array of block counters.
1048
Gilad Arnoldcb638912013-06-24 04:57:11 -07001049 Note that the generated array is not portable as is due to byte-ordering
1050 issues, hence it should not be serialized.
1051
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001052 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001053 total_size: The total block size in bytes.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001054
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001055 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -07001056 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001057 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001058 """
Gilad Arnoldcb638912013-06-24 04:57:11 -07001059 return array.array('H',
1060 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001061
Gilad Arnold382df5c2013-05-03 12:49:28 -07001062 def _CheckOperations(self, operations, report, base_name, old_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001063 new_fs_size, old_usable_size, new_usable_size,
1064 prev_data_offset, allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001065 """Checks a sequence of update operations.
1066
1067 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001068 operations: The sequence of operations to check.
1069 report: The report object to add to.
1070 base_name: The name of the operation block.
1071 old_fs_size: The old filesystem size in bytes.
1072 new_fs_size: The new filesystem size in bytes.
Amin Hassaniae853742017-10-11 10:27:27 -07001073 old_usable_size: The overall usable size of the old partition in bytes.
Gilad Arnoldcb638912013-06-24 04:57:11 -07001074 new_usable_size: The overall usable size of the new partition in bytes.
1075 prev_data_offset: Offset of last used data bytes.
1076 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001077
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001078 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -08001079 The total data blob size used.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001080
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001081 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001082 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001083 """
1084 # The total size of data blobs used by operations scanned thus far.
1085 total_data_used = 0
1086 # Counts of specific operation types.
1087 op_counts = {
1088 common.OpType.REPLACE: 0,
1089 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001090 common.OpType.REPLACE_XZ: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001091 common.OpType.MOVE: 0,
Amin Hassani8ad22ba2017-10-11 10:15:11 -07001092 common.OpType.ZERO: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001093 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001094 common.OpType.SOURCE_COPY: 0,
1095 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001096 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001097 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001098 }
1099 # Total blob sizes for each operation type.
1100 op_blob_totals = {
1101 common.OpType.REPLACE: 0,
1102 common.OpType.REPLACE_BZ: 0,
Amin Hassani0de7f782017-12-07 12:13:03 -08001103 common.OpType.REPLACE_XZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001104 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001105 common.OpType.BSDIFF: 0,
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001106 # SOURCE_COPY operations don't have blobs.
1107 common.OpType.SOURCE_BSDIFF: 0,
Amin Hassani5ef5d452017-08-04 13:10:59 -07001108 common.OpType.PUFFDIFF: 0,
Amin Hassaniefa62d92017-11-09 13:46:56 -08001109 common.OpType.BROTLI_BSDIFF: 0,
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001110 }
1111 # Counts of hashed vs unhashed operations.
1112 blob_hash_counts = {
1113 'hashed': 0,
1114 'unhashed': 0,
1115 }
1116 if allow_signature:
1117 blob_hash_counts['signature'] = 0
1118
1119 # Allocate old and new block counters.
Amin Hassaniae853742017-10-11 10:27:27 -07001120 old_block_counters = (self._AllocBlockCounters(old_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -07001121 if old_fs_size else None)
1122 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001123
1124 # Process and verify each operation.
1125 op_num = 0
1126 for op, op_name in common.OperationIter(operations, base_name):
1127 op_num += 1
1128
Gilad Arnoldcb638912013-06-24 04:57:11 -07001129 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001130 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -07001131 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001132 op_counts[op.type] += 1
1133
1134 is_last = op_num == len(operations)
1135 curr_data_used = self._CheckOperation(
1136 op, op_name, is_last, old_block_counters, new_block_counters,
Amin Hassaniae853742017-10-11 10:27:27 -07001137 old_usable_size, new_usable_size,
Gilad Arnold4f50b412013-05-14 09:19:17 -07001138 prev_data_offset + total_data_used, allow_signature,
1139 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001140 if curr_data_used:
1141 op_blob_totals[op.type] += curr_data_used
1142 total_data_used += curr_data_used
1143
1144 # Report totals and breakdown statistics.
1145 report.AddField('total operations', op_num)
1146 report.AddField(
1147 None,
1148 histogram.Histogram.FromCountDict(op_counts,
1149 key_names=common.OpType.NAMES),
1150 indent=1)
1151 report.AddField('total blobs', sum(blob_hash_counts.values()))
1152 report.AddField(None,
1153 histogram.Histogram.FromCountDict(blob_hash_counts),
1154 indent=1)
1155 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
1156 report.AddField(
1157 None,
1158 histogram.Histogram.FromCountDict(op_blob_totals,
1159 formatter=_AddHumanReadableSize,
1160 key_names=common.OpType.NAMES),
1161 indent=1)
1162
1163 # Report read/write histograms.
1164 if old_block_counters:
1165 report.AddField('block read hist',
1166 histogram.Histogram.FromKeyList(old_block_counters),
1167 linebreak=True, indent=1)
1168
Gilad Arnold382df5c2013-05-03 12:49:28 -07001169 new_write_hist = histogram.Histogram.FromKeyList(
1170 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
1171 report.AddField('block write hist', new_write_hist, linebreak=True,
1172 indent=1)
1173
Gilad Arnoldcb638912013-06-24 04:57:11 -07001174 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001175 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001176 raise error.PayloadError(
1177 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001178 base_name)
1179
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001180 return total_data_used
1181
1182 def _CheckSignatures(self, report, pubkey_file_name):
1183 """Checks a payload's signature block."""
1184 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1185 sigs = update_metadata_pb2.Signatures()
1186 sigs.ParseFromString(sigs_raw)
1187 report.AddSection('signatures')
1188
Gilad Arnoldcb638912013-06-24 04:57:11 -07001189 # Check: At least one signature present.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001190 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001191 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001192
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001193 last_ops_section = (self.payload.manifest.kernel_install_operations or
1194 self.payload.manifest.install_operations)
1195 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001196 # Check: signatures_{offset,size} must match the last (fake) operation.
1197 if not (fake_sig_op.type == common.OpType.REPLACE and
1198 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001199 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001200 raise error.PayloadError(
1201 'Signatures_{offset,size} (%d+%d) does not match last operation '
1202 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001203 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1204 fake_sig_op.data_length))
1205
1206 # Compute the checksum of all data up to signature blob.
1207 # TODO(garnold) we're re-reading the whole data section into a string
1208 # just to compute the checksum; instead, we could do it incrementally as
1209 # we read the blobs one-by-one, under the assumption that we're reading
1210 # them in order (which currently holds). This should be reconsidered.
1211 payload_hasher = self.payload.manifest_hasher.copy()
1212 common.Read(self.payload.payload_file, self.sigs_offset,
1213 offset=self.payload.data_offset, hasher=payload_hasher)
1214
1215 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1216 sig_report = report.AddSubReport(sig_name)
1217
Gilad Arnoldcb638912013-06-24 04:57:11 -07001218 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001219 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1220 self._CheckMandatoryField(sig, 'data', None, sig_name)
1221 sig_report.AddField('data len', len(sig.data))
1222
Gilad Arnoldcb638912013-06-24 04:57:11 -07001223 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001224 if sig.version == 1:
1225 self._CheckSha256Signature(sig.data, pubkey_file_name,
1226 payload_hasher.digest(), sig_name)
1227 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001228 raise error.PayloadError('Unknown signature version (%d).' %
1229 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001230
1231 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001232 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001233 """Checker entry point, invoking all checks.
1234
1235 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001236 pubkey_file_name: Public key used for signature verification.
1237 metadata_sig_file: Metadata signature, if verification is desired.
Gilad Arnold06eea332015-07-13 18:06:33 -07001238 rootfs_part_size: The size of rootfs partitions in bytes (default: infer
1239 based on payload type and version).
Gilad Arnoldcb638912013-06-24 04:57:11 -07001240 kernel_part_size: The size of kernel partitions in bytes (default: use
1241 reported filesystem size).
1242 report_out_file: File object to dump the report to.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001243
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001244 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001245 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001246 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001247 if not pubkey_file_name:
1248 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1249
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001250 report = _PayloadReport()
1251
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001252 # Get payload file size.
1253 self.payload.payload_file.seek(0, 2)
1254 payload_file_size = self.payload.payload_file.tell()
1255 self.payload.ResetFile()
1256
1257 try:
1258 # Check metadata signature (if provided).
1259 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001260 metadata_sig = base64.b64decode(metadata_sig_file.read())
1261 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1262 self.payload.manifest_hasher.digest(),
1263 'metadata signature')
1264
Gilad Arnoldcb638912013-06-24 04:57:11 -07001265 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001266 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001267 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001268 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001269 raise error.PayloadError('Unknown payload version (%d).' %
1270 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001271 report.AddField('version', self.payload.header.version)
1272 report.AddField('manifest len', self.payload.header.manifest_len)
1273
Gilad Arnoldcb638912013-06-24 04:57:11 -07001274 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001275 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001276 assert self.payload_type, 'payload type should be known by now'
1277
Gilad Arnold06eea332015-07-13 18:06:33 -07001278 # Infer the usable partition size when validating rootfs operations:
1279 # - If rootfs partition size was provided, use that.
1280 # - Otherwise, if this is an older delta (minor version < 2), stick with
1281 # a known constant size. This is necessary because older deltas may
1282 # exceed the filesystem size when moving data blocks around.
1283 # - Otherwise, use the encoded filesystem size.
1284 new_rootfs_usable_size = self.new_rootfs_fs_size
Amin Hassaniae853742017-10-11 10:27:27 -07001285 old_rootfs_usable_size = self.old_rootfs_fs_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001286 if rootfs_part_size:
1287 new_rootfs_usable_size = rootfs_part_size
Amin Hassaniae853742017-10-11 10:27:27 -07001288 old_rootfs_usable_size = rootfs_part_size
Gilad Arnold06eea332015-07-13 18:06:33 -07001289 elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
1290 new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Amin Hassaniae853742017-10-11 10:27:27 -07001291 old_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
Gilad Arnold06eea332015-07-13 18:06:33 -07001292
Gilad Arnoldcb638912013-06-24 04:57:11 -07001293 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001294 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1295 # no explicit size provided *and* the partition size is not embedded in
1296 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001297 report.AddSection('rootfs operations')
1298 total_blob_size = self._CheckOperations(
1299 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001300 'install_operations', self.old_rootfs_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001301 self.new_rootfs_fs_size, old_rootfs_usable_size,
1302 new_rootfs_usable_size, 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001303
Gilad Arnoldcb638912013-06-24 04:57:11 -07001304 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001305 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001306 report.AddSection('kernel operations')
1307 total_blob_size += self._CheckOperations(
1308 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001309 'kernel_install_operations', self.old_kernel_fs_size,
1310 self.new_kernel_fs_size,
Amin Hassaniae853742017-10-11 10:27:27 -07001311 kernel_part_size if kernel_part_size else self.old_kernel_fs_size,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001312 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1313 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001314
Gilad Arnoldcb638912013-06-24 04:57:11 -07001315 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001316 used_payload_size = self.payload.data_offset + total_blob_size
1317 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001318 raise error.PayloadError(
1319 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001320 (used_payload_size, payload_file_size))
1321
Gilad Arnoldcb638912013-06-24 04:57:11 -07001322 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001323 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001324 self._CheckSignatures(report, pubkey_file_name)
1325
Gilad Arnoldcb638912013-06-24 04:57:11 -07001326 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001327 report.AddSection('summary')
1328 report.AddField('update type', self.payload_type)
1329
1330 report.Finalize()
1331 finally:
1332 if report_out_file:
1333 report.Dump(report_out_file)