blob: 9a293029378850a2e4408701b1d32aaa2667e79e [file] [log] [blame]
Tom Wai-Hong Tamc0168912012-09-13 13:24:02 +08001#!/usr/bin/python
2# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6'''A module to support automated testing of ChromeOS firmware.
7
8Utilizes services provided by saft_flashrom_util.py read/write the
9flashrom chip and to parse the flash rom image.
10
11See docstring for FlashromHandler class below.
12'''
13
14import hashlib
15import os
16import struct
17
18class FvSection(object):
19 '''An object to hold information about a firmware section.
20
21 This includes file names for the signature header and the body, and the
22 version number.
23 '''
24
25 def __init__(self, sig_name, body_name):
26 self._sig_name = sig_name
27 self._body_name = body_name
28 self._version = -1 # Is not set on construction.
29 self._flags = 0 # Is not set on construction.
30 self._sha = None # Is not set on construction.
31 self._datakey_version = -1 # Is not set on construction.
32 self._kernel_subkey_version = -1 # Is not set on construction.
33
34 def names(self):
35 return (self._sig_name, self._body_name)
36
37 def get_sig_name(self):
38 return self._sig_name
39
40 def get_body_name(self):
41 return self._body_name
42
43 def get_version(self):
44 return self._version
45
46 def get_flags(self):
47 return self._flags
48
49 def get_sha(self):
50 return self._sha
51
52 def get_datakey_version(self):
53 return self._datakey_version
54
55 def get_kernel_subkey_version(self):
56 return self._kernel_subkey_version
57
58 def set_version(self, version):
59 self._version = version
60
61 def set_flags(self, flags):
62 self._flags = flags
63
64 def set_sha(self, sha):
65 self._sha = sha
66
67 def set_datakey_version(self, version):
68 self._datakey_version = version
69
70 def set_kernel_subkey_version(self, version):
71 self._kernel_subkey_version = version
72
73class FlashromHandlerError(Exception):
74 pass
75
76
77class FlashromHandler(object):
78 '''An object to provide logical services for automated flashrom testing.'''
79
80 DELTA = 1 # value to add to a byte to corrupt a section contents
81
82 # File in the state directory to store public root key.
83 PUB_KEY_FILE_NAME = 'root.pubkey'
84 FW_KEYBLOCK_FILE_NAME = 'firmware.keyblock'
85 FW_PRIV_DATA_KEY_FILE_NAME = 'firmware_data_key.vbprivk'
86 KERNEL_SUBKEY_FILE_NAME = 'kernel_subkey.vbpubk'
87
88 def __init__(self):
89 # make sure it does not accidentally overwrite the image.
90 self.fum = None
91 self.chros_if = None
92 self.image = ''
93 self.pub_key_file = ''
94
95 def init(self, flashrom_util_module,
96 chros_if,
97 pub_key_file=None,
98 dev_key_path='./',
99 target='bios'):
100 '''Flashrom handler initializer.
101
102 Args:
103 flashrom_util_module - a module providing flashrom access utilities.
104 chros_if - a module providing interface to Chromium OS services
105 pub_key_file - a string, name of the file contaning a public key to
106 use for verifying both existing and new firmware.
107 '''
108 if target == 'bios':
109 self.fum = flashrom_util_module.flashrom_util(target_is_ec=False)
110 self.fv_sections = {
111 'a': FvSection('VBOOTA', 'FVMAIN'),
112 'b': FvSection('VBOOTB', 'FVMAINB'),
113 }
114 elif target == 'ec':
115 self.fum = flashrom_util_module.flashrom_util(target_is_ec=True)
116 self.fv_sections = {
117 'rw': FvSection(None, 'EC_RW'),
118 }
119 else:
120 raise FlashromHandlerError("Invalid target.")
121 self.chros_if = chros_if
122 self.pub_key_file = pub_key_file
123 self.dev_key_path = dev_key_path
124
125 def new_image(self, image_file=None):
126 '''Parse the full flashrom image and store sections into files.
127
128 Args:
129 image_file - a string, the name of the file contaning full ChromeOS
130 flashrom image. If not passed in or empty - the actual
131 flashrom is read and its contents are saved into a
132 temporary file which is used instead.
133
134 The input file is parsed and the sections of importance (as defined in
135 self.fv_sections) are saved in separate files in the state directory
136 as defined in the chros_if object.
137 '''
138
139 if image_file:
140 self.image = open(image_file, 'rb').read()
141 self.fum.set_firmware_layout(image_file)
142 else:
143 self.image = self.fum.read_whole()
144
145 for section in self.fv_sections.itervalues():
146 for subsection_name in section.names():
147 if not subsection_name:
148 continue
149 f = open(self.chros_if.state_dir_file(subsection_name), 'wb')
150 f.write(self.fum.get_section(self.image, subsection_name))
151 f.close()
152
153 s = hashlib.sha1()
154 s.update(self.fum.get_section(self.image, section.get_body_name()))
155 section.set_sha(s.hexdigest())
156
157 # If there is no "sig" subsection, skip reading version and flags.
158 if not section.get_sig_name():
159 continue
160
161 # Now determine this section's version number.
162 vb_section = self.fum.get_section(
163 self.image, section.get_sig_name())
164
165 section.set_version(self.chros_if.retrieve_body_version(vb_section))
166 section.set_flags(self.chros_if.retrieve_preamble_flags(vb_section))
167 section.set_datakey_version(
168 self.chros_if.retrieve_datakey_version(vb_section))
169 section.set_kernel_subkey_version(
170 self.chros_if.retrieve_kernel_subkey_version(vb_section))
171
172 if not self.pub_key_file:
173 self._retrieve_pub_key()
174
175 def _retrieve_pub_key(self):
176 '''Retrieve root public key from the firmware GBB section.'''
177
178 gbb_header_format = '<4s20s2I'
179 pubk_header_format = '<2Q'
180
181 gbb_section = self.fum.get_section(self.image, 'FV_GBB')
182
183 # do some sanity checks
184 try:
185 sig, _, rootk_offs, rootk_size = struct.unpack_from(
186 gbb_header_format, gbb_section)
187 except struct.error, e:
188 raise FlashromHandlerError(e)
189
190 if sig != '$GBB' or (rootk_offs + rootk_size) > len(gbb_section):
191 raise FlashromHandlerError('Bad gbb header')
192
193 key_body_offset, key_body_size = struct.unpack_from(
194 pubk_header_format, gbb_section, rootk_offs)
195
196 # Generally speaking the offset field can be anything, but in case of
197 # GBB section the key is stored as a standalone entity, so the offset
198 # of the key body is expected to be equal to the key header size of
199 # 0x20.
200 # Should this convention change, the check below would fail, which
201 # would be a good prompt for revisiting this test's behavior and
202 # algorithms.
203 if key_body_offset != 0x20 or key_body_size > rootk_size:
204 raise FlashromHandlerError('Bad public key format')
205
206 # All checks passed, let's store the key in a file.
207 self.pub_key_file = self.chros_if.state_dir_file(self.PUB_KEY_FILE_NAME)
208 keyf = open(self.pub_key_file, 'w')
209 key = gbb_section[
210 rootk_offs:rootk_offs + key_body_offset + key_body_size]
211 keyf.write(key)
212 keyf.close()
213
214 def verify_image(self):
215 '''Confirm the image's validity.
216
217 Using the file supplied to init() as the public key container verify
218 the two sections' (FirmwareA and FirmwareB) integrity. The contents of
219 the sections is taken from the files created by new_image()
220
221 In case there is an integrity error raises FlashromHandlerError
222 exception with the appropriate error message text.
223 '''
224
225 for section in self.fv_sections.itervalues():
226 cmd = 'vbutil_firmware --verify %s --signpubkey %s --fv %s' % (
227 self.chros_if.state_dir_file(section.get_sig_name()),
228 self.pub_key_file,
229 self.chros_if.state_dir_file(section.get_body_name()))
230 self.chros_if.run_shell_command(cmd)
231
232 def _modify_section(self, section, delta, body_or_sig=False, corrupt_all=False):
233 '''Modify a firmware section inside the image, either body or signature.
234
235 If corrupt_all is set, the passed in delta is added to all bytes in the
236 section. Otherwise, the delta is added to the value located at 2% offset
237 into the section blob, either body or signature.
238
239 Calling this function again for the same section the complimentary
240 delta value would restore the section contents.
241 '''
242
243 if not self.image:
244 raise FlashromHandlerError(
245 'Attempt at using an uninitialized object')
246 if section not in self.fv_sections:
247 raise FlashromHandlerError('Unknown FW section %s'
248 % section)
249
250 # Get the appropriate section of the image.
251 if body_or_sig:
252 subsection_name = self.fv_sections[section].get_body_name()
253 else:
254 subsection_name = self.fv_sections[section].get_sig_name()
255 blob = self.fum.get_section(self.image, subsection_name)
256
257 # Modify the byte in it within 2% of the section blob.
258 modified_index = len(blob) / 50
259 if corrupt_all:
260 blob_list = [('%c' % ((ord(x) + delta) % 0x100)) for x in blob]
261 else:
262 blob_list = list(blob)
263 blob_list[modified_index] = ('%c' %
264 ((ord(blob[modified_index]) + delta) % 0x100))
265 self.image = self.fum.put_section(self.image,
266 subsection_name, ''.join(blob_list))
267
268 return subsection_name
269
270 def corrupt_section(self, section, corrupt_all=False):
271 '''Corrupt a section signature of the image'''
272
273 return self._modify_section(section, self.DELTA, body_or_sig=False,
274 corrupt_all=corrupt_all)
275
276 def corrupt_section_body(self, section, corrupt_all=False):
277 '''Corrupt a section body of the image'''
278
279 return self._modify_section(section, self.DELTA, body_or_sig=True,
280 corrupt_all=corrupt_all)
281
282 def restore_section(self, section, restore_all=False):
283 '''Restore a previously corrupted section signature of the image.'''
284
285 return self._modify_section(section, -self.DELTA, body_or_sig=False,
286 corrupt_all=restore_all)
287
288 def restore_section_body(self, section, restore_all=False):
289 '''Restore a previously corrupted section body of the image.'''
290
291 return self._modify_section(section, -self.DELTA, body_or_sig=True,
292 corrupt_all=restore_all)
293
294 def corrupt_firmware(self, section, corrupt_all=False):
295 '''Corrupt a section signature in the FLASHROM!!!'''
296
297 subsection_name = self.corrupt_section(section, corrupt_all=corrupt_all)
298 self.fum.write_partial(self.image, (subsection_name, ))
299
300 def corrupt_firmware_body(self, section, corrupt_all=False):
301 '''Corrupt a section body in the FLASHROM!!!'''
302
303 subsection_name = self.corrupt_section_body(section,
304 corrupt_all=corrupt_all)
305 self.fum.write_partial(self.image, (subsection_name, ))
306
307 def restore_firmware(self, section, restore_all=False):
308 '''Restore the previously corrupted section sig in the FLASHROM!!!'''
309
310 subsection_name = self.restore_section(section, restore_all=restore_all)
311 self.fum.write_partial(self.image, (subsection_name, ))
312
313 def restore_firmware_body(self, section, restore_all=False):
314 '''Restore the previously corrupted section body in the FLASHROM!!!'''
315
316 subsection_name = self.restore_section_body(section,
317 restore_all=False)
318 self.fum.write_partial(self.image, (subsection_name, ))
319
320 def firmware_sections_equal(self):
321 '''Check if firmware sections A and B are equal.
322
323 This function presumes that the entire BIOS image integrity has been
324 verified, so different signature sections mean different images and
325 vice versa.
326 '''
327 sig_a = self.fum.get_section(self.image,
328 self.fv_sections['a'].get_sig_name())
329 sig_b = self.fum.get_section(self.image,
330 self.fv_sections['b'].get_sig_name())
331 return sig_a == sig_b
332
333 def copy_from_to(self, src, dst):
334 '''Copy one firmware image section to another.
335
336 This function copies both signature and body of one firmware section
337 into another. After this function runs both sections are identical.
338 '''
339 src_sect = self.fv_sections[src]
340 dst_sect = self.fv_sections[dst]
341 self.image = self.fum.put_section(
342 self.image,
343 dst_sect.get_body_name(),
344 self.fum.get_section(self.image, src_sect.get_body_name()))
345 self.image = self.fum.put_section(
346 self.image,
347 dst_sect.get_sig_name(),
348 self.fum.get_section(self.image, src_sect.get_sig_name()))
349
350 def write_whole(self):
351 '''Write the whole image into the flashrom.'''
352
353 if not self.image:
354 raise FlashromHandlerError(
355 'Attempt at using an uninitialized object')
356 self.fum.write_whole(self.image)
357
358 def dump_whole(self, filename):
359 '''Write the whole image into a file.'''
360
361 if not self.image:
362 raise FlashromHandlerError(
363 'Attempt at using an uninitialized object')
364 open(filename, 'w').write(self.image)
365
366 def dump_partial(self, subsection_name, filename):
367 '''Write the subsection part into a file.'''
368
369 if not self.image:
370 raise FlashromHandlerError(
371 'Attempt at using an uninitialized object')
372 blob = self.fum.get_section(self.image, subsection_name)
373 open(filename, 'w').write(blob)
374
375 def get_gbb_flags(self):
376 '''Retrieve the GBB flags'''
377 gbb_header_format = '<12sL'
378 gbb_section = self.fum.get_section(self.image, 'FV_GBB')
379 try:
380 _, gbb_flags = struct.unpack_from(gbb_header_format, gbb_section)
381 except struct.error, e:
382 raise FlashromHandlerError(e)
383 return gbb_flags
384
385 def get_section_sha(self, section):
386 '''Retrieve SHA1 hash of a firmware body section'''
387 return self.fv_sections[section].get_sha()
388
389 def get_section_version(self, section):
390 '''Retrieve version number of a firmware section'''
391 return self.fv_sections[section].get_version()
392
393 def get_section_flags(self, section):
394 '''Retrieve preamble flags of a firmware section'''
395 return self.fv_sections[section].get_flags()
396
397 def get_section_datakey_version(self, section):
398 '''Retrieve data key version number of a firmware section'''
399 return self.fv_sections[section].get_datakey_version()
400
401 def get_section_kernel_subkey_version(self, section):
402 '''Retrieve kernel subkey version number of a firmware section'''
403 return self.fv_sections[section].get_kernel_subkey_version()
404
405 def get_section_body(self, section):
406 '''Retrieve body of a firmware section'''
407 subsection_name = self.fv_sections[section].get_body_name()
408 blob = self.fum.get_section(self.image, subsection_name)
409 return blob
410
411 def _find_ecbin_offset(self, blob):
412 '''Return the offset of EC binary from the given firmware blob'''
413 # The RW firmware is concatenated from u-boot, dtb, and ecbin.
414 # Search the magic of dtb to locate the dtb bloc.
415 dtb_offset = blob.index("\xD0\x0D\xFE\xED\x00")
416 # The dtb size is a 32-bit integer which follows the magic.
417 _, dtb_size = struct.unpack_from(">2L", blob, dtb_offset)
418 # The ecbin part is aligned to 4-byte.
419 ecbin_offset = (dtb_offset + dtb_size + 3) & ~3
420 return ecbin_offset
421
422 def _find_ecbin_size_offset_on_dtb(self, blob):
423 '''Return the offset of EC binary size on the DTB blob'''
424 # We now temporarily use this hack to find the offset.
425 # TODO(waihong@chromium.org): Should use fdtget to get the field and
426 # fdtput to change it.
427 dtb_offset = blob.index("\xD0\x0D\xFE\xED\x00")
428 prop_offset = blob.index("blob boot,dtb,ecbin", dtb_offset) + 0x18
429 ecbin_size_offset = blob.index("ecbin", prop_offset) + 0x18
430 return ecbin_size_offset
431
432 def get_section_ecbin(self, section):
433 '''Retrieve EC binary of a firmware section'''
434 blob = self.get_section_body(section)
435 ecbin_offset = self._find_ecbin_offset(blob)
436 # Remove the pads of ecbin.
437 pad = blob[-1]
438 ecbin = blob[ecbin_offset :].rstrip(pad)
439 return ecbin
440
441 def set_section_body(self, section, blob):
442 '''Put the supplied blob to the body of the firmware section'''
443 subsection_name = self.fv_sections[section].get_body_name()
444 self.image = self.fum.put_section(self.image, subsection_name, blob)
445
446 def set_section_ecbin(self, section, ecbin, write_through=False):
447 '''Put the supplied EC binary to the firwmare section.
448
449 Note that the updated firmware image is not signed yet. Should call
450 set_section_version() afterward.
451 '''
452 # Remove unncessary padding bytes.
453 pad = '\xff'
454 ecbin_align = 4
455 ecbin = ecbin.rstrip(pad)
456 ecbin += pad * ((ecbin_align - 1) - (len(ecbin) - 1) % ecbin_align)
457 ecbin_size = len(ecbin)
458
459 # Put the ecbin into the firmware body.
460 old_blob = self.get_section_body(section)
461 ecbin_offset = self._find_ecbin_offset(old_blob)
462 pad = old_blob[-1]
463 pad_size = len(old_blob) - ecbin_offset - ecbin_size
464 new_blob = old_blob[0 : ecbin_offset] + ecbin + pad * pad_size
465
466 # Modify the ecbin size on dtb.
467 size_offset = self._find_ecbin_size_offset_on_dtb(new_blob)
468 new_blob = (new_blob[0 : size_offset] + struct.pack('>L', ecbin_size) +
469 new_blob[size_offset + 4 :])
470
471 self.set_section_body(section, new_blob)
472 if write_through:
473 subsection_name = self.fv_sections[section].get_body_name()
474 self.dump_partial(subsection_name,
475 self.chros_if.state_dir_file(subsection_name))
476 self.fum.write_partial(self.image, (subsection_name, ))
477
478 def set_section_version(self, section, version, flags,
479 write_through=False):
480 '''
481 Re-sign the firmware section using the supplied version number and
482 flag.
483 '''
484 if (self.get_section_version(section) == version and
485 self.get_section_flags(section) == flags):
486 return # No version or flag change, nothing to do.
487 if version < 0:
488 raise FlashromHandlerError(
489 'Attempt to set version %d on section %s' % (version, section))
490 fv_section = self.fv_sections[section]
491 sig_name = self.chros_if.state_dir_file(fv_section.get_sig_name())
492 sig_size = os.path.getsize(sig_name)
493
494 # Construct the command line
495 args = ['--vblock %s' % sig_name]
496 args.append('--keyblock %s' % os.path.join(
497 self.dev_key_path, self.FW_KEYBLOCK_FILE_NAME))
498 args.append('--fv %s' % self.chros_if.state_dir_file(
499 fv_section.get_body_name()))
500 args.append('--version %d' % version)
501 args.append('--kernelkey %s' % os.path.join(
502 self.dev_key_path, self.KERNEL_SUBKEY_FILE_NAME))
503 args.append('--signprivate %s' % os.path.join(
504 self.dev_key_path, self.FW_PRIV_DATA_KEY_FILE_NAME))
505 args.append('--flags %d' % flags)
506 cmd = 'vbutil_firmware %s' % ' '.join(args)
507 self.chros_if.run_shell_command(cmd)
508
509 # Pad the new signature.
510 new_sig = open(sig_name, 'a')
511 pad = ('%c' % 0xff) * (sig_size - os.path.getsize(sig_name))
512 new_sig.write(pad)
513 new_sig.close()
514
515 # Inject the new signature block into the image
516 new_sig = open(sig_name, 'r').read()
517 self.image = self.fum.put_section(
518 self.image, fv_section.get_sig_name(), new_sig)
519 if write_through:
520 self.fum.write_partial(self.image, (fv_section.get_sig_name(), ))