Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftest/x86/bugs: Add selftests for ITS

Below are the tests added for Indirect Target Selection (ITS):

- its_sysfs.py - Check if sysfs reflects the correct mitigation status for
the mitigation selected via the kernel cmdline.

- its_permutations.py - tests mitigation selection with cmdline
permutations with other bugs like spectre_v2 and retbleed.

- its_indirect_alignment.py - verifies that for addresses in
.retpoline_sites section that belong to lower half of cacheline are
patched to ITS-safe thunk. Typical output looks like below:

Site 49: function symbol: __x64_sys_restart_syscall+0x1f <0xffffffffbb1509af>
# vmlinux: 0xffffffff813509af: jmp 0xffffffff81f5a8e0
# kcore: 0xffffffffbb1509af: jmpq *%rax
# ITS thunk NOT expected for site 49
# PASSED: Found *%rax
#
Site 50: function symbol: __resched_curr+0xb0 <0xffffffffbb181910>
# vmlinux: 0xffffffff81381910: jmp 0xffffffff81f5a8e0
# kcore: 0xffffffffbb181910: jmp 0xffffffffc02000fc
# ITS thunk expected for site 50
# PASSED: Found 0xffffffffc02000fc -> jmpq *%rax <scattered-thunk?>

- its_ret_alignment.py - verifies that for addresses in .return_sites
section that belong to lower half of cacheline are patched to
its_return_thunk. Typical output looks like below:

Site 97: function symbol: collect_event+0x48 <0xffffffffbb007f18>
# vmlinux: 0xffffffff81207f18: jmp 0xffffffff81f5b500
# kcore: 0xffffffffbb007f18: jmp 0xffffffffbbd5b560
# PASSED: Found jmp 0xffffffffbbd5b560 <its_return_thunk>
#
Site 98: function symbol: collect_event+0xa4 <0xffffffffbb007f74>
# vmlinux: 0xffffffff81207f74: jmp 0xffffffff81f5b500
# kcore: 0xffffffffbb007f74: retq
# PASSED: Found retq

Some of these tests have dependency on tools like virtme-ng[1] and drgn[2].
When the dependencies are not met, the test will be skipped.

[1] https://github.com/arighi/virtme-ng
[2] https://github.com/osandov/drgn

Co-developed-by: Tao Zhang <tao1.zhang@linux.intel.com>
Signed-off-by: Tao Zhang <tao1.zhang@linux.intel.com>
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>

authored by

Pawan Gupta and committed by
Dave Hansen
7a9b709e e52c1dc7

