blob: 8a866190d02913cd35938de3cadcae8fec0ef6c0 [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 """
Gilad Arnold6a3a3872013-10-04 18:18:45 -070070 ret = str(value)
71 formatted_str = format_func(value)
72 if formatted_str:
73 ret += ' (%s)' % formatted_str
74 return ret
Gilad Arnold553b0ec2013-01-26 01:00:39 -080075
76
77def _AddHumanReadableSize(size):
78 """Adds a human readable representation to a byte size value."""
79 return _AddFormat(format_utils.BytesToHumanReadable, size)
80
81
82#
83# Payload report generator.
84#
Gilad Arnoldcb638912013-06-24 04:57:11 -070085
Gilad Arnold553b0ec2013-01-26 01:00:39 -080086class _PayloadReport(object):
87 """A payload report generator.
88
89 A report is essentially a sequence of nodes, which represent data points. It
90 is initialized to have a "global", untitled section. A node may be a
91 sub-report itself.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080092 """
93
Gilad Arnoldcb638912013-06-24 04:57:11 -070094 # Report nodes: Field, sub-report, section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080095 class Node(object):
96 """A report node interface."""
97
98 @staticmethod
99 def _Indent(indent, line):
100 """Indents a line by a given indentation amount.
101
102 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700103 indent: The indentation amount.
104 line: The line content (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800105 Returns:
106 The properly indented line (string).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800107 """
108 return '%*s%s' % (indent, '', line)
109
110 def GenerateLines(self, base_indent, sub_indent, curr_section):
111 """Generates the report lines for this node.
112
113 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700114 base_indent: Base indentation for each line.
115 sub_indent: Additional indentation for sub-nodes.
116 curr_section: The current report section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800117 Returns:
118 A pair consisting of a list of properly indented report lines and a new
119 current section object.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800120 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700121 raise NotImplementedError
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800122
123 class FieldNode(Node):
124 """A field report node, representing a (name, value) pair."""
125
126 def __init__(self, name, value, linebreak, indent):
127 super(_PayloadReport.FieldNode, self).__init__()
128 self.name = name
129 self.value = value
130 self.linebreak = linebreak
131 self.indent = indent
132
133 def GenerateLines(self, base_indent, sub_indent, curr_section):
134 """Generates a properly formatted 'name : value' entry."""
135 report_output = ''
136 if self.name:
137 report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
138 value_lines = str(self.value).splitlines()
139 if self.linebreak and self.name:
140 report_output += '\n' + '\n'.join(
141 ['%*s%s' % (self.indent, '', line) for line in value_lines])
142 else:
143 if self.name:
144 report_output += ' '
145 report_output += '%*s' % (self.indent, '')
146 cont_line_indent = len(report_output)
147 indented_value_lines = [value_lines[0]]
148 indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
149 for line in value_lines[1:]])
150 report_output += '\n'.join(indented_value_lines)
151
152 report_lines = [self._Indent(base_indent, line + '\n')
153 for line in report_output.split('\n')]
154 return report_lines, curr_section
155
156 class SubReportNode(Node):
157 """A sub-report node, representing a nested report."""
158
159 def __init__(self, title, report):
160 super(_PayloadReport.SubReportNode, self).__init__()
161 self.title = title
162 self.report = report
163
164 def GenerateLines(self, base_indent, sub_indent, curr_section):
165 """Recurse with indentation."""
166 report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
167 report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
168 sub_indent))
169 return report_lines, curr_section
170
171 class SectionNode(Node):
172 """A section header node."""
173
174 def __init__(self, title=None):
175 super(_PayloadReport.SectionNode, self).__init__()
176 self.title = title
177 self.max_field_name_len = 0
178
179 def GenerateLines(self, base_indent, sub_indent, curr_section):
180 """Dump a title line, return self as the (new) current section."""
181 report_lines = []
182 if self.title:
183 report_lines.append(self._Indent(base_indent,
184 '=== %s ===\n' % self.title))
185 return report_lines, self
186
187 def __init__(self):
188 self.report = []
189 self.last_section = self.global_section = self.SectionNode()
190 self.is_finalized = False
191
192 def GenerateLines(self, base_indent, sub_indent):
193 """Generates the lines in the report, properly indented.
194
195 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700196 base_indent: The indentation used for root-level report lines.
197 sub_indent: The indentation offset used for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800198 Returns:
199 A list of indented report lines.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800200 """
201 report_lines = []
202 curr_section = self.global_section
203 for node in self.report:
204 node_report_lines, curr_section = node.GenerateLines(
205 base_indent, sub_indent, curr_section)
206 report_lines.extend(node_report_lines)
207
208 return report_lines
209
210 def Dump(self, out_file, base_indent=0, sub_indent=2):
211 """Dumps the report to a file.
212
213 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700214 out_file: File object to output the content to.
215 base_indent: Base indentation for report lines.
216 sub_indent: Added indentation for sub-reports.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800217 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800218 report_lines = self.GenerateLines(base_indent, sub_indent)
219 if report_lines and not self.is_finalized:
220 report_lines.append('(incomplete report)\n')
221
222 for line in report_lines:
223 out_file.write(line)
224
225 def AddField(self, name, value, linebreak=False, indent=0):
226 """Adds a field/value pair to the payload report.
227
228 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700229 name: The field's name.
230 value: The field's value.
231 linebreak: Whether the value should be printed on a new line.
232 indent: Amount of extra indent for each line of the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800233 """
234 assert not self.is_finalized
235 if name and self.last_section.max_field_name_len < len(name):
236 self.last_section.max_field_name_len = len(name)
237 self.report.append(self.FieldNode(name, value, linebreak, indent))
238
239 def AddSubReport(self, title):
240 """Adds and returns a sub-report with a title."""
241 assert not self.is_finalized
242 sub_report = self.SubReportNode(title, type(self)())
243 self.report.append(sub_report)
244 return sub_report.report
245
246 def AddSection(self, title):
247 """Adds a new section title."""
248 assert not self.is_finalized
249 self.last_section = self.SectionNode(title)
250 self.report.append(self.last_section)
251
252 def Finalize(self):
253 """Seals the report, marking it as complete."""
254 self.is_finalized = True
255
256
257#
258# Payload verification.
259#
Gilad Arnoldcb638912013-06-24 04:57:11 -0700260
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800261class PayloadChecker(object):
262 """Checking the integrity of an update payload.
263
264 This is a short-lived object whose purpose is to isolate the logic used for
265 verifying the integrity of an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800266 """
267
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700268 def __init__(self, payload, assert_type=None, block_size=0,
269 allow_unhashed=False, disabled_tests=()):
Gilad Arnold272a4992013-05-08 13:12:53 -0700270 """Initialize the checker.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700271
272 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700273 payload: The payload object to check.
274 assert_type: Assert that payload is either 'full' or 'delta' (optional).
275 block_size: Expected filesystem / payload block size (optional).
276 allow_unhashed: Allow operations with unhashed data blobs.
277 disabled_tests: Sequence of tests to disable.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700278 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700279 if not payload.is_init:
280 raise ValueError('Uninitialized update payload.')
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700281
282 # Set checker configuration.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800283 self.payload = payload
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700284 self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
285 if not _IsPowerOfTwo(self.block_size):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700286 raise error.PayloadError(
287 'Expected block (%d) size is not a power of two.' % self.block_size)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700288 if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700289 raise error.PayloadError('Invalid assert_type value (%r).' %
290 assert_type)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700291 self.payload_type = assert_type
292 self.allow_unhashed = allow_unhashed
293
294 # Disable specific tests.
295 self.check_dst_pseudo_extents = (
296 _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
297 self.check_move_same_src_dst_block = (
298 _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
299 self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800300
301 # Reset state; these will be assigned when the manifest is checked.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800302 self.sigs_offset = 0
303 self.sigs_size = 0
Gilad Arnold382df5c2013-05-03 12:49:28 -0700304 self.old_rootfs_fs_size = 0
305 self.old_kernel_fs_size = 0
306 self.new_rootfs_fs_size = 0
307 self.new_kernel_fs_size = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800308
309 @staticmethod
310 def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
311 msg_name=None, linebreak=False, indent=0):
312 """Adds an element from a protobuf message to the payload report.
313
314 Checks to see whether a message contains a given element, and if so adds
315 the element value to the provided report. A missing mandatory element
316 causes an exception to be raised.
317
318 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700319 msg: The message containing the element.
320 name: The name of the element.
321 report: A report object to add the element name/value to.
322 is_mandatory: Whether or not this element must be present.
323 is_submsg: Whether this element is itself a message.
324 convert: A function for converting the element value for reporting.
325 msg_name: The name of the message object (for error reporting).
326 linebreak: Whether the value report should induce a line break.
327 indent: Amount of indent used for reporting the value.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800328 Returns:
329 A pair consisting of the element value and the generated sub-report for
330 it (if the element is a sub-message, None otherwise). If the element is
331 missing, returns (None, None).
332 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700333 error.PayloadError if a mandatory element is missing.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800334 """
335 if not msg.HasField(name):
336 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700337 raise error.PayloadError('%smissing mandatory %s %r.' %
338 (msg_name + ' ' if msg_name else '',
339 'sub-message' if is_submsg else 'field',
340 name))
341 return None, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800342
343 value = getattr(msg, name)
344 if is_submsg:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700345 return value, report and report.AddSubReport(name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800346 else:
347 if report:
348 report.AddField(name, convert(value), linebreak=linebreak,
349 indent=indent)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700350 return value, None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800351
352 @staticmethod
353 def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
354 linebreak=False, indent=0):
355 """Adds a mandatory field; returning first component from _CheckElem."""
356 return PayloadChecker._CheckElem(msg, field_name, report, True, False,
357 convert=convert, msg_name=msg_name,
358 linebreak=linebreak, indent=indent)[0]
359
360 @staticmethod
361 def _CheckOptionalField(msg, field_name, report, convert=str,
362 linebreak=False, indent=0):
363 """Adds an optional field; returning first component from _CheckElem."""
364 return PayloadChecker._CheckElem(msg, field_name, report, False, False,
365 convert=convert, linebreak=linebreak,
366 indent=indent)[0]
367
368 @staticmethod
369 def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
370 """Adds a mandatory sub-message; wrapper for _CheckElem."""
371 return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
372 msg_name)
373
374 @staticmethod
375 def _CheckOptionalSubMsg(msg, submsg_name, report):
376 """Adds an optional sub-message; wrapper for _CheckElem."""
377 return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
378
379 @staticmethod
380 def _CheckPresentIff(val1, val2, name1, name2, obj_name):
381 """Checks that val1 is None iff val2 is None.
382
383 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700384 val1: first value to be compared.
385 val2: second value to be compared.
386 name1: name of object holding the first value.
387 name2: name of object holding the second value.
388 obj_name: Name of the object containing these values.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800389 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700390 error.PayloadError if assertion does not hold.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800391 """
392 if None in (val1, val2) and val1 is not val2:
393 present, missing = (name1, name2) if val2 is None else (name2, name1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700394 raise error.PayloadError('%r present without %r%s.' %
395 (present, missing,
396 ' in ' + obj_name if obj_name else ''))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800397
398 @staticmethod
399 def _Run(cmd, send_data=None):
400 """Runs a subprocess, returns its output.
401
402 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700403 cmd: Sequence of command-line argument for invoking the subprocess.
404 send_data: Data to feed to the process via its stdin.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800405 Returns:
406 A tuple containing the stdout and stderr output of the process.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800407 """
408 run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
409 stdout=subprocess.PIPE)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700410 try:
411 result = run_process.communicate(input=send_data)
412 finally:
413 exit_code = run_process.wait()
414
415 if exit_code:
416 raise RuntimeError('Subprocess %r failed with code %r.' %
417 (cmd, exit_code))
418
419 return result
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800420
421 @staticmethod
422 def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
423 """Verifies an actual hash against a signed one.
424
425 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700426 sig_data: The raw signature data.
427 pubkey_file_name: Public key used for verifying signature.
428 actual_hash: The actual hash digest.
429 sig_name: Signature name for error reporting.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800430 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700431 error.PayloadError if signature could not be verified.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800432 """
433 if len(sig_data) != 256:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700434 raise error.PayloadError(
435 '%s: signature size (%d) not as expected (256).' %
436 (sig_name, len(sig_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800437 signed_data, _ = PayloadChecker._Run(
438 ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
439 send_data=sig_data)
440
Gilad Arnold5502b562013-03-08 13:22:31 -0800441 if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700442 raise error.PayloadError('%s: unexpected signed data length (%d).' %
443 (sig_name, len(signed_data)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800444
Gilad Arnold5502b562013-03-08 13:22:31 -0800445 if not signed_data.startswith(common.SIG_ASN1_HEADER):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700446 raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
447 sig_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800448
Gilad Arnold5502b562013-03-08 13:22:31 -0800449 signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800450 if signed_hash != actual_hash:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700451 raise error.PayloadError(
452 '%s: signed hash (%s) different from actual (%s).' %
453 (sig_name, common.FormatSha256(signed_hash),
454 common.FormatSha256(actual_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800455
456 @staticmethod
457 def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
458 block_name=None):
459 """Checks that a given length fits given block space.
460
461 This ensures that the number of blocks allocated is appropriate for the
462 length of the data residing in these blocks.
463
464 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700465 length: The actual length of the data.
466 num_blocks: The number of blocks allocated for it.
467 block_size: The size of each block in bytes.
468 length_name: Name of length (used for error reporting).
469 block_name: Name of block (used for error reporting).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800470 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700471 error.PayloadError if the aforementioned invariant is not satisfied.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800472 """
473 # Check: length <= num_blocks * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700474 if length > num_blocks * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700475 raise error.PayloadError(
476 '%s (%d) > num %sblocks (%d) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800477 (length_name, length, block_name or '', num_blocks, block_size))
478
479 # Check: length > (num_blocks - 1) * block_size.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700480 if length <= (num_blocks - 1) * block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700481 raise error.PayloadError(
482 '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800483 (length_name, length, block_name or '', num_blocks - 1, block_size))
484
Gilad Arnold382df5c2013-05-03 12:49:28 -0700485 def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800486 """Checks the payload manifest.
487
488 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700489 report: A report object to add to.
490 rootfs_part_size: Size of the rootfs partition in bytes.
491 kernel_part_size: Size of the kernel partition in bytes.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800492 Returns:
493 A tuple consisting of the partition block size used during the update
494 (integer), the signatures block offset and size.
495 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700496 error.PayloadError if any of the checks fail.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800497 """
498 manifest = self.payload.manifest
499 report.AddSection('manifest')
500
501 # Check: block_size must exist and match the expected value.
502 actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
503 report, 'manifest')
504 if actual_block_size != self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700505 raise error.PayloadError('Block_size (%d) not as expected (%d).' %
506 (actual_block_size, self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800507
508 # Check: signatures_offset <==> signatures_size.
509 self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
510 report)
511 self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
512 report)
513 self._CheckPresentIff(self.sigs_offset, self.sigs_size,
514 'signatures_offset', 'signatures_size', 'manifest')
515
516 # Check: old_kernel_info <==> old_rootfs_info.
517 oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
518 'old_kernel_info', report)
519 ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
520 'old_rootfs_info', report)
521 self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
522 'old_rootfs_info', 'manifest')
523 if oki_msg: # equivalently, ori_msg
524 # Assert/mark delta payload.
525 if self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700526 raise error.PayloadError(
527 'Apparent full payload contains old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800528 self.payload_type = _TYPE_DELTA
529
530 # Check: {size, hash} present in old_{kernel,rootfs}_info.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700531 self.old_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800532 oki_msg, 'size', oki_report, 'old_kernel_info')
533 self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
534 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700535 self.old_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800536 ori_msg, 'size', ori_report, 'old_rootfs_info')
537 self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
538 convert=common.FormatSha256)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700539
540 # Check: old_{kernel,rootfs} size must fit in respective partition.
541 if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700542 raise error.PayloadError(
543 'Old kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700544 (self.old_kernel_fs_size, kernel_part_size))
545 if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700546 raise error.PayloadError(
547 'Old rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700548 (self.old_rootfs_fs_size, rootfs_part_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800549 else:
550 # Assert/mark full payload.
551 if self.payload_type == _TYPE_DELTA:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700552 raise error.PayloadError(
553 'Apparent delta payload missing old_{kernel,rootfs}_info.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800554 self.payload_type = _TYPE_FULL
555
556 # Check: new_kernel_info present; contains {size, hash}.
557 nki_msg, nki_report = self._CheckMandatorySubMsg(
558 manifest, 'new_kernel_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700559 self.new_kernel_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800560 nki_msg, 'size', nki_report, 'new_kernel_info')
561 self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
562 convert=common.FormatSha256)
563
564 # Check: new_rootfs_info present; contains {size, hash}.
565 nri_msg, nri_report = self._CheckMandatorySubMsg(
566 manifest, 'new_rootfs_info', report, 'manifest')
Gilad Arnold382df5c2013-05-03 12:49:28 -0700567 self.new_rootfs_fs_size = self._CheckMandatoryField(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800568 nri_msg, 'size', nri_report, 'new_rootfs_info')
569 self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
570 convert=common.FormatSha256)
571
Gilad Arnold382df5c2013-05-03 12:49:28 -0700572 # Check: new_{kernel,rootfs} size must fit in respective partition.
573 if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700574 raise error.PayloadError(
575 'New kernel content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700576 (self.new_kernel_fs_size, kernel_part_size))
577 if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700578 raise error.PayloadError(
579 'New rootfs content (%d) exceed partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700580 (self.new_rootfs_fs_size, rootfs_part_size))
581
Gilad Arnoldcb638912013-06-24 04:57:11 -0700582 # Check: Payload must contain at least one operation.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800583 if not(len(manifest.install_operations) or
584 len(manifest.kernel_install_operations)):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700585 raise error.PayloadError('Payload contains no operations.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800586
587 def _CheckLength(self, length, total_blocks, op_name, length_name):
588 """Checks whether a length matches the space designated in extents.
589
590 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700591 length: The total length of the data.
592 total_blocks: The total number of blocks in extents.
593 op_name: Operation name (for error reporting).
594 length_name: Length name (for error reporting).
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800595 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700596 error.PayloadError is there a problem with the length.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800597 """
598 # Check: length is non-zero.
599 if length == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700600 raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800601
602 # Check that length matches number of blocks.
603 self._CheckBlocksFitLength(length, total_blocks, self.block_size,
604 '%s: %s' % (op_name, length_name))
605
Gilad Arnold382df5c2013-05-03 12:49:28 -0700606 def _CheckExtents(self, extents, usable_size, block_counters, name,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800607 allow_pseudo=False, allow_signature=False):
608 """Checks a sequence of extents.
609
610 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700611 extents: The sequence of extents to check.
612 usable_size: The usable size of the partition to which the extents apply.
613 block_counters: Array of counters corresponding to the number of blocks.
614 name: The name of the extent block.
615 allow_pseudo: Whether or not pseudo block numbers are allowed.
616 allow_signature: Whether or not the extents are used for a signature.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800617 Returns:
618 The total number of blocks in the extents.
619 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700620 error.PayloadError if any of the entailed checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800621 """
622 total_num_blocks = 0
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800623 for ex, ex_name in common.ExtentIter(extents, name):
Gilad Arnoldcb638912013-06-24 04:57:11 -0700624 # Check: Mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800625 start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
626 None, ex_name)
627 num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
628 ex_name)
629 end_block = start_block + num_blocks
630
631 # Check: num_blocks > 0.
632 if num_blocks == 0:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700633 raise error.PayloadError('%s: extent length is zero.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800634
635 if start_block != common.PSEUDO_EXTENT_MARKER:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700636 # Check: Make sure we're within the partition limit.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700637 if usable_size and end_block * self.block_size > usable_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700638 raise error.PayloadError(
639 '%s: extent (%s) exceeds usable partition size (%d).' %
Gilad Arnold382df5c2013-05-03 12:49:28 -0700640 (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800641
642 # Record block usage.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700643 for i in xrange(start_block, end_block):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800644 block_counters[i] += 1
Gilad Arnold5502b562013-03-08 13:22:31 -0800645 elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
646 # Pseudo-extents must be allowed explicitly, or otherwise be part of a
647 # signature operation (in which case there has to be exactly one).
Gilad Arnoldcb638912013-06-24 04:57:11 -0700648 raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800649
650 total_num_blocks += num_blocks
651
652 return total_num_blocks
653
654 def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
655 """Specific checks for REPLACE/REPLACE_BZ operations.
656
657 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700658 op: The operation object from the manifest.
659 data_length: The length of the data blob associated with the operation.
660 total_dst_blocks: Total number of blocks in dst_extents.
661 op_name: Operation name for error reporting.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800662 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700663 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800664 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700665 # Check: Does not contain src extents.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800666 if op.src_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700667 raise error.PayloadError('%s: contains src_extents.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800668
Gilad Arnoldcb638912013-06-24 04:57:11 -0700669 # Check: Contains data.
Gilad Arnold5502b562013-03-08 13:22:31 -0800670 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700671 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800672
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800673 if op.type == common.OpType.REPLACE:
674 PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
675 self.block_size,
676 op_name + '.data_length', 'dst')
677 else:
678 # Check: data_length must be smaller than the alotted dst blocks.
679 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700680 raise error.PayloadError(
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800681 '%s: data_length (%d) must be less than allotted dst block '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700682 'space (%d * %d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800683 (op_name, data_length, total_dst_blocks, self.block_size))
684
685 def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
686 total_dst_blocks, op_name):
687 """Specific checks for MOVE operations.
688
689 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700690 op: The operation object from the manifest.
691 data_offset: The offset of a data blob for the operation.
692 total_src_blocks: Total number of blocks in src_extents.
693 total_dst_blocks: Total number of blocks in dst_extents.
694 op_name: Operation name for error reporting.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800695 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700696 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800697 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700698 # Check: No data_{offset,length}.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800699 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700700 raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800701
Gilad Arnoldcb638912013-06-24 04:57:11 -0700702 # Check: total_src_blocks == total_dst_blocks.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800703 if total_src_blocks != total_dst_blocks:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700704 raise error.PayloadError(
705 '%s: total src blocks (%d) != total dst blocks (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800706 (op_name, total_src_blocks, total_dst_blocks))
707
Gilad Arnoldcb638912013-06-24 04:57:11 -0700708 # Check: For all i, i-th src block index != i-th dst block index.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800709 i = 0
710 src_extent_iter = iter(op.src_extents)
711 dst_extent_iter = iter(op.dst_extents)
712 src_extent = dst_extent = None
713 src_idx = src_num = dst_idx = dst_num = 0
714 while i < total_src_blocks:
715 # Get the next source extent, if needed.
716 if not src_extent:
717 try:
718 src_extent = src_extent_iter.next()
719 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700720 raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
721 (op_name, i, total_src_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800722 src_idx = src_extent.start_block
723 src_num = src_extent.num_blocks
724
725 # Get the next dest extent, if needed.
726 if not dst_extent:
727 try:
728 dst_extent = dst_extent_iter.next()
729 except StopIteration:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700730 raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
731 (op_name, i, total_dst_blocks))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800732 dst_idx = dst_extent.start_block
733 dst_num = dst_extent.num_blocks
734
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700735 if self.check_move_same_src_dst_block and src_idx == dst_idx:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700736 raise error.PayloadError(
737 '%s: src/dst block number %d is the same (%d).' %
738 (op_name, i, src_idx))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800739
740 advance = min(src_num, dst_num)
741 i += advance
742
743 src_idx += advance
744 src_num -= advance
745 if src_num == 0:
746 src_extent = None
747
748 dst_idx += advance
749 dst_num -= advance
750 if dst_num == 0:
751 dst_extent = None
752
Gilad Arnold5502b562013-03-08 13:22:31 -0800753 # Make sure we've exhausted all src/dst extents.
754 if src_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700755 raise error.PayloadError('%s: excess src blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800756 if dst_extent:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700757 raise error.PayloadError('%s: excess dst blocks.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800758
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800759 def _CheckBsdiffOperation(self, data_length, total_dst_blocks, op_name):
760 """Specific checks for BSDIFF operations.
761
762 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700763 data_length: The length of the data blob associated with the operation.
764 total_dst_blocks: Total number of blocks in dst_extents.
765 op_name: Operation name for error reporting.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800766 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700767 error.PayloadError if any check fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800768 """
Gilad Arnold5502b562013-03-08 13:22:31 -0800769 # Check: data_{offset,length} present.
770 if data_length is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700771 raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
Gilad Arnold5502b562013-03-08 13:22:31 -0800772
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800773 # Check: data_length is strictly smaller than the alotted dst blocks.
774 if data_length >= total_dst_blocks * self.block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700775 raise error.PayloadError(
Gilad Arnold5502b562013-03-08 13:22:31 -0800776 '%s: data_length (%d) must be smaller than allotted dst space '
Gilad Arnoldcb638912013-06-24 04:57:11 -0700777 '(%d * %d = %d).' %
Gilad Arnold5502b562013-03-08 13:22:31 -0800778 (op_name, data_length, total_dst_blocks, self.block_size,
779 total_dst_blocks * self.block_size))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800780
781 def _CheckOperation(self, op, op_name, is_last, old_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700782 new_block_counters, old_usable_size, new_usable_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700783 prev_data_offset, allow_signature, blob_hash_counts):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800784 """Checks a single update operation.
785
786 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700787 op: The operation object.
788 op_name: Operation name string for error reporting.
789 is_last: Whether this is the last operation in the sequence.
790 old_block_counters: Arrays of block read counters.
791 new_block_counters: Arrays of block write counters.
792 old_usable_size: The overall usable size for src data in bytes.
793 new_usable_size: The overall usable size for dst data in bytes.
794 prev_data_offset: Offset of last used data bytes.
795 allow_signature: Whether this may be a signature operation.
796 blob_hash_counts: Counters for hashed/unhashed blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800797 Returns:
798 The amount of data blob associated with the operation.
799 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700800 error.PayloadError if any check has failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800801 """
802 # Check extents.
803 total_src_blocks = self._CheckExtents(
Gilad Arnold4f50b412013-05-14 09:19:17 -0700804 op.src_extents, old_usable_size, old_block_counters,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800805 op_name + '.src_extents', allow_pseudo=True)
806 allow_signature_in_extents = (allow_signature and is_last and
807 op.type == common.OpType.REPLACE)
808 total_dst_blocks = self._CheckExtents(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700809 op.dst_extents, new_usable_size, new_block_counters,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700810 op_name + '.dst_extents',
811 allow_pseudo=(not self.check_dst_pseudo_extents),
812 allow_signature=allow_signature_in_extents)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800813
814 # Check: data_offset present <==> data_length present.
815 data_offset = self._CheckOptionalField(op, 'data_offset', None)
816 data_length = self._CheckOptionalField(op, 'data_length', None)
817 self._CheckPresentIff(data_offset, data_length, 'data_offset',
818 'data_length', op_name)
819
Gilad Arnoldcb638912013-06-24 04:57:11 -0700820 # Check: At least one dst_extent.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800821 if not op.dst_extents:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700822 raise error.PayloadError('%s: dst_extents is empty.' % op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800823
824 # Check {src,dst}_length, if present.
825 if op.HasField('src_length'):
826 self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
827 if op.HasField('dst_length'):
828 self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
829
830 if op.HasField('data_sha256_hash'):
831 blob_hash_counts['hashed'] += 1
832
Gilad Arnoldcb638912013-06-24 04:57:11 -0700833 # Check: Operation carries data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800834 if data_offset is None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700835 raise error.PayloadError(
836 '%s: data_sha256_hash present but no data_{offset,length}.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800837 op_name)
838
Gilad Arnoldcb638912013-06-24 04:57:11 -0700839 # Check: Hash verifies correctly.
840 # pylint cannot find the method in hashlib, for some reason.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800841 # pylint: disable=E1101
842 actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
843 data_length))
844 if op.data_sha256_hash != actual_hash.digest():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700845 raise error.PayloadError(
846 '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
Gilad Arnold96405372013-05-04 00:24:58 -0700847 (op_name, common.FormatSha256(op.data_sha256_hash),
848 common.FormatSha256(actual_hash.digest())))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800849 elif data_offset is not None:
850 if allow_signature_in_extents:
851 blob_hash_counts['signature'] += 1
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700852 elif self.allow_unhashed:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800853 blob_hash_counts['unhashed'] += 1
854 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700855 raise error.PayloadError('%s: unhashed operation not allowed.' %
856 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800857
858 if data_offset is not None:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700859 # Check: Contiguous use of data section.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800860 if data_offset != prev_data_offset:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700861 raise error.PayloadError(
862 '%s: data offset (%d) not matching amount used so far (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800863 (op_name, data_offset, prev_data_offset))
864
865 # Type-specific checks.
866 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
867 self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
868 elif self.payload_type == _TYPE_FULL:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700869 raise error.PayloadError('%s: non-REPLACE operation in a full payload.' %
870 op_name)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800871 elif op.type == common.OpType.MOVE:
872 self._CheckMoveOperation(op, data_offset, total_src_blocks,
873 total_dst_blocks, op_name)
874 elif op.type == common.OpType.BSDIFF:
875 self._CheckBsdiffOperation(data_length, total_dst_blocks, op_name)
876 else:
877 assert False, 'cannot get here'
878
879 return data_length if data_length is not None else 0
880
Gilad Arnold382df5c2013-05-03 12:49:28 -0700881 def _SizeToNumBlocks(self, size):
882 """Returns the number of blocks needed to contain a given byte size."""
883 return (size + self.block_size - 1) / self.block_size
884
885 def _AllocBlockCounters(self, total_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800886 """Returns a freshly initialized array of block counters.
887
Gilad Arnoldcb638912013-06-24 04:57:11 -0700888 Note that the generated array is not portable as is due to byte-ordering
889 issues, hence it should not be serialized.
890
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800891 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700892 total_size: The total block size in bytes.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800893 Returns:
Gilad Arnold9753f3d2013-07-23 08:34:45 -0700894 An array of unsigned short elements initialized to zero, one for each of
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800895 the blocks necessary for containing the partition.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800896 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700897 return array.array('H',
898 itertools.repeat(0, self._SizeToNumBlocks(total_size)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800899
Gilad Arnold382df5c2013-05-03 12:49:28 -0700900 def _CheckOperations(self, operations, report, base_name, old_fs_size,
901 new_fs_size, new_usable_size, prev_data_offset,
902 allow_signature):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800903 """Checks a sequence of update operations.
904
905 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700906 operations: The sequence of operations to check.
907 report: The report object to add to.
908 base_name: The name of the operation block.
909 old_fs_size: The old filesystem size in bytes.
910 new_fs_size: The new filesystem size in bytes.
911 new_usable_size: The overall usable size of the new partition in bytes.
912 prev_data_offset: Offset of last used data bytes.
913 allow_signature: Whether this sequence may contain signature operations.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800914 Returns:
Gilad Arnold5502b562013-03-08 13:22:31 -0800915 The total data blob size used.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800916 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700917 error.PayloadError if any of the checks fails.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800918 """
919 # The total size of data blobs used by operations scanned thus far.
920 total_data_used = 0
921 # Counts of specific operation types.
922 op_counts = {
923 common.OpType.REPLACE: 0,
924 common.OpType.REPLACE_BZ: 0,
925 common.OpType.MOVE: 0,
926 common.OpType.BSDIFF: 0,
927 }
928 # Total blob sizes for each operation type.
929 op_blob_totals = {
930 common.OpType.REPLACE: 0,
931 common.OpType.REPLACE_BZ: 0,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700932 # MOVE operations don't have blobs.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800933 common.OpType.BSDIFF: 0,
934 }
935 # Counts of hashed vs unhashed operations.
936 blob_hash_counts = {
937 'hashed': 0,
938 'unhashed': 0,
939 }
940 if allow_signature:
941 blob_hash_counts['signature'] = 0
942
943 # Allocate old and new block counters.
Gilad Arnold4f50b412013-05-14 09:19:17 -0700944 old_block_counters = (self._AllocBlockCounters(new_usable_size)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700945 if old_fs_size else None)
946 new_block_counters = self._AllocBlockCounters(new_usable_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800947
948 # Process and verify each operation.
949 op_num = 0
950 for op, op_name in common.OperationIter(operations, base_name):
951 op_num += 1
952
Gilad Arnoldcb638912013-06-24 04:57:11 -0700953 # Check: Type is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800954 if op.type not in op_counts.keys():
Gilad Arnoldcb638912013-06-24 04:57:11 -0700955 raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800956 op_counts[op.type] += 1
957
958 is_last = op_num == len(operations)
959 curr_data_used = self._CheckOperation(
960 op, op_name, is_last, old_block_counters, new_block_counters,
Gilad Arnold4f50b412013-05-14 09:19:17 -0700961 new_usable_size if old_fs_size else 0, new_usable_size,
962 prev_data_offset + total_data_used, allow_signature,
963 blob_hash_counts)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800964 if curr_data_used:
965 op_blob_totals[op.type] += curr_data_used
966 total_data_used += curr_data_used
967
968 # Report totals and breakdown statistics.
969 report.AddField('total operations', op_num)
970 report.AddField(
971 None,
972 histogram.Histogram.FromCountDict(op_counts,
973 key_names=common.OpType.NAMES),
974 indent=1)
975 report.AddField('total blobs', sum(blob_hash_counts.values()))
976 report.AddField(None,
977 histogram.Histogram.FromCountDict(blob_hash_counts),
978 indent=1)
979 report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
980 report.AddField(
981 None,
982 histogram.Histogram.FromCountDict(op_blob_totals,
983 formatter=_AddHumanReadableSize,
984 key_names=common.OpType.NAMES),
985 indent=1)
986
987 # Report read/write histograms.
988 if old_block_counters:
989 report.AddField('block read hist',
990 histogram.Histogram.FromKeyList(old_block_counters),
991 linebreak=True, indent=1)
992
Gilad Arnold382df5c2013-05-03 12:49:28 -0700993 new_write_hist = histogram.Histogram.FromKeyList(
994 new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
995 report.AddField('block write hist', new_write_hist, linebreak=True,
996 indent=1)
997
Gilad Arnoldcb638912013-06-24 04:57:11 -0700998 # Check: Full update must write each dst block once.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800999 if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001000 raise error.PayloadError(
1001 '%s: not all blocks written exactly once during full update.' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001002 base_name)
1003
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001004 return total_data_used
1005
1006 def _CheckSignatures(self, report, pubkey_file_name):
1007 """Checks a payload's signature block."""
1008 sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
1009 sigs = update_metadata_pb2.Signatures()
1010 sigs.ParseFromString(sigs_raw)
1011 report.AddSection('signatures')
1012
Gilad Arnoldcb638912013-06-24 04:57:11 -07001013 # Check: At least one signature present.
1014 # pylint cannot see through the protobuf object, it seems.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001015 # pylint: disable=E1101
1016 if not sigs.signatures:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001017 raise error.PayloadError('Signature block is empty.')
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001018
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001019 last_ops_section = (self.payload.manifest.kernel_install_operations or
1020 self.payload.manifest.install_operations)
1021 fake_sig_op = last_ops_section[-1]
Gilad Arnold5502b562013-03-08 13:22:31 -08001022 # Check: signatures_{offset,size} must match the last (fake) operation.
1023 if not (fake_sig_op.type == common.OpType.REPLACE and
1024 self.sigs_offset == fake_sig_op.data_offset and
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001025 self.sigs_size == fake_sig_op.data_length):
Gilad Arnoldcb638912013-06-24 04:57:11 -07001026 raise error.PayloadError(
1027 'Signatures_{offset,size} (%d+%d) does not match last operation '
1028 '(%d+%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001029 (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
1030 fake_sig_op.data_length))
1031
1032 # Compute the checksum of all data up to signature blob.
1033 # TODO(garnold) we're re-reading the whole data section into a string
1034 # just to compute the checksum; instead, we could do it incrementally as
1035 # we read the blobs one-by-one, under the assumption that we're reading
1036 # them in order (which currently holds). This should be reconsidered.
1037 payload_hasher = self.payload.manifest_hasher.copy()
1038 common.Read(self.payload.payload_file, self.sigs_offset,
1039 offset=self.payload.data_offset, hasher=payload_hasher)
1040
1041 for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
1042 sig_report = report.AddSubReport(sig_name)
1043
Gilad Arnoldcb638912013-06-24 04:57:11 -07001044 # Check: Signature contains mandatory fields.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001045 self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
1046 self._CheckMandatoryField(sig, 'data', None, sig_name)
1047 sig_report.AddField('data len', len(sig.data))
1048
Gilad Arnoldcb638912013-06-24 04:57:11 -07001049 # Check: Signatures pertains to actual payload hash.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001050 if sig.version == 1:
1051 self._CheckSha256Signature(sig.data, pubkey_file_name,
1052 payload_hasher.digest(), sig_name)
1053 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001054 raise error.PayloadError('Unknown signature version (%d).' %
1055 sig.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001056
1057 def Run(self, pubkey_file_name=None, metadata_sig_file=None,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001058 rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001059 """Checker entry point, invoking all checks.
1060
1061 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001062 pubkey_file_name: Public key used for signature verification.
1063 metadata_sig_file: Metadata signature, if verification is desired.
1064 rootfs_part_size: The size of rootfs partitions in bytes (default: use
1065 reported filesystem size).
1066 kernel_part_size: The size of kernel partitions in bytes (default: use
1067 reported filesystem size).
1068 report_out_file: File object to dump the report to.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001069 Raises:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001070 error.PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001071 """
Gilad Arnold9b90c932013-05-22 17:12:56 -07001072 if not pubkey_file_name:
1073 pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
1074
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001075 report = _PayloadReport()
1076
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001077 # Get payload file size.
1078 self.payload.payload_file.seek(0, 2)
1079 payload_file_size = self.payload.payload_file.tell()
1080 self.payload.ResetFile()
1081
1082 try:
1083 # Check metadata signature (if provided).
1084 if metadata_sig_file:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001085 metadata_sig = base64.b64decode(metadata_sig_file.read())
1086 self._CheckSha256Signature(metadata_sig, pubkey_file_name,
1087 self.payload.manifest_hasher.digest(),
1088 'metadata signature')
1089
Gilad Arnoldcb638912013-06-24 04:57:11 -07001090 # Part 1: Check the file header.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001091 report.AddSection('header')
Gilad Arnoldcb638912013-06-24 04:57:11 -07001092 # Check: Payload version is valid.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001093 if self.payload.header.version != 1:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001094 raise error.PayloadError('Unknown payload version (%d).' %
1095 self.payload.header.version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001096 report.AddField('version', self.payload.header.version)
1097 report.AddField('manifest len', self.payload.header.manifest_len)
1098
Gilad Arnoldcb638912013-06-24 04:57:11 -07001099 # Part 2: Check the manifest.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001100 self._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001101 assert self.payload_type, 'payload type should be known by now'
1102
Gilad Arnoldcb638912013-06-24 04:57:11 -07001103 # Part 3: Examine rootfs operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001104 # TODO(garnold)(chromium:243559) only default to the filesystem size if
1105 # no explicit size provided *and* the partition size is not embedded in
1106 # the payload; see issue for more details.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001107 report.AddSection('rootfs operations')
1108 total_blob_size = self._CheckOperations(
1109 self.payload.manifest.install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001110 'install_operations', self.old_rootfs_fs_size,
1111 self.new_rootfs_fs_size,
1112 rootfs_part_size if rootfs_part_size else self.new_rootfs_fs_size,
1113 0, False)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001114
Gilad Arnoldcb638912013-06-24 04:57:11 -07001115 # Part 4: Examine kernel operations.
Gilad Arnold0990f512013-05-30 17:09:31 -07001116 # TODO(garnold)(chromium:243559) as above.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001117 report.AddSection('kernel operations')
1118 total_blob_size += self._CheckOperations(
1119 self.payload.manifest.kernel_install_operations, report,
Gilad Arnold382df5c2013-05-03 12:49:28 -07001120 'kernel_install_operations', self.old_kernel_fs_size,
1121 self.new_kernel_fs_size,
1122 kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
1123 total_blob_size, True)
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001124
Gilad Arnoldcb638912013-06-24 04:57:11 -07001125 # Check: Operations data reach the end of the payload file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001126 used_payload_size = self.payload.data_offset + total_blob_size
1127 if used_payload_size != payload_file_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001128 raise error.PayloadError(
1129 'Used payload size (%d) different from actual file size (%d).' %
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001130 (used_payload_size, payload_file_size))
1131
Gilad Arnoldcb638912013-06-24 04:57:11 -07001132 # Part 5: Handle payload signatures message.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001133 if self.check_payload_sig and self.sigs_size:
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001134 self._CheckSignatures(report, pubkey_file_name)
1135
Gilad Arnoldcb638912013-06-24 04:57:11 -07001136 # Part 6: Summary.
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001137 report.AddSection('summary')
1138 report.AddField('update type', self.payload_type)
1139
1140 report.Finalize()
1141 finally:
1142 if report_out_file:
1143 report.Dump(report_out_file)