blob: 549ffcb8efb985f648ff46b35ff4557162c723e3 [file] [log] [blame]
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Verifying the integrity of a Chrome OS update payload.
6
7This module is used internally by the main Payload class for verifying the
8integrity of an update payload. The interface for invoking the checks is as
9follows:
10
11 checker = PayloadChecker(payload)
12 checker.Run(...)
Gilad Arnold553b0ec2013-01-26 01:00:39 -080013"""
14
15import array
16import base64
17import hashlib
Gilad Arnoldcb638912013-06-24 04:57:11 -070018import itertools
Gilad Arnold9b90c932013-05-22 17:12:56 -070019import os
Gilad Arnold553b0ec2013-01-26 01:00:39 -080020import subprocess
21
22import common
Gilad Arnoldcb638912013-06-24 04:57:11 -070023import error
Gilad Arnold553b0ec2013-01-26 01:00:39 -080024import format_utils
25import histogram
26import update_metadata_pb2
27
28
29#
Gilad Arnold9b90c932013-05-22 17:12:56 -070030# Constants.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080031#
Gilad Arnoldcb638912013-06-24 04:57:11 -070032
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070033_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents'
34_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
35_CHECK_PAYLOAD_SIG = 'payload-sig'
36CHECKS_TO_DISABLE = (
Gilad Arnold382df5c2013-05-03 12:49:28 -070037 _CHECK_DST_PSEUDO_EXTENTS,
38 _CHECK_MOVE_SAME_SRC_DST_BLOCK,
39 _CHECK_PAYLOAD_SIG,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070040)
41
Gilad Arnold553b0ec2013-01-26 01:00:39 -080042_TYPE_FULL = 'full'
43_TYPE_DELTA = 'delta'
44
45_DEFAULT_BLOCK_SIZE = 4096
46
Gilad Arnold9b90c932013-05-22 17:12:56 -070047_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem'
48_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
49 _DEFAULT_PUBKEY_BASE_NAME)
50
Gilad Arnold553b0ec2013-01-26 01:00:39 -080051
52#
53# Helper functions.
54#
Gilad Arnoldcb638912013-06-24 04:57:11 -070055
Gilad Arnold553b0ec2013-01-26 01:00:39 -080056def _IsPowerOfTwo(val):
57 """Returns True iff val is a power of two."""
58 return val > 0 and (val & (val - 1)) == 0
59
60
61def _AddFormat(format_func, value):
62 """Adds a custom formatted representation to ordinary string representation.
63
64 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070065 format_func: A value formatter.
66 value: Value to be formatted and returned.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080067 Returns:
68 A string 'x (y)' where x = str(value) and y = format_func(value).
Gilad Arnold553b0ec2013-01-26 01:00:39 -080069 """
70 return '%s (%s)' % (value, format_func(value))
71
72
73def _AddHumanReadableSize(size):
74 """Adds a human readable representation to a byte size value."""
75 return _AddFormat(format_utils.BytesToHumanReadable, size)
76
77
78#
79# Payload report generator.
80#
Gilad Arnoldcb638912013-06-24 04:57:11 -070081
Gilad Arnold553b0ec2013-01-26 01:00:39 -080082class _PayloadReport(object):
83 """A payload report generator.
84
85 A report is essentially a sequence of nodes, which represent data points. It
86 is initialized to have a "global", untitled section. A node may be a
87 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080088 """
89
Gilad Arnoldcb638912013-06-24 04:57:11 -070090 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080091 class Node(object):
92 """A report node interface."""
93
94 @staticmethod
95 def _Indent(indent, line):
96 """Indents a line by a given indentation amount.
97
98 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -070099 indent: The indentation amount.
100 line: The line content (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800101 Returns:
102 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800103 """
104 return '%*s%s' % (indent, '', line)
105
106 def GenerateLines(self, base_indent, sub_indent, curr_section):
107 """Generates the report lines for this node.
108
109 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700110 base_indent: Base indentation for each line.
111 sub_indent: Additional indentation for sub-nodes.
112 curr_section: The current report section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800113 Returns:
114 A pair consisting of a list of properly indented report lines and a new
115 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800116 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700117 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800118
119 class FieldNode(Node):
120 """A field report node, representing a (name, value) pair."""
121
122 def __init__(self, name, value, linebreak, indent):
123 super(_PayloadReport.FieldNode, self).__init__()
124 self.name = name
125 self.value = value
126 self.linebreak = linebreak
127 self.indent = indent
128
129 def GenerateLines(self, base_indent, sub_indent, curr_section):
130 """Generates a properly formatted 'name : value' entry."""
131 report_output = ''
132 if self.name:
133 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
134 value_lines = str(self.value).splitlines()
135 if self.linebreak and self.name:
136 report_output += '\n' + '\n'.join(
137 ['%*s%s' % (self.indent, '', line) for line in value_lines])
138 else:
139 if self.name:
140 report_output += ' '
141 report_output += '%*s' % (self.indent, '')
142 cont_line_indent = len(report_output)
143 indented_value_lines = [value_lines[0]]
144 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
145 for line in value_lines[1:]])
146 report_output += '\n'.join(indented_value_lines)
147
148 report_lines = [self._Indent(base_indent, line + '\n')
149 for line in report_output.split('\n')]
150 return report_lines, curr_section
151
152 class SubReportNode(Node):
153 """A sub-report node, representing a nested report."""
154
155 def __init__(self, title, report):
156 super(_PayloadReport.SubReportNode, self).__init__()
157 self.title = title
158 self.report = report
159
160 def GenerateLines(self, base_indent, sub_indent, curr_section):
161 """Recurse with indentation."""
162 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
163 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
164 sub_indent))
165 return report_lines, curr_section
166
167 class SectionNode(Node):
168 """A section header node."""
169
170 def __init__(self, title=None):
171 super(_PayloadReport.SectionNode, self).__init__()
172 self.title = title
173 self.max_field_name_len = 0
174
175 def GenerateLines(self, base_indent, sub_indent, curr_section):
176 """Dump a title line, return self as the (new) current section."""
177 report_lines = []
178 if self.title:
179 report_lines.append(self._Indent(base_indent,
180 '=== %s ===\n' % self.title))
181 return report_lines, self
182
183 def __init__(self):
184 self.report = []
185 self.last_section = self.global_section = self.SectionNode()
186 self.is_finalized = False
187
188 def GenerateLines(self, base_indent, sub_indent):
189 """Generates the lines in the report, properly indented.
190
191 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700192 base_indent: The indentation used for root-level report lines.
193 sub_indent: The indentation offset used for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800194 Returns:
195 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800196 """
197 report_lines = []
198 curr_section = self.global_section
199 for node in self.report:
200 node_report_lines, curr_section = node.GenerateLines(
201 base_indent, sub_indent, curr_section)
202 report_lines.extend(node_report_lines)
203
204 return report_lines
205
206 def Dump(self, out_file, base_indent=0, sub_indent=2):
207 """Dumps the report to a file.
208
209 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700210 out_file: File object to output the content to.
211 base_indent: Base indentation for report lines.
212 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800213 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800214 report_lines = self.GenerateLines(base_indent, sub_indent)
215 if report_lines and not self.is_finalized:
216 report_lines.append('(incomplete report)\n')
217
218 for line in report_lines:
219 out_file.write(line)
220
221 def AddField(self, name, value, linebreak=False, indent=0):
222 """Adds a field/value pair to the payload report.
223
224 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700225 name: The field's name.
226 value: The field's value.
227 linebreak: Whether the value should be printed on a new line.
228 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800229 """
230 assert not self.is_finalized
231 if name and self.last_section.max_field_name_len < len(name):
232 self.last_section.max_field_name_len = len(name)
233 self.report.append(self.FieldNode(name, value, linebreak, indent))
234
235 def AddSubReport(self, title):
236 """Adds and returns a sub-report with a title."""
237 assert not self.is_finalized
238 sub_report = self.SubReportNode(title, type(self)())
239 self.report.append(sub_report)
240 return sub_report.report
241
242 def AddSection(self, title):
243 """Adds a new section title."""
244 assert not self.is_finalized
245 self.last_section = self.SectionNode(title)
246 self.report.append(self.last_section)
247
248 def Finalize(self):
249 """Seals the report, marking it as complete."""
250 self.is_finalized = True
251
252
253#
254# Payload verification.
255#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700256
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800257class PayloadChecker(object):
258 """Checking the integrity of an update payload.
259
260 This is a short-lived object whose purpose is to isolate the logic used for
261 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800262 """
263
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700264 def __init__(self, payload, assert_type=None, block_size=0,
265 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700266 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700267
268 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700269 payload: The payload object to check.
270 assert_type: Assert that payload is either 'full' or 'delta' (optional).
271 block_size: Expected filesystem / payload block size (optional).
272 allow_unhashed: Allow operations with unhashed data blobs.
273 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700274 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700275 if not payload.is_init:
276 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700277
278 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800279 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700280 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
281 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700282 raise error.PayloadError(
283 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700284 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700285 raise error.PayloadError('Invalid assert_type value (%r).' %
286 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700287 self.payload_type = assert_type
288 self.allow_unhashed = allow_unhashed
289
290 # Disable specific tests.
291 self.check_dst_pseudo_extents = (
292 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
293 self.check_move_same_src_dst_block = (
294 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
295 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800296
297 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800298 self.sigs_offset = 0
299 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700300 self.old_rootfs_fs_size = 0
301 self.old_kernel_fs_size = 0
302 self.new_rootfs_fs_size = 0
303 self.new_kernel_fs_size = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800304
305 @staticmethod
306 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
307 msg_name=None, linebreak=False, indent=0):
308 """Adds an element from a protobuf message to the payload report.
309
310 Checks to see whether a message contains a given element, and if so adds
311 the element value to the provided report. A missing mandatory element
312 causes an exception to be raised.
313
314 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700315 msg: The message containing the element.
316 name: The name of the element.
317 report: A report object to add the element name/value to.
318 is_mandatory: Whether or not this element must be present.
319 is_submsg: Whether this element is itself a message.
320 convert: A function for converting the element value for reporting.
321 msg_name: The name of the message object (for error reporting).
322 linebreak: Whether the value report should induce a line break.
323 indent: Amount of indent used for reporting the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800324 Returns:
325 A pair consisting of the element value and the generated sub-report for
326 it (if the element is a sub-message, None otherwise). If the element is
327 missing, returns (None, None).
328 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700329 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800330 """
331 if not msg.HasField(name):
332 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700333 raise error.PayloadError('%smissing mandatory %s %r.' %
334 (msg_name + ' ' if msg_name else '',
335 'sub-message' if is_submsg else 'field',
336 name))
337 return None, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800338
339 value = getattr(msg, name)
340 if is_submsg:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700341 return value, report and report.AddSubReport(name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800342 else:
343 if report:
344 report.AddField(name, convert(value), linebreak=linebreak,
345 indent=indent)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700346 return value, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800347
348 @staticmethod
349 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
350 linebreak=False, indent=0):
351 """Adds a mandatory field; returning first component from _CheckElem."""
352 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
353 convert=convert, msg_name=msg_name,
354 linebreak=linebreak, indent=indent)[0]
355
356 @staticmethod
357 def _CheckOptionalField(msg, field_name, report, convert=str,
358 linebreak=False, indent=0):
359 """Adds an optional field; returning first component from _CheckElem."""
360 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
361 convert=convert, linebreak=linebreak,
362 indent=indent)[0]
363
364 @staticmethod
365 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
366 """Adds a mandatory sub-message; wrapper for _CheckElem."""
367 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
368 msg_name)
369
370 @staticmethod
371 def _CheckOptionalSubMsg(msg, submsg_name, report):
372 """Adds an optional sub-message; wrapper for _CheckElem."""
373 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
374
375 @staticmethod
376 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
377 """Checks that val1 is None iff val2 is None.
378
379 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700380 val1: first value to be compared.
381 val2: second value to be compared.
382 name1: name of object holding the first value.
383 name2: name of object holding the second value.
384 obj_name: Name of the object containing these values.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800385 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700386 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800387 """
388 if None in (val1, val2) and val1 is not val2:
389 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700390 raise error.PayloadError('%r present without %r%s.' %
391 (present, missing,
392 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800393
394 @staticmethod
395 def _Run(cmd, send_data=None):
396 """Runs a subprocess, returns its output.
397
398 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700399 cmd: Sequence of command-line argument for invoking the subprocess.
400 send_data: Data to feed to the process via its stdin.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800401 Returns:
402 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800403 """
404 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
405 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700406 try:
407 result = run_process.communicate(input=send_data)
408 finally:
409 exit_code = run_process.wait()
410
411 if exit_code:
412 raise RuntimeError('Subprocess %r failed with code %r.' %
413 (cmd, exit_code))
414
415 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800416
417 @staticmethod
418 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
419 """Verifies an actual hash against a signed one.
420
421 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700422 sig_data: The raw signature data.
423 pubkey_file_name: Public key used for verifying signature.
424 actual_hash: The actual hash digest.
425 sig_name: Signature name for error reporting.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800426 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700427 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800428 """
429 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700430 raise error.PayloadError(
431 '%s: signature size (%d) not as expected (256).' %
432 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800433 signed_data, _ = PayloadChecker._Run(
434 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
435 send_data=sig_data)
436
Gilad Arnold5502b562013-03-08 13:22:31 -0800437 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700438 raise error.PayloadError('%s: unexpected signed data length (%d).' %
439 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800440
Gilad Arnold5502b562013-03-08 13:22:31 -0800441 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700442 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
443 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800444
Gilad Arnold5502b562013-03-08 13:22:31 -0800445 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800446 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700447 raise error.PayloadError(
448 '%s: signed hash (%s) different from actual (%s).' %
449 (sig_name, common.FormatSha256(signed_hash),
450 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800451
452 @staticmethod
453 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
454 block_name=None):
455 """Checks that a given length fits given block space.
456
457 This ensures that the number of blocks allocated is appropriate for the
458 length of the data residing in these blocks.
459
460 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700461 length: The actual length of the data.
462 num_blocks: The number of blocks allocated for it.
463 block_size: The size of each block in bytes.
464 length_name: Name of length (used for error reporting).
465 block_name: Name of block (used for error reporting).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800466 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700467 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800468 """
469 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700470 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700471 raise error.PayloadError(
472 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800473 (length_name, length, block_name or '', num_blocks, block_size))
474
475 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700476 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700477 raise error.PayloadError(
478 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800479 (length_name, length, block_name or '', num_blocks - 1, block_size))
480
Gilad Arnold382df5c2013-05-03 12:49:28 -0700481 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800482 """Checks the payload manifest.
483
484 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700485 report: A report object to add to.
486 rootfs_part_size: Size of the rootfs partition in bytes.
487 kernel_part_size: Size of the kernel partition in bytes.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800488 Returns:
489 A tuple consisting of the partition block size used during the update
490 (integer), the signatures block offset and size.
491 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700492 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800493 """
494 manifest = self.payload.manifest
495 report.AddSection('manifest')
496
497 # Check: block_size must exist and match the expected value.
498 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
499 report, 'manifest')
500 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700501 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
502 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800503
504 # Check: signatures_offset <==> signatures_size.
505 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
506 report)
507 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
508 report)
509 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
510 'signatures_offset', 'signatures_size', 'manifest')
511
512 # Check: old_kernel_info <==> old_rootfs_info.
513 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
514 'old_kernel_info', report)
515 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
516 'old_rootfs_info', report)
517 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
518 'old_rootfs_info', 'manifest')
519 if oki_msg: # equivalently, ori_msg
520 # Assert/mark delta payload.
521 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700522 raise error.PayloadError(
523 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800524 self.payload_type = _TYPE_DELTA
525
526 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700527 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800528 oki_msg, 'size', oki_report, 'old_kernel_info')
529 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
530 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700531 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800532 ori_msg, 'size', ori_report, 'old_rootfs_info')
533 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
534 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700535
536 # Check: old_{kernel,rootfs} size must fit in respective partition.
537 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700538 raise error.PayloadError(
539 'Old kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700540 (self.old_kernel_fs_size, kernel_part_size))
541 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700542 raise error.PayloadError(
543 'Old rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700544 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800545 else:
546 # Assert/mark full payload.
547 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700548 raise error.PayloadError(
549 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800550 self.payload_type = _TYPE_FULL
551
552 # Check: new_kernel_info present; contains {size, hash}.
553 nki_msg, nki_report = self._CheckMandatorySubMsg(
554 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700555 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800556 nki_msg, 'size', nki_report, 'new_kernel_info')
557 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
558 convert=common.FormatSha256)
559
560 # Check: new_rootfs_info present; contains {size, hash}.
561 nri_msg, nri_report = self._CheckMandatorySubMsg(
562 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700563 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800564 nri_msg, 'size', nri_report, 'new_rootfs_info')
565 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
566 convert=common.FormatSha256)
567
Gilad Arnold382df5c2013-05-03 12:49:28 -0700568 # Check: new_{kernel,rootfs} size must fit in respective partition.
569 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700570 raise error.PayloadError(
571 'New kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700572 (self.new_kernel_fs_size, kernel_part_size))
573 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700574 raise error.PayloadError(
575 'New rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700576 (self.new_rootfs_fs_size, rootfs_part_size))
577
Gilad Arnoldcb638912013-06-24 04:57:11 -0700578 # Check: Payload must contain at least one operation.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800579 if not(len(manifest.install_operations) or
580 len(manifest.kernel_install_operations)):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700581 raise error.PayloadError('Payload contains no operations.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800582
583 def _CheckLength(self, length, total_blocks, op_name, length_name):
584 """Checks whether a length matches the space designated in extents.
585
586 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700587 length: The total length of the data.
588 total_blocks: The total number of blocks in extents.
589 op_name: Operation name (for error reporting).
590 length_name: Length name (for error reporting).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800591 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700592 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800593 """
594 # Check: length is non-zero.
595 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700596 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800597
598 # Check that length matches number of blocks.
599 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
600 '%s: %s' % (op_name, length_name))
601
Gilad Arnold382df5c2013-05-03 12:49:28 -0700602 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800603 allow_pseudo=False, allow_signature=False):
604 """Checks a sequence of extents.
605
606 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700607 extents: The sequence of extents to check.
608 usable_size: The usable size of the partition to which the extents apply.
609 block_counters: Array of counters corresponding to the number of blocks.
610 name: The name of the extent block.
611 allow_pseudo: Whether or not pseudo block numbers are allowed.
612 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800613 Returns:
614 The total number of blocks in the extents.
615 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700616 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800617 """
618 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800619 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700620 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800621 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
622 None, ex_name)
623 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
624 ex_name)
625 end_block = start_block + num_blocks
626
627 # Check: num_blocks > 0.
628 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700629 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800630
631 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700632 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700633 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700634 raise error.PayloadError(
635 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700636 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800637
638 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700639 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800640 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800641 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
642 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
643 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700644 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800645
646 total_num_blocks += num_blocks
647
648 return total_num_blocks
649
650 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
651 """Specific checks for REPLACE/REPLACE_BZ operations.
652
653 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700654 op: The operation object from the manifest.
655 data_length: The length of the data blob associated with the operation.
656 total_dst_blocks: Total number of blocks in dst_extents.
657 op_name: Operation name for error reporting.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800658 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700659 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800660 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700661 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800662 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700663 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800664
Gilad Arnoldcb638912013-06-24 04:57:11 -0700665 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800666 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700667 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800668
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800669 if op.type == common.OpType.REPLACE:
670 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
671 self.block_size,
672 op_name + '.data_length', 'dst')
673 else:
674 # Check: data_length must be smaller than the alotted dst blocks.
675 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700676 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800677 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700678 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800679 (op_name, data_length, total_dst_blocks, self.block_size))
680
681 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
682 total_dst_blocks, op_name):
683 """Specific checks for MOVE operations.
684
685 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700686 op: The operation object from the manifest.
687 data_offset: The offset of a data blob for the operation.
688 total_src_blocks: Total number of blocks in src_extents.
689 total_dst_blocks: Total number of blocks in dst_extents.
690 op_name: Operation name for error reporting.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800691 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700692 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800693 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700694 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800695 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700696 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800697
Gilad Arnoldcb638912013-06-24 04:57:11 -0700698 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800699 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700700 raise error.PayloadError(
701 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800702 (op_name, total_src_blocks, total_dst_blocks))
703
Gilad Arnoldcb638912013-06-24 04:57:11 -0700704 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800705 i = 0
706 src_extent_iter = iter(op.src_extents)
707 dst_extent_iter = iter(op.dst_extents)
708 src_extent = dst_extent = None
709 src_idx = src_num = dst_idx = dst_num = 0
710 while i < total_src_blocks:
711 # Get the next source extent, if needed.
712 if not src_extent:
713 try:
714 src_extent = src_extent_iter.next()
715 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700716 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
717 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800718 src_idx = src_extent.start_block
719 src_num = src_extent.num_blocks
720
721 # Get the next dest extent, if needed.
722 if not dst_extent:
723 try:
724 dst_extent = dst_extent_iter.next()
725 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700726 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
727 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800728 dst_idx = dst_extent.start_block
729 dst_num = dst_extent.num_blocks
730
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700731 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700732 raise error.PayloadError(
733 '%s: src/dst block number %d is the same (%d).' %
734 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800735
736 advance = min(src_num, dst_num)
737 i += advance
738
739 src_idx += advance
740 src_num -= advance
741 if src_num == 0:
742 src_extent = None
743
744 dst_idx += advance
745 dst_num -= advance
746 if dst_num == 0:
747 dst_extent = None
748
Gilad Arnold5502b562013-03-08 13:22:31 -0800749 # Make sure we've exhausted all src/dst extents.
750 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700751 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800752 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700753 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800754
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800755 def _CheckBsdiffOperation(self, data_length, total_dst_blocks, op_name):
756 """Specific checks for BSDIFF operations.
757
758 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700759 data_length: The length of the data blob associated with the operation.
760 total_dst_blocks: Total number of blocks in dst_extents.
761 op_name: Operation name for error reporting.
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 Arnold5502b562013-03-08 13:22:31 -0800765 # Check: data_{offset,length} present.
766 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700767 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800768
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800769 # Check: data_length is strictly smaller than the alotted dst blocks.
770 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700771 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800772 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700773 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800774 (op_name, data_length, total_dst_blocks, self.block_size,
775 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800776
777 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700778 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700779 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800780 """Checks a single update operation.
781
782 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700783 op: The operation object.
784 op_name: Operation name string for error reporting.
785 is_last: Whether this is the last operation in the sequence.
786 old_block_counters: Arrays of block read counters.
787 new_block_counters: Arrays of block write counters.
788 old_usable_size: The overall usable size for src data in bytes.
789 new_usable_size: The overall usable size for dst data in bytes.
790 prev_data_offset: Offset of last used data bytes.
791 allow_signature: Whether this may be a signature operation.
792 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800793 Returns:
794 The amount of data blob associated with the operation.
795 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700796 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800797 """
798 # Check extents.
799 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700800 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800801 op_name + '.src_extents', allow_pseudo=True)
802 allow_signature_in_extents = (allow_signature and is_last and
803 op.type == common.OpType.REPLACE)
804 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700805 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700806 op_name + '.dst_extents',
807 allow_pseudo=(not self.check_dst_pseudo_extents),
808 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800809
810 # Check: data_offset present <==> data_length present.
811 data_offset = self._CheckOptionalField(op, 'data_offset', None)
812 data_length = self._CheckOptionalField(op, 'data_length', None)
813 self._CheckPresentIff(data_offset, data_length, 'data_offset',
814 'data_length', op_name)
815
Gilad Arnoldcb638912013-06-24 04:57:11 -0700816 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800817 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700818 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800819
820 # Check {src,dst}_length, if present.
821 if op.HasField('src_length'):
822 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
823 if op.HasField('dst_length'):
824 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
825
826 if op.HasField('data_sha256_hash'):
827 blob_hash_counts['hashed'] += 1
828
Gilad Arnoldcb638912013-06-24 04:57:11 -0700829 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800830 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700831 raise error.PayloadError(
832 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800833 op_name)
834
Gilad Arnoldcb638912013-06-24 04:57:11 -0700835 # Check: Hash verifies correctly.
836 # pylint cannot find the method in hashlib, for some reason.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800837 # pylint: disable=E1101
838 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
839 data_length))
840 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700841 raise error.PayloadError(
842 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700843 (op_name, common.FormatSha256(op.data_sha256_hash),
844 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800845 elif data_offset is not None:
846 if allow_signature_in_extents:
847 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700848 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800849 blob_hash_counts['unhashed'] += 1
850 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700851 raise error.PayloadError('%s: unhashed operation not allowed.' %
852 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800853
854 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700855 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800856 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700857 raise error.PayloadError(
858 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800859 (op_name, data_offset, prev_data_offset))
860
861 # Type-specific checks.
862 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
863 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
864 elif self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700865 raise error.PayloadError('%s: non-REPLACE operation in a full payload.' %
866 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800867 elif op.type == common.OpType.MOVE:
868 self._CheckMoveOperation(op, data_offset, total_src_blocks,
869 total_dst_blocks, op_name)
870 elif op.type == common.OpType.BSDIFF:
871 self._CheckBsdiffOperation(data_length, total_dst_blocks, op_name)
872 else:
873 assert False, 'cannot get here'
874
875 return data_length if data_length is not None else 0
876
Gilad Arnold382df5c2013-05-03 12:49:28 -0700877 def _SizeToNumBlocks(self, size):
878 """Returns the number of blocks needed to contain a given byte size."""
879 return (size + self.block_size - 1) / self.block_size
880
881 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800882 """Returns a freshly initialized array of block counters.
883
Gilad Arnoldcb638912013-06-24 04:57:11 -0700884 Note that the generated array is not portable as is due to byte-ordering
885 issues, hence it should not be serialized.
886
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800887 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700888 total_size: The total block size in bytes.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800889 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -0700890 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800891 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800892 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700893 return array.array('H',
894 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800895
Gilad Arnold382df5c2013-05-03 12:49:28 -0700896 def _CheckOperations(self, operations, report, base_name, old_fs_size,
897 new_fs_size, new_usable_size, prev_data_offset,
898 allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800899 """Checks a sequence of update operations.
900
901 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700902 operations: The sequence of operations to check.
903 report: The report object to add to.
904 base_name: The name of the operation block.
905 old_fs_size: The old filesystem size in bytes.
906 new_fs_size: The new filesystem size in bytes.
907 new_usable_size: The overall usable size of the new partition in bytes.
908 prev_data_offset: Offset of last used data bytes.
909 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800910 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -0800911 The total data blob size used.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800912 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700913 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800914 """
915 # The total size of data blobs used by operations scanned thus far.
916 total_data_used = 0
917 # Counts of specific operation types.
918 op_counts = {
919 common.OpType.REPLACE: 0,
920 common.OpType.REPLACE_BZ: 0,
921 common.OpType.MOVE: 0,
922 common.OpType.BSDIFF: 0,
923 }
924 # Total blob sizes for each operation type.
925 op_blob_totals = {
926 common.OpType.REPLACE: 0,
927 common.OpType.REPLACE_BZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700928 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800929 common.OpType.BSDIFF: 0,
930 }
931 # Counts of hashed vs unhashed operations.
932 blob_hash_counts = {
933 'hashed': 0,
934 'unhashed': 0,
935 }
936 if allow_signature:
937 blob_hash_counts['signature'] = 0
938
939 # Allocate old and new block counters.
Gilad Arnold4f50b412013-05-14 09:19:17 -0700940 old_block_counters = (self._AllocBlockCounters(new_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700941 if old_fs_size else None)
942 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800943
944 # Process and verify each operation.
945 op_num = 0
946 for op, op_name in common.OperationIter(operations, base_name):
947 op_num += 1
948
Gilad Arnoldcb638912013-06-24 04:57:11 -0700949 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800950 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700951 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800952 op_counts[op.type] += 1
953
954 is_last = op_num == len(operations)
955 curr_data_used = self._CheckOperation(
956 op, op_name, is_last, old_block_counters, new_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700957 new_usable_size if old_fs_size else 0, new_usable_size,
958 prev_data_offset + total_data_used, allow_signature,
959 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800960 if curr_data_used:
961 op_blob_totals[op.type] += curr_data_used
962 total_data_used += curr_data_used
963
964 # Report totals and breakdown statistics.
965 report.AddField('total operations', op_num)
966 report.AddField(
967 None,
968 histogram.Histogram.FromCountDict(op_counts,
969 key_names=common.OpType.NAMES),
970 indent=1)
971 report.AddField('total blobs', sum(blob_hash_counts.values()))
972 report.AddField(None,
973 histogram.Histogram.FromCountDict(blob_hash_counts),
974 indent=1)
975 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
976 report.AddField(
977 None,
978 histogram.Histogram.FromCountDict(op_blob_totals,
979 formatter=_AddHumanReadableSize,
980 key_names=common.OpType.NAMES),
981 indent=1)
982
983 # Report read/write histograms.
984 if old_block_counters:
985 report.AddField('block read hist',
986 histogram.Histogram.FromKeyList(old_block_counters),
987 linebreak=True, indent=1)
988
Gilad Arnold382df5c2013-05-03 12:49:28 -0700989 new_write_hist = histogram.Histogram.FromKeyList(
990 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
991 report.AddField('block write hist', new_write_hist, linebreak=True,
992 indent=1)
993
Gilad Arnoldcb638912013-06-24 04:57:11 -0700994 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800995 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700996 raise error.PayloadError(
997 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800998 base_name)
999
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001000 return total_data_used
1001
1002 def _CheckSignatures(self, report, pubkey_file_name):
1003 """Checks a payload's signature block."""
1004 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1005 sigs = update_metadata_pb2.Signatures()
1006 sigs.ParseFromString(sigs_raw)
1007 report.AddSection('signatures')
1008
Gilad Arnoldcb638912013-06-24 04:57:11 -07001009 # Check: At least one signature present.
1010 # pylint cannot see through the protobuf object, it seems.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001011 # pylint: disable=E1101
1012 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001013 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001014
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001015 last_ops_section = (self.payload.manifest.kernel_install_operations or
1016 self.payload.manifest.install_operations)
1017 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001018 # Check: signatures_{offset,size} must match the last (fake) operation.
1019 if not (fake_sig_op.type == common.OpType.REPLACE and
1020 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001021 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001022 raise error.PayloadError(
1023 'Signatures_{offset,size} (%d+%d) does not match last operation '
1024 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001025 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1026 fake_sig_op.data_length))
1027
1028 # Compute the checksum of all data up to signature blob.
1029 # TODO(garnold) we're re-reading the whole data section into a string
1030 # just to compute the checksum; instead, we could do it incrementally as
1031 # we read the blobs one-by-one, under the assumption that we're reading
1032 # them in order (which currently holds). This should be reconsidered.
1033 payload_hasher = self.payload.manifest_hasher.copy()
1034 common.Read(self.payload.payload_file, self.sigs_offset,
1035 offset=self.payload.data_offset, hasher=payload_hasher)
1036
1037 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1038 sig_report = report.AddSubReport(sig_name)
1039
Gilad Arnoldcb638912013-06-24 04:57:11 -07001040 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001041 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1042 self._CheckMandatoryField(sig, 'data', None, sig_name)
1043 sig_report.AddField('data len', len(sig.data))
1044
Gilad Arnoldcb638912013-06-24 04:57:11 -07001045 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001046 if sig.version == 1:
1047 self._CheckSha256Signature(sig.data, pubkey_file_name,
1048 payload_hasher.digest(), sig_name)
1049 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001050 raise error.PayloadError('Unknown signature version (%d).' %
1051 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001052
1053 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001054 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001055 """Checker entry point, invoking all checks.
1056
1057 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001058 pubkey_file_name: Public key used for signature verification.
1059 metadata_sig_file: Metadata signature, if verification is desired.
1060 rootfs_part_size: The size of rootfs partitions in bytes (default: use
1061 reported filesystem size).
1062 kernel_part_size: The size of kernel partitions in bytes (default: use
1063 reported filesystem size).
1064 report_out_file: File object to dump the report to.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001065 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001066 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001067 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001068 if not pubkey_file_name:
1069 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1070
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001071 report = _PayloadReport()
1072
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001073 # Get payload file size.
1074 self.payload.payload_file.seek(0, 2)
1075 payload_file_size = self.payload.payload_file.tell()
1076 self.payload.ResetFile()
1077
1078 try:
1079 # Check metadata signature (if provided).
1080 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001081 metadata_sig = base64.b64decode(metadata_sig_file.read())
1082 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1083 self.payload.manifest_hasher.digest(),
1084 'metadata signature')
1085
Gilad Arnoldcb638912013-06-24 04:57:11 -07001086 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001087 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001088 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001089 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001090 raise error.PayloadError('Unknown payload version (%d).' %
1091 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001092 report.AddField('version', self.payload.header.version)
1093 report.AddField('manifest len', self.payload.header.manifest_len)
1094
Gilad Arnoldcb638912013-06-24 04:57:11 -07001095 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001096 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001097 assert self.payload_type, 'payload type should be known by now'
1098
Gilad Arnoldcb638912013-06-24 04:57:11 -07001099 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001100 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1101 # no explicit size provided *and* the partition size is not embedded in
1102 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001103 report.AddSection('rootfs operations')
1104 total_blob_size = self._CheckOperations(
1105 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001106 'install_operations', self.old_rootfs_fs_size,
1107 self.new_rootfs_fs_size,
1108 rootfs_part_size if rootfs_part_size else self.new_rootfs_fs_size,
1109 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001110
Gilad Arnoldcb638912013-06-24 04:57:11 -07001111 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001112 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001113 report.AddSection('kernel operations')
1114 total_blob_size += self._CheckOperations(
1115 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001116 'kernel_install_operations', self.old_kernel_fs_size,
1117 self.new_kernel_fs_size,
1118 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1119 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001120
Gilad Arnoldcb638912013-06-24 04:57:11 -07001121 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001122 used_payload_size = self.payload.data_offset + total_blob_size
1123 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001124 raise error.PayloadError(
1125 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001126 (used_payload_size, payload_file_size))
1127
Gilad Arnoldcb638912013-06-24 04:57:11 -07001128 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001129 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001130 self._CheckSignatures(report, pubkey_file_name)
1131
Gilad Arnoldcb638912013-06-24 04:57:11 -07001132 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001133 report.AddSection('summary')
1134 report.AddField('update type', self.payload_type)
1135
1136 report.Finalize()
1137 finally:
1138 if report_out_file:
1139 report.Dump(report_out_file)