1# Copyright 2018 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 5import logging 6import os 7from autotest_lib.client.bin import test, utils 8from autotest_lib.client.common_lib import error 9 10 11class security_CpuVulnerabilities(test.test): 12 """ 13 This test ensures that the kernel contains appropriate mitigations against 14 CPU vulnerabilities by checking what the kernel reports in 15 '/sys/devices/system/cpu/vulnerabilities'. 16 """ 17 version = 1 18 19 SYSTEM_CPU_VULNERABILITIES = '/sys/devices/system/cpu/vulnerabilities' 20 21 TESTS = { 22 'amd': { 23 'meltdown': ('0', set()), 24 'spectre_v1': ('0', set(['__user pointer sanitization'])), 25 'spectre_v2': ('0', set(['Full AMD retpoline'])), 26 }, 27 'arm': {}, 28 'i386': {}, 29 'x86_64': { 30 'meltdown': ('0', set(['PTI'])), 31 'spectre_v1': ('4.4', set(['__user pointer sanitization'])), 32 'spectre_v2': ('0', set(['Full generic retpoline'])), 33 }, 34 } 35 36 37 def run_once(self): 38 """Runs the test.""" 39 arch = utils.get_cpu_arch() 40 if arch == 'x86_64': 41 arch = utils.get_cpu_soc_family() 42 curr_kernel = utils.get_kernel_version() 43 44 logging.debug('CPU arch is "%s"', arch) 45 logging.debug('Kernel version is "%s"', curr_kernel) 46 47 if arch not in self.TESTS: 48 raise error.TestNAError('"%s" arch not in test baseline' % arch) 49 50 # Kernels <= 3.14 don't have this directory and are expected to abort 51 # with TestNA. 52 if not os.path.exists(self.SYSTEM_CPU_VULNERABILITIES): 53 raise error.TestNAError('"%s" directory not present, not testing' % 54 self.SYSTEM_CPU_VULNERABILITIES) 55 56 failures = [] 57 for filename, expected in self.TESTS[arch].items(): 58 file = os.path.join(self.SYSTEM_CPU_VULNERABILITIES, filename) 59 if not os.path.exists(file): 60 raise error.TestError('"%s" file does not exist, cannot test' % 61 file) 62 63 min_kernel = expected[0] 64 if utils.compare_versions(curr_kernel, min_kernel) == -1: 65 # The kernel on the DUT is older than the version where 66 # the mitigation was introduced. 67 info_message = 'DUT kernel version "%s"' % curr_kernel 68 info_message += ' is older than "%s"' % min_kernel 69 info_message += ', skipping "%s" test' % filename 70 logging.info(info_message) 71 continue 72 73 # E.g.: 74 # Not affected 75 # $ cat /sys/devices/system/cpu/vulnerabilities/meltdown 76 # Not affected 77 # 78 # One mitigation 79 # $ cat /sys/devices/system/cpu/vulnerabilities/meltdown 80 # Mitigation: PTI 81 # 82 # Several mitigations 83 # $ cat /sys/devices/system/cpu/vulnerabilities/spectre_v2 84 # Mitigation: Full generic retpoline, IBPB, IBRS_FW 85 with open(file) as f: 86 lines = f.readlines() 87 if len(lines) > 1: 88 logging.warning('"%s" has more than one line', file) 89 90 actual = lines[0].strip() 91 logging.debug('"%s" -> "%s"', file, actual) 92 93 expected_mitigations = expected[1] 94 if not expected_mitigations: 95 if actual != 'Not affected': 96 failures.append((file, actual, expected_mitigations)) 97 else: 98 # CPU is affected. 99 if 'Mitigation' not in actual: 100 failures.append((file, actual, expected_mitigations)) 101 else: 102 mit_list = actual.split(':', 1)[1].split(',') 103 actual_mitigations = set(t.strip() for t in mit_list) 104 # Test set inclusion. 105 if actual_mitigations < expected_mitigations: 106 failures.append((file, actual_mitigations, 107 expected_mitigations)) 108 109 if failures: 110 for failure in failures: 111 logging.error('"%s" was "%s", expected "%s"', *failure) 112 raise error.TestFail('CPU vulnerabilities not mitigated properly') 113