blob: c6289811def8aa1271ca319f0669f38a5ca661aa [file] [log] [blame]
# 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')