Home | History | Annotate | Download | only in security_CpuVulnerabilities
      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 
      5 import logging
      6 import os
      7 from autotest_lib.client.bin import test, utils
      8 from autotest_lib.client.common_lib import error
      9 
     10 
     11 class 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