+631
+1
tools/testing/selftests/Makefile
··· 121 121 TARGETS += vDSO 122 122 TARGETS += mm 123 123 TARGETS += x86 124 + TARGETS += x86/bugs 124 125 TARGETS += zram 125 126 #Please keep the TARGETS list alphabetically sorted 126 127 # Run "make quicktest=1 run_tests" or
+3
tools/testing/selftests/x86/bugs/Makefile
··· 1 + TEST_PROGS := its_sysfs.py its_permutations.py its_indirect_alignment.py its_ret_alignment.py 2 + TEST_FILES := common.py 3 + include ../../lib.mk
+164
tools/testing/selftests/x86/bugs/common.py
··· 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 + 9 + import os, sys, re, shutil 10 + 11 + sys.path.insert(0, '../../kselftest') 12 + import ksft 13 + 14 + def 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 + 20 + def cpuinfo_has(arg): 21 + cpuinfo = read_file('/proc/cpuinfo') 22 + if arg in cpuinfo: 23 + return True 24 + return False 25 + 26 + def cmdline_has(arg): 27 + cmdline = read_file('/proc/cmdline') 28 + if arg in cmdline: 29 + return True 30 + return False 31 + 32 + def 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 + 39 + def cmdline_has_none(args): 40 + return not cmdline_has_either(args) 41 + 42 + def 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 + 49 + def get_sysfs(bug): 50 + return read_file("/sys/devices/system/cpu/vulnerabilities/" + bug) 51 + 52 + def sysfs_has(bug, mitigation): 53 + status = get_sysfs(bug) 54 + if mitigation in status: 55 + return True 56 + return False 57 + 58 + def 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 + 65 + def sysfs_has_none(bugs, mitigations): 66 + return not sysfs_has_either(bugs, mitigations) 67 + 68 + def 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 + 75 + def 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 + 80 + def 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 + 86 + def 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 + 91 + def 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 + 104 + def 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 + 116 + def 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 + 131 + def 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 + 148 + def 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 + 154 + def get_runtime_kernel(): 155 + import drgn 156 + return drgn.program_from_kernel() 157 + 158 + def 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()
+150
tools/testing/selftests/x86/bugs/its_indirect_alignment.py
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0 3 + # 4 + # Copyright (c) 2025 Intel Corporation 5 + # 6 + # Test for indirect target selection (ITS) mitigation. 7 + # 8 + # Test if indirect CALL/JMP are correctly patched by evaluating 9 + # the vmlinux .retpoline_sites in /proc/kcore. 10 + 11 + # Install dependencies 12 + # add-apt-repository ppa:michel-slm/kernel-utils 13 + # apt update 14 + # apt install -y python3-drgn python3-pyelftools python3-capstone 15 + # 16 + # Best to copy the vmlinux at a standard location: 17 + # mkdir -p /usr/lib/debug/lib/modules/$(uname -r) 18 + # cp $VMLINUX /usr/lib/debug/lib/modules/$(uname -r)/vmlinux 19 + # 20 + # Usage: ./its_indirect_alignment.py [vmlinux] 21 + 22 + import os, sys, argparse 23 + from pathlib import Path 24 + 25 + this_dir = os.path.dirname(os.path.realpath(__file__)) 26 + sys.path.insert(0, this_dir + '/../../kselftest') 27 + import ksft 28 + import common as c 29 + 30 + bug = "indirect_target_selection" 31 + 32 + mitigation = c.get_sysfs(bug) 33 + if not mitigation or "Aligned branch/return thunks" not in mitigation: 34 + ksft.test_result_skip("Skipping its_indirect_alignment.py: Aligned branch/return thunks not enabled") 35 + ksft.finished() 36 + 37 + if c.sysfs_has("spectre_v2", "Retpolines"): 38 + ksft.test_result_skip("Skipping its_indirect_alignment.py: Retpolines deployed") 39 + ksft.finished() 40 + 41 + c.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_indirect_alignment.py") 42 + 43 + from elftools.elf.elffile import ELFFile 44 + from drgn.helpers.common.memory import identify_address 45 + 46 + cap = c.init_capstone() 47 + 48 + if len(os.sys.argv) > 1: 49 + arg_vmlinux = os.sys.argv[1] 50 + if not os.path.exists(arg_vmlinux): 51 + ksft.test_result_fail(f"its_indirect_alignment.py: vmlinux not found at argument path: {arg_vmlinux}") 52 + ksft.exit_fail() 53 + os.makedirs(f"/usr/lib/debug/lib/modules/{os.uname().release}", exist_ok=True) 54 + os.system(f'cp {arg_vmlinux} /usr/lib/debug/lib/modules/$(uname -r)/vmlinux') 55 + 56 + vmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux" 57 + if not os.path.exists(vmlinux): 58 + ksft.test_result_fail(f"its_indirect_alignment.py: vmlinux not found at {vmlinux}") 59 + ksft.exit_fail() 60 + 61 + ksft.print_msg(f"Using vmlinux: {vmlinux}") 62 + 63 + retpolines_start_vmlinux, retpolines_sec_offset, size = c.get_section_info(vmlinux, '.retpoline_sites') 64 + ksft.print_msg(f"vmlinux: Section .retpoline_sites (0x{retpolines_start_vmlinux:x}) found at 0x{retpolines_sec_offset:x} with size 0x{size:x}") 65 + 66 + sites_offset = c.get_patch_sites(vmlinux, retpolines_sec_offset, size) 67 + total_retpoline_tests = len(sites_offset) 68 + ksft.print_msg(f"Found {total_retpoline_tests} retpoline sites") 69 + 70 + prog = c.get_runtime_kernel() 71 + retpolines_start_kcore = prog.symbol('__retpoline_sites').address 72 + ksft.print_msg(f'kcore: __retpoline_sites: 0x{retpolines_start_kcore:x}') 73 + 74 + x86_indirect_its_thunk_r15 = prog.symbol('__x86_indirect_its_thunk_r15').address 75 + ksft.print_msg(f'kcore: __x86_indirect_its_thunk_r15: 0x{x86_indirect_its_thunk_r15:x}') 76 + 77 + tests_passed = 0 78 + tests_failed = 0 79 + tests_unknown = 0 80 + 81 + with open(vmlinux, 'rb') as f: 82 + elffile = ELFFile(f) 83 + text_section = elffile.get_section_by_name('.text') 84 + 85 + for i in range(0, len(sites_offset)): 86 + site = retpolines_start_kcore + sites_offset[i] 87 + vmlinux_site = retpolines_start_vmlinux + sites_offset[i] 88 + passed = unknown = failed = False 89 + try: 90 + vmlinux_insn = c.get_instruction_from_vmlinux(elffile, text_section, text_section['sh_addr'], vmlinux_site) 91 + kcore_insn = list(cap.disasm(prog.read(site, 16), site))[0] 92 + operand = kcore_insn.op_str 93 + insn_end = site + kcore_insn.size - 1 # TODO handle Jcc.32 __x86_indirect_thunk_\reg 94 + safe_site = insn_end & 0x20 95 + site_status = "" if safe_site else "(unsafe)" 96 + 97 + ksft.print_msg(f"\nSite {i}: {identify_address(prog, site)} <0x{site:x}> {site_status}") 98 + ksft.print_msg(f"\tvmlinux: 0x{vmlinux_insn.address:x}:\t{vmlinux_insn.mnemonic}\t{vmlinux_insn.op_str}") 99 + ksft.print_msg(f"\tkcore: 0x{kcore_insn.address:x}:\t{kcore_insn.mnemonic}\t{kcore_insn.op_str}") 100 + 101 + if (site & 0x20) ^ (insn_end & 0x20): 102 + ksft.print_msg(f"\tSite at safe/unsafe boundary: {str(kcore_insn.bytes)} {kcore_insn.mnemonic} {operand}") 103 + if safe_site: 104 + tests_passed += 1 105 + passed = True 106 + ksft.print_msg(f"\tPASSED: At safe address") 107 + continue 108 + 109 + if operand.startswith('0xffffffff'): 110 + thunk = int(operand, 16) 111 + if thunk > x86_indirect_its_thunk_r15: 112 + insn_at_thunk = list(cap.disasm(prog.read(thunk, 16), thunk))[0] 113 + operand += ' -> ' + insn_at_thunk.mnemonic + ' ' + insn_at_thunk.op_str + ' <dynamic-thunk?>' 114 + if 'jmp' in insn_at_thunk.mnemonic and thunk & 0x20: 115 + ksft.print_msg(f"\tPASSED: Found {operand} at safe address") 116 + passed = True 117 + if not passed: 118 + if kcore_insn.operands[0].type == capstone.CS_OP_IMM: 119 + operand += ' <' + prog.symbol(int(operand, 16)) + '>' 120 + if '__x86_indirect_its_thunk_' in operand: 121 + ksft.print_msg(f"\tPASSED: Found {operand}") 122 + else: 123 + ksft.print_msg(f"\tPASSED: Found direct branch: {kcore_insn}, ITS thunk not required.") 124 + passed = True 125 + else: 126 + unknown = True 127 + if passed: 128 + tests_passed += 1 129 + elif unknown: 130 + ksft.print_msg(f"UNKNOWN: unexpected operand: {kcore_insn}") 131 + tests_unknown += 1 132 + else: 133 + ksft.print_msg(f'\t************* FAILED *************') 134 + ksft.print_msg(f"\tFound {kcore_insn.bytes} {kcore_insn.mnemonic} {operand}") 135 + ksft.print_msg(f'\t**********************************') 136 + tests_failed += 1 137 + except Exception as e: 138 + ksft.print_msg(f"UNKNOWN: An unexpected error occurred: {e}") 139 + tests_unknown += 1 140 + 141 + ksft.print_msg(f"\n\nSummary:") 142 + ksft.print_msg(f"PASS: \t{tests_passed} \t/ {total_retpoline_tests}") 143 + ksft.print_msg(f"FAIL: \t{tests_failed} \t/ {total_retpoline_tests}") 144 + ksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_retpoline_tests}") 145 + 146 + if tests_failed == 0: 147 + ksft.test_result_pass("All ITS return thunk sites passed") 148 + else: 149 + ksft.test_result_fail(f"{tests_failed} ITS return thunk sites failed") 150 + ksft.finished()
+109
tools/testing/selftests/x86/bugs/its_permutations.py
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0 3 + # 4 + # Copyright (c) 2025 Intel Corporation 5 + # 6 + # Test for indirect target selection (ITS) cmdline permutations with other bugs 7 + # like spectre_v2 and retbleed. 8 + 9 + import os, sys, subprocess, itertools, re, shutil 10 + 11 + test_dir = os.path.dirname(os.path.realpath(__file__)) 12 + sys.path.insert(0, test_dir + '/../../kselftest') 13 + import ksft 14 + import common as c 15 + 16 + bug = "indirect_target_selection" 17 + mitigation = c.get_sysfs(bug) 18 + 19 + if not mitigation or "Not affected" in mitigation: 20 + ksft.test_result_skip("Skipping its_permutations.py: not applicable") 21 + ksft.finished() 22 + 23 + if shutil.which('vng') is None: 24 + ksft.test_result_skip("Skipping its_permutations.py: virtme-ng ('vng') not found in PATH.") 25 + ksft.finished() 26 + 27 + TEST = f"{test_dir}/its_sysfs.py" 28 + default_kparam = ['clearcpuid=hypervisor', 'panic=5', 'panic_on_warn=1', 'oops=panic', 'nmi_watchdog=1', 'hung_task_panic=1'] 29 + 30 + DEBUG = " -v " 31 + 32 + # Install dependencies 33 + # https://github.com/arighi/virtme-ng 34 + # apt install virtme-ng 35 + BOOT_CMD = f"vng --run {test_dir}/../../../../../arch/x86/boot/bzImage " 36 + #BOOT_CMD += DEBUG 37 + 38 + bug = "indirect_target_selection" 39 + 40 + input_options = { 41 + 'indirect_target_selection' : ['off', 'on', 'stuff', 'vmexit'], 42 + 'retbleed' : ['off', 'stuff', 'auto'], 43 + 'spectre_v2' : ['off', 'on', 'eibrs', 'retpoline', 'ibrs', 'eibrs,retpoline'], 44 + } 45 + 46 + def pretty_print(output): 47 + OKBLUE = '\033[94m' 48 + OKGREEN = '\033[92m' 49 + WARNING = '\033[93m' 50 + FAIL = '\033[91m' 51 + ENDC = '\033[0m' 52 + BOLD = '\033[1m' 53 + 54 + # Define patterns and their corresponding colors 55 + patterns = { 56 + r"^ok \d+": OKGREEN, 57 + r"^not ok \d+": FAIL, 58 + r"^# Testing .*": OKBLUE, 59 + r"^# Found: .*": WARNING, 60 + r"^# Totals: .*": BOLD, 61 + r"pass:([1-9]\d*)": OKGREEN, 62 + r"fail:([1-9]\d*)": FAIL, 63 + r"skip:([1-9]\d*)": WARNING, 64 + } 65 + 66 + # Apply colors based on patterns 67 + for pattern, color in patterns.items(): 68 + output = re.sub(pattern, lambda match: f"{color}{match.group(0)}{ENDC}", output, flags=re.MULTILINE) 69 + 70 + print(output) 71 + 72 + combinations = list(itertools.product(*input_options.values())) 73 + ksft.print_header() 74 + ksft.set_plan(len(combinations)) 75 + 76 + logs = "" 77 + 78 + for combination in combinations: 79 + append = "" 80 + log = "" 81 + for p in default_kparam: 82 + append += f' --append={p}' 83 + command = BOOT_CMD + append 84 + test_params = "" 85 + for i, key in enumerate(input_options.keys()): 86 + param = f'{key}={combination[i]}' 87 + test_params += f' {param}' 88 + command += f" --append={param}" 89 + command += f" -- {TEST}" 90 + test_name = f"{bug} {test_params}" 91 + pretty_print(f'# Testing {test_name}') 92 + t = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 93 + t.wait() 94 + output, _ = t.communicate() 95 + if t.returncode == 0: 96 + ksft.test_result_pass(test_name) 97 + else: 98 + ksft.test_result_fail(test_name) 99 + output = output.decode() 100 + log += f" {output}" 101 + pretty_print(log) 102 + logs += output + "\n" 103 + 104 + # Optionally use tappy to parse the output 105 + # apt install python3-tappy 106 + with open("logs.txt", "w") as f: 107 + f.write(logs) 108 + 109 + ksft.finished()
+139
tools/testing/selftests/x86/bugs/its_ret_alignment.py
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0 3 + # 4 + # Copyright (c) 2025 Intel Corporation 5 + # 6 + # Test for indirect target selection (ITS) mitigation. 7 + # 8 + # Tests if the RETs are correctly patched by evaluating the 9 + # vmlinux .return_sites in /proc/kcore. 10 + # 11 + # Install dependencies 12 + # add-apt-repository ppa:michel-slm/kernel-utils 13 + # apt update 14 + # apt install -y python3-drgn python3-pyelftools python3-capstone 15 + # 16 + # Run on target machine 17 + # mkdir -p /usr/lib/debug/lib/modules/$(uname -r) 18 + # cp $VMLINUX /usr/lib/debug/lib/modules/$(uname -r)/vmlinux 19 + # 20 + # Usage: ./its_ret_alignment.py 21 + 22 + import os, sys, argparse 23 + from pathlib import Path 24 + 25 + this_dir = os.path.dirname(os.path.realpath(__file__)) 26 + sys.path.insert(0, this_dir + '/../../kselftest') 27 + import ksft 28 + import common as c 29 + 30 + bug = "indirect_target_selection" 31 + mitigation = c.get_sysfs(bug) 32 + if not mitigation or "Aligned branch/return thunks" not in mitigation: 33 + ksft.test_result_skip("Skipping its_ret_alignment.py: Aligned branch/return thunks not enabled") 34 + ksft.finished() 35 + 36 + c.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_ret_alignment.py") 37 + 38 + from elftools.elf.elffile import ELFFile 39 + from drgn.helpers.common.memory import identify_address 40 + 41 + cap = c.init_capstone() 42 + 43 + if len(os.sys.argv) > 1: 44 + arg_vmlinux = os.sys.argv[1] 45 + if not os.path.exists(arg_vmlinux): 46 + ksft.test_result_fail(f"its_ret_alignment.py: vmlinux not found at user-supplied path: {arg_vmlinux}") 47 + ksft.exit_fail() 48 + os.makedirs(f"/usr/lib/debug/lib/modules/{os.uname().release}", exist_ok=True) 49 + os.system(f'cp {arg_vmlinux} /usr/lib/debug/lib/modules/$(uname -r)/vmlinux') 50 + 51 + vmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux" 52 + if not os.path.exists(vmlinux): 53 + ksft.test_result_fail(f"its_ret_alignment.py: vmlinux not found at {vmlinux}") 54 + ksft.exit_fail() 55 + 56 + ksft.print_msg(f"Using vmlinux: {vmlinux}") 57 + 58 + rethunks_start_vmlinux, rethunks_sec_offset, size = c.get_section_info(vmlinux, '.return_sites') 59 + ksft.print_msg(f"vmlinux: Section .return_sites (0x{rethunks_start_vmlinux:x}) found at 0x{rethunks_sec_offset:x} with size 0x{size:x}") 60 + 61 + sites_offset = c.get_patch_sites(vmlinux, rethunks_sec_offset, size) 62 + total_rethunk_tests = len(sites_offset) 63 + ksft.print_msg(f"Found {total_rethunk_tests} rethunk sites") 64 + 65 + prog = c.get_runtime_kernel() 66 + rethunks_start_kcore = prog.symbol('__return_sites').address 67 + ksft.print_msg(f'kcore: __rethunk_sites: 0x{rethunks_start_kcore:x}') 68 + 69 + its_return_thunk = prog.symbol('its_return_thunk').address 70 + ksft.print_msg(f'kcore: its_return_thunk: 0x{its_return_thunk:x}') 71 + 72 + tests_passed = 0 73 + tests_failed = 0 74 + tests_unknown = 0 75 + tests_skipped = 0 76 + 77 + with open(vmlinux, 'rb') as f: 78 + elffile = ELFFile(f) 79 + text_section = elffile.get_section_by_name('.text') 80 + 81 + for i in range(len(sites_offset)): 82 + site = rethunks_start_kcore + sites_offset[i] 83 + vmlinux_site = rethunks_start_vmlinux + sites_offset[i] 84 + try: 85 + passed = unknown = failed = skipped = False 86 + 87 + symbol = identify_address(prog, site) 88 + vmlinux_insn = c.get_instruction_from_vmlinux(elffile, text_section, text_section['sh_addr'], vmlinux_site) 89 + kcore_insn = list(cap.disasm(prog.read(site, 16), site))[0] 90 + 91 + insn_end = site + kcore_insn.size - 1 92 + 93 + safe_site = insn_end & 0x20 94 + site_status = "" if safe_site else "(unsafe)" 95 + 96 + ksft.print_msg(f"\nSite {i}: {symbol} <0x{site:x}> {site_status}") 97 + ksft.print_msg(f"\tvmlinux: 0x{vmlinux_insn.address:x}:\t{vmlinux_insn.mnemonic}\t{vmlinux_insn.op_str}") 98 + ksft.print_msg(f"\tkcore: 0x{kcore_insn.address:x}:\t{kcore_insn.mnemonic}\t{kcore_insn.op_str}") 99 + 100 + if safe_site: 101 + tests_passed += 1 102 + passed = True 103 + ksft.print_msg(f"\tPASSED: At safe address") 104 + continue 105 + 106 + if "jmp" in kcore_insn.mnemonic: 107 + passed = True 108 + elif "ret" not in kcore_insn.mnemonic: 109 + skipped = True 110 + 111 + if passed: 112 + ksft.print_msg(f"\tPASSED: Found {kcore_insn.mnemonic} {kcore_insn.op_str}") 113 + tests_passed += 1 114 + elif skipped: 115 + ksft.print_msg(f"\tSKIPPED: Found '{kcore_insn.mnemonic}'") 116 + tests_skipped += 1 117 + elif unknown: 118 + ksft.print_msg(f"UNKNOWN: An unknown instruction: {kcore_insn}") 119 + tests_unknown += 1 120 + else: 121 + ksft.print_msg(f'\t************* FAILED *************') 122 + ksft.print_msg(f"\tFound {kcore_insn.mnemonic} {kcore_insn.op_str}") 123 + ksft.print_msg(f'\t**********************************') 124 + tests_failed += 1 125 + except Exception as e: 126 + ksft.print_msg(f"UNKNOWN: An unexpected error occurred: {e}") 127 + tests_unknown += 1 128 + 129 + ksft.print_msg(f"\n\nSummary:") 130 + ksft.print_msg(f"PASSED: \t{tests_passed} \t/ {total_rethunk_tests}") 131 + ksft.print_msg(f"FAILED: \t{tests_failed} \t/ {total_rethunk_tests}") 132 + ksft.print_msg(f"SKIPPED: \t{tests_skipped} \t/ {total_rethunk_tests}") 133 + ksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_rethunk_tests}") 134 + 135 + if tests_failed == 0: 136 + ksft.test_result_pass("All ITS return thunk sites passed.") 137 + else: 138 + ksft.test_result_fail(f"{tests_failed} failed sites need ITS return thunks.") 139 + ksft.finished()
+65
tools/testing/selftests/x86/bugs/its_sysfs.py
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0 3 + # 4 + # Copyright (c) 2025 Intel Corporation 5 + # 6 + # Test for Indirect Target Selection(ITS) mitigation sysfs status. 7 + 8 + import sys, os, re 9 + this_dir = os.path.dirname(os.path.realpath(__file__)) 10 + sys.path.insert(0, this_dir + '/../../kselftest') 11 + import ksft 12 + 13 + from common import * 14 + 15 + bug = "indirect_target_selection" 16 + mitigation = get_sysfs(bug) 17 + 18 + ITS_MITIGATION_ALIGNED_THUNKS = "Mitigation: Aligned branch/return thunks" 19 + ITS_MITIGATION_RETPOLINE_STUFF = "Mitigation: Retpolines, Stuffing RSB" 20 + ITS_MITIGATION_VMEXIT_ONLY = "Mitigation: Vulnerable, KVM: Not affected" 21 + ITS_MITIGATION_VULNERABLE = "Vulnerable" 22 + 23 + def check_mitigation(): 24 + if mitigation == ITS_MITIGATION_ALIGNED_THUNKS: 25 + if cmdline_has(f'{bug}=stuff') and sysfs_has("spectre_v2", "Retpolines"): 26 + bug_check_fail(bug, ITS_MITIGATION_ALIGNED_THUNKS, ITS_MITIGATION_RETPOLINE_STUFF) 27 + return 28 + if cmdline_has(f'{bug}=vmexit') and cpuinfo_has('its_native_only'): 29 + bug_check_fail(bug, ITS_MITIGATION_ALIGNED_THUNKS, ITS_MITIGATION_VMEXIT_ONLY) 30 + return 31 + bug_check_pass(bug, ITS_MITIGATION_ALIGNED_THUNKS) 32 + return 33 + 34 + if mitigation == ITS_MITIGATION_RETPOLINE_STUFF: 35 + if cmdline_has(f'{bug}=stuff') and sysfs_has("spectre_v2", "Retpolines"): 36 + bug_check_pass(bug, ITS_MITIGATION_RETPOLINE_STUFF) 37 + return 38 + if sysfs_has('retbleed', 'Stuffing'): 39 + bug_check_pass(bug, ITS_MITIGATION_RETPOLINE_STUFF) 40 + return 41 + bug_check_fail(bug, ITS_MITIGATION_RETPOLINE_STUFF, ITS_MITIGATION_ALIGNED_THUNKS) 42 + 43 + if mitigation == ITS_MITIGATION_VMEXIT_ONLY: 44 + if cmdline_has(f'{bug}=vmexit') and cpuinfo_has('its_native_only'): 45 + bug_check_pass(bug, ITS_MITIGATION_VMEXIT_ONLY) 46 + return 47 + bug_check_fail(bug, ITS_MITIGATION_VMEXIT_ONLY, ITS_MITIGATION_ALIGNED_THUNKS) 48 + 49 + if mitigation == ITS_MITIGATION_VULNERABLE: 50 + if sysfs_has("spectre_v2", "Vulnerable"): 51 + bug_check_pass(bug, ITS_MITIGATION_VULNERABLE) 52 + else: 53 + bug_check_fail(bug, "Mitigation", ITS_MITIGATION_VULNERABLE) 54 + 55 + bug_status_unknown(bug, mitigation) 56 + return 57 + 58 + ksft.print_header() 59 + ksft.set_plan(1) 60 + ksft.print_msg(f'{bug}: {mitigation} ...') 61 + 62 + if not basic_checks_sufficient(bug, mitigation): 63 + check_mitigation() 64 + 65 + ksft.finished()