xref: /linux/tools/testing/selftests/x86/bugs/common.py (revision 6f5bf947bab06f37ff931c359fd5770c4d9cbf87)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3#
4# Copyright (c) 2025 Intel Corporation
5#
6# This contains kselftest framework adapted common functions for testing
7# mitigation for x86 bugs.
8
9import os, sys, re, shutil
10
11sys.path.insert(0, '../../kselftest')
12import ksft
13
14def read_file(path):
15    if not os.path.exists(path):
16        return None
17    with open(path, 'r') as file:
18        return file.read().strip()
19
20def cpuinfo_has(arg):
21    cpuinfo = read_file('/proc/cpuinfo')
22    if arg in cpuinfo:
23        return True
24    return False
25
26def cmdline_has(arg):
27    cmdline = read_file('/proc/cmdline')
28    if arg in cmdline:
29        return True
30    return False
31
32def cmdline_has_either(args):
33    cmdline = read_file('/proc/cmdline')
34    for arg in args:
35        if arg in cmdline:
36            return True
37    return False
38
39def cmdline_has_none(args):
40    return not cmdline_has_either(args)
41
42def cmdline_has_all(args):
43    cmdline = read_file('/proc/cmdline')
44    for arg in args:
45        if arg not in cmdline:
46            return False
47    return True
48
49def get_sysfs(bug):
50    return read_file("/sys/devices/system/cpu/vulnerabilities/" + bug)
51
52def sysfs_has(bug, mitigation):
53    status = get_sysfs(bug)
54    if mitigation in status:
55        return True
56    return False
57
58def sysfs_has_either(bugs, mitigations):
59    for bug in bugs:
60        for mitigation in mitigations:
61            if sysfs_has(bug, mitigation):
62                return True
63    return False
64
65def sysfs_has_none(bugs, mitigations):
66    return not sysfs_has_either(bugs, mitigations)
67
68def sysfs_has_all(bugs, mitigations):
69    for bug in bugs:
70        for mitigation in mitigations:
71            if not sysfs_has(bug, mitigation):
72                return False
73    return True
74
75def bug_check_pass(bug, found):
76    ksft.print_msg(f"\nFound: {found}")
77    # ksft.print_msg(f"\ncmdline: {read_file('/proc/cmdline')}")
78    ksft.test_result_pass(f'{bug}: {found}')
79
80def bug_check_fail(bug, found, expected):
81    ksft.print_msg(f'\nFound:\t {found}')
82    ksft.print_msg(f'Expected:\t {expected}')
83    ksft.print_msg(f"\ncmdline: {read_file('/proc/cmdline')}")
84    ksft.test_result_fail(f'{bug}: {found}')
85
86def bug_status_unknown(bug, found):
87    ksft.print_msg(f'\nUnknown status: {found}')
88    ksft.print_msg(f"\ncmdline: {read_file('/proc/cmdline')}")
89    ksft.test_result_fail(f'{bug}: {found}')
90
91def basic_checks_sufficient(bug, mitigation):
92    if not mitigation:
93        bug_status_unknown(bug, "None")
94        return True
95    elif mitigation == "Not affected":
96        ksft.test_result_pass(bug)
97        return True
98    elif mitigation == "Vulnerable":
99        if cmdline_has_either([f'{bug}=off', 'mitigations=off']):
100            bug_check_pass(bug, mitigation)
101            return True
102    return False
103
104def get_section_info(vmlinux, section_name):
105    from elftools.elf.elffile import ELFFile
106    with open(vmlinux, 'rb') as f:
107        elffile = ELFFile(f)
108        section = elffile.get_section_by_name(section_name)
109        if section is None:
110            ksft.print_msg("Available sections in vmlinux:")
111            for sec in elffile.iter_sections():
112                ksft.print_msg(sec.name)
113            raise ValueError(f"Section {section_name} not found in {vmlinux}")
114        return section['sh_addr'], section['sh_offset'], section['sh_size']
115
116def get_patch_sites(vmlinux, offset, size):
117    import struct
118    output = []
119    with open(vmlinux, 'rb') as f:
120        f.seek(offset)
121        i = 0
122        while i < size:
123            data = f.read(4)  # s32
124            if not data:
125                break
126            sym_offset = struct.unpack('<i', data)[0] + i
127            i += 4
128            output.append(sym_offset)
129    return output
130
131def get_instruction_from_vmlinux(elffile, section, virtual_address, target_address):
132    from capstone import Cs, CS_ARCH_X86, CS_MODE_64
133    section_start = section['sh_addr']
134    section_end = section_start + section['sh_size']
135
136    if not (section_start <= target_address < section_end):
137        return None
138
139    offset = target_address - section_start
140    code = section.data()[offset:offset + 16]
141
142    cap = init_capstone()
143    for instruction in cap.disasm(code, target_address):
144        if instruction.address == target_address:
145            return instruction
146    return None
147
148def init_capstone():
149    from capstone import Cs, CS_ARCH_X86, CS_MODE_64, CS_OPT_SYNTAX_ATT
150    cap = Cs(CS_ARCH_X86, CS_MODE_64)
151    cap.syntax = CS_OPT_SYNTAX_ATT
152    return cap
153
154def get_runtime_kernel():
155    import drgn
156    return drgn.program_from_kernel()
157
158def check_dependencies_or_skip(modules, script_name="unknown test"):
159    for mod in modules:
160        try:
161            __import__(mod)
162        except ImportError:
163            ksft.test_result_skip(f"Skipping {script_name}: missing module '{mod}'")
164            ksft.finished()
165