| # Copyright 2018 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import os |
| from autotest_lib.client.bin import test, utils |
| from autotest_lib.client.common_lib import error |
| |
| |
| class security_CpuVulnerabilities(test.test): |
| """ |
| This test ensures that the kernel contains appropriate mitigations against |
| CPU vulnerabilities by checking what the kernel reports in |
| '/sys/devices/system/cpu/vulnerabilities'. |
| """ |
| version = 1 |
| |
| SYSTEM_CPU_VULNERABILITIES = '/sys/devices/system/cpu/vulnerabilities' |
| |
| TESTS = { |
| 'amd': { |
| 'meltdown': ('0', set()), |
| 'spectre_v1': ('0', set(['__user pointer sanitization'])), |
| 'spectre_v2': ('0', set(['Full AMD retpoline'])), |
| }, |
| 'arm': {}, |
| 'i386': {}, |
| 'x86_64': { |
| 'meltdown': ('0', set(['PTI'])), |
| 'spectre_v1': ('4.4', set(['__user pointer sanitization'])), |
| 'spectre_v2': ('0', set(['Full generic retpoline'])), |
| }, |
| } |
| |
| |
| def run_once(self): |
| """Runs the test.""" |
| arch = utils.get_cpu_arch() |
| if arch == 'x86_64': |
| arch = utils.get_cpu_soc_family() |
| curr_kernel = utils.get_kernel_version() |
| |
| logging.debug('CPU arch is "%s"', arch) |
| logging.debug('Kernel version is "%s"', curr_kernel) |
| |
| if arch not in self.TESTS: |
| raise error.TestNAError('"%s" arch not in test baseline' % arch) |
| |
| # Kernels <= 3.14 don't have this directory and are expected to abort |
| # with TestNA. |
| if not os.path.exists(self.SYSTEM_CPU_VULNERABILITIES): |
| raise error.TestNAError('"%s" directory not present, not testing' % |
| self.SYSTEM_CPU_VULNERABILITIES) |
| |
| failures = [] |
| for filename, expected in self.TESTS[arch].items(): |
| file = os.path.join(self.SYSTEM_CPU_VULNERABILITIES, filename) |
| if not os.path.exists(file): |
| raise error.TestError('"%s" file does not exist, cannot test' % |
| file) |
| |
| min_kernel = expected[0] |
| if utils.compare_versions(curr_kernel, min_kernel) == -1: |
| # The kernel on the DUT is older than the version where |
| # the mitigation was introduced. |
| info_message = 'DUT kernel version "%s"' % curr_kernel |
| info_message += ' is older than "%s"' % min_kernel |
| info_message += ', skipping "%s" test' % filename |
| logging.info(info_message) |
| continue |
| |
| # E.g.: |
| # Not affected |
| # $ cat /sys/devices/system/cpu/vulnerabilities/meltdown |
| # Not affected |
| # |
| # One mitigation |
| # $ cat /sys/devices/system/cpu/vulnerabilities/meltdown |
| # Mitigation: PTI |
| # |
| # Several mitigations |
| # $ cat /sys/devices/system/cpu/vulnerabilities/spectre_v2 |
| # Mitigation: Full generic retpoline, IBPB, IBRS_FW |
| with open(file) as f: |
| lines = f.readlines() |
| if len(lines) > 1: |
| logging.warning('"%s" has more than one line', file) |
| |
| actual = lines[0].strip() |
| logging.debug('"%s" -> "%s"', file, actual) |
| |
| expected_mitigations = expected[1] |
| if not expected_mitigations: |
| if actual != 'Not affected': |
| failures.append((file, actual, expected_mitigations)) |
| else: |
| # CPU is affected. |
| if 'Mitigation' not in actual: |
| failures.append((file, actual, expected_mitigations)) |
| else: |
| mit_list = actual.split(':', 1)[1].split(',') |
| actual_mitigations = set(t.strip() for t in mit_list) |
| # Test set inclusion. |
| if actual_mitigations < expected_mitigations: |
| failures.append((file, actual_mitigations, |
| expected_mitigations)) |
| |
| if failures: |
| for failure in failures: |
| logging.error('"%s" was "%s", expected "%s"', *failure) |
| raise error.TestFail('CPU vulnerabilities not mitigated properly') |