• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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