tests2/experimental/vboot_tests/run-tests.py (239 lines of code) (raw):

#!/usr/bin/env python2 # Copyright (c) 2014-present, Facebook, Inc. # All rights reserved. # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. An additional grant # of patent rights can be found in the PATENTS file in the same directory. import pexpect import sys import argparse import subprocess TPM_SOCKET = '/var/run/tpm/tpmd_socket:0' QEMU_DRIVE = '-drive if=mtd,format=raw,file=' QEMU_OPTS = [ '-nographic', '-M ast2500-edk', '-m 256M', ] def expected(buffer, matches): for match in matches: if buffer.find(match) < 0: raise Exception('Cannot find: %s \n%s' % (match, buffer)) def expect_or_expected(c, match): try: expected(c.before, [match]) return True except Exception: c.expect(match) def run(args): print "Using CMD %s" % (args) c = pexpect.spawn(args) c.logfile = sys.stdout return c def expect_close(proc): try: proc.close(force=True) except Exception as e: print("Could not close process: %s" % (str(e))) def create_call(flash0, flash1): subprocess.call([ 'dd', 'if=/dev/zero', 'of=%s/flash.CS0.shell' % (OUTPUT), 'bs=1k', 'count=%d' % (32 * 1024) ]) subprocess.call([ 'dd', 'if=/dev/zero', 'of=%s/flash.CS1.shell' % (OUTPUT), 'bs=1k', 'count=%d' % (32 * 1024) ]) subprocess.call([ 'dd', 'if=%s/flashes/%s.CS0.%s' % (OUTPUT, FLASH_NAME, flash0), 'of=%s/flash.CS0.shell' % (OUTPUT), 'bs=1k', 'conv=notrunc' ]) subprocess.call([ 'dd', 'if=%s/flashes/%s.CS1.%s' % (OUTPUT, FLASH_NAME, flash1), 'of=%s/flash.CS1.shell' % (OUTPUT), 'bs=1k', 'conv=notrunc' ]) cmd = "%s %s %s%s %s%s" % ( QEMU, ' '.join(QEMU_OPTS), QEMU_DRIVE, "%s/flash.CS0.shell" % (OUTPUT), QEMU_DRIVE, "%s/flash.CS1.shell" % (OUTPUT), ) return run(cmd) def expect_code(flash0, flash1, error_type, error_code): c = create_call(flash0, flash1) c.expect('Delete') c.send('\x1b\x5b\x33\x7e') c.expect('=> ') c.sendline('vbs') c.expect('\n=>', timeout=5) expected(c.before, [ 'Status: type (%d) code (%d)' % (error_type, error_code) ]) return c def expect_kernel( flash0, flash1, error_type, error_code, location='280e0000', verify=True, failure=None ): c = create_call(flash0, flash1) if verify: c.expect('Verifying Hash Integrity') expect_or_expected(c, 'Loading kernel from FIT Image at %s' % (location)) if error_type == 0: expect_or_expected(c, 'Starting kernel') elif failure is not None: c.expect(failure, timeout=5) return c def test_success_state(): c = expect_code('0.0', '0.0', 0, 0) expected(c.before, [ 'recovery_boot: 0' ]) expect_close(c) def test_bad_magic(): c = expect_code('3.30', '3.30', 3, 30) expect_close(c) def test_no_images(): c = expect_code('3.31', '3.31', 3, 31) expect_close(c) def test_no_firmware(): c = expect_code('3.32', '3.32', 3, 32) expect_close(c) def test_no_config(): c = expect_code('3.33', '3.33', 3, 33) expect_close(c) def test_no_keys(): c = expect_code('3.35', '3.35', 3, 35) expect_close(c) def test_bad_keys(): # Returns general unverified firmware code. c = expect_code('3.36', '3.36', 4, 43) expect_close(c) def test_bad_firmware_missing_position(): c = expect_code('3.37.1', '3.37.1', 3, 37) expect_close(c) def test_bad_firmware_missing_size(): c = expect_code('3.37.2', '3.37.2', 3, 37) expect_close(c) def test_bad_firmware_huge_size(): c = expect_code('3.37.3', '3.37.3', 3, 37) expect_close(c) def test_invalid_size(): c = expect_code('3.38', '3.38', 3, 38) expect_close(c) def test_keys_invalid_different_kek(): c = expect_code('4.40.1', '4.40.1', 4, 40) expect_close(c) def test_keys_invalid_bad_hint(): c = expect_code('4.40.2', '4.40.2', 4, 40) expect_close(c) def test_keys_invalid_bad_data(): # Returns general unverified firmware code. c = expect_code('4.40.3', '4.40.3', 4, 43) expect_close(c) def test_firmware_unverified(): c = expect_code('4.43.1', '4.43.1', 4, 43) expect_close(c) def test_firmware_invalid_bad_hint1(): c = expect_code('4.43.2', '4.43.2', 4, 43) expect_close(c) def test_firmware_invalid_bad_hint2(): c = expect_code('4.43.3', '4.43.3', 4, 43) expect_close(c) def test_firmware_invalid_duplicate_hash(): c = expect_code('4.43.4', '4.43.4', 4, 43) expect_close(c) def test_firmware_invalid_bad_timestamp(): c = expect_code('4.43.5', '4.43.5', 4, 43) expect_close(c) def test_firmware_invalid_bad_hash(): c = expect_code('4.43.6', '4.43.6', 4, 43) expect_close(c) def test_kernel(): c = expect_kernel('0.0', '0.0', 0, 0) expect_close(c) def test_kernel_fail(): c = expect_kernel('0.0', '6.60.3', 6, 60, failure='Bad Data Hash') expect_close(c) def test_kernel_corrupt(): c = expect_kernel('0.0', '6.60.4', 6, 60, failure='Bad Data Hash') expect_close(c) def test_kernel_early_fail(): # This will not attempt to verify the kernel because vboot already failed. # The recovery kernel address (on SPI0 0x2000:0000) is expected. c = expect_kernel('0.0', '4.43.6', 0, 0, location='200e0000', verify=False) expect_close(c) def test_fallback_last(): c = expect_code('0.0', 'next.1', 0, 0) expect_close(c) c = expect_code('0.0', 'next.2', 0, 0) expect_close(c) c = expect_code('0.0', 'next.1', 0, 0) expect_close(c) def test_fallback_fail(): c = expect_code('0.0', '0.0', 9, 91) expect_close(c) def test_fallback_forward_unsigned(): # Try an unsigned image. c = expect_code('0.0', '4.43.5.1', 4, 43) expect_close(c) # It should not effect backups c = expect_code('0.0', 'next.1', 0, 0) expect_close(c) c = expect_code('0.0', 'next.2', 0, 0) expect_close(c) def test_revoke_subordinate(): c = expect_code('0.0', 'future', 0, 0) expect_close(c) # The subordinate changed c = expect_code('0.0', 'next.2', 9, 91) expect_close(c) c = expect_code('0.0', 'future', 0, 0) expect_close(c) if __name__ == '__main__': parser = argparse.ArgumentParser( description='Run verified-boot unit tests') parser.add_argument( 'qemu', metavar='QEMU', help='Location of QEMU binary') parser.add_argument( 'output', metavar='TEST-DIR', help='The output path used in create-tests') parser.add_argument( 'flash_name', metavar='FLASH-NAME', help='Name of the flash (flash-fbtp, flash0) used in create-tests') parser.add_argument( '--tpm', default=False, action='store_true', help='Also perform TPM tests', ) args = parser.parse_args() QEMU = args.qemu OUTPUT = args.output FLASH_NAME = args.flash_name if args.tpm: QEMU_OPTS += [ "-tpmdev passthrough,id=tpm0,cancel-path=/dev/null,path=%s" % ( TPM_SOCKET), '-device tpm-tis,id=tpm0,tpmdev=tpm0,address=0x20', ] test_success_state() test_bad_magic() test_no_images() test_no_firmware() test_no_config() test_no_keys() test_bad_keys() test_bad_firmware_missing_position() test_bad_firmware_missing_size() test_bad_firmware_huge_size() test_invalid_size() # 4.40 error code set. test_keys_invalid_different_kek() test_keys_invalid_bad_hint() test_keys_invalid_bad_data() # 4.43 error code set. test_firmware_unverified() test_firmware_invalid_bad_hint1() test_firmware_invalid_bad_hint2() test_firmware_invalid_duplicate_hash() test_firmware_invalid_bad_timestamp() test_firmware_invalid_bad_hash() # The OS/kernel tests. test_kernel() test_kernel_fail() test_kernel_corrupt() # This will force-recovery test_kernel_early_fail() # Fallback tests if args.tpm: test_fallback_last() test_fallback_fail() test_fallback_forward_unsigned() test_revoke_subordinate()