xref: /linux/tools/testing/selftests/x86/bugs/its_ret_alignment.py (revision 6f5bf947bab06f37ff931c359fd5770c4d9cbf87)
1*7a9b709eSPawan Gupta#!/usr/bin/env python3
2*7a9b709eSPawan Gupta# SPDX-License-Identifier: GPL-2.0
3*7a9b709eSPawan Gupta#
4*7a9b709eSPawan Gupta# Copyright (c) 2025 Intel Corporation
5*7a9b709eSPawan Gupta#
6*7a9b709eSPawan Gupta# Test for indirect target selection (ITS) mitigation.
7*7a9b709eSPawan Gupta#
8*7a9b709eSPawan Gupta# Tests if the RETs are correctly patched by evaluating the
9*7a9b709eSPawan Gupta# vmlinux .return_sites in /proc/kcore.
10*7a9b709eSPawan Gupta#
11*7a9b709eSPawan Gupta# Install dependencies
12*7a9b709eSPawan Gupta# add-apt-repository ppa:michel-slm/kernel-utils
13*7a9b709eSPawan Gupta# apt update
14*7a9b709eSPawan Gupta# apt install -y python3-drgn python3-pyelftools python3-capstone
15*7a9b709eSPawan Gupta#
16*7a9b709eSPawan Gupta# Run on target machine
17*7a9b709eSPawan Gupta# mkdir -p /usr/lib/debug/lib/modules/$(uname -r)
18*7a9b709eSPawan Gupta# cp $VMLINUX /usr/lib/debug/lib/modules/$(uname -r)/vmlinux
19*7a9b709eSPawan Gupta#
20*7a9b709eSPawan Gupta# Usage: ./its_ret_alignment.py
21*7a9b709eSPawan Gupta
22*7a9b709eSPawan Guptaimport os, sys, argparse
23*7a9b709eSPawan Guptafrom pathlib import Path
24*7a9b709eSPawan Gupta
25*7a9b709eSPawan Guptathis_dir = os.path.dirname(os.path.realpath(__file__))
26*7a9b709eSPawan Guptasys.path.insert(0, this_dir + '/../../kselftest')
27*7a9b709eSPawan Guptaimport ksft
28*7a9b709eSPawan Guptaimport common as c
29*7a9b709eSPawan Gupta
30*7a9b709eSPawan Guptabug = "indirect_target_selection"
31*7a9b709eSPawan Guptamitigation = c.get_sysfs(bug)
32*7a9b709eSPawan Guptaif not mitigation or "Aligned branch/return thunks" not in mitigation:
33*7a9b709eSPawan Gupta    ksft.test_result_skip("Skipping its_ret_alignment.py: Aligned branch/return thunks not enabled")
34*7a9b709eSPawan Gupta    ksft.finished()
35*7a9b709eSPawan Gupta
36*7a9b709eSPawan Guptac.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_ret_alignment.py")
37*7a9b709eSPawan Gupta
38*7a9b709eSPawan Guptafrom elftools.elf.elffile import ELFFile
39*7a9b709eSPawan Guptafrom drgn.helpers.common.memory import identify_address
40*7a9b709eSPawan Gupta
41*7a9b709eSPawan Guptacap = c.init_capstone()
42*7a9b709eSPawan Gupta
43*7a9b709eSPawan Guptaif len(os.sys.argv) > 1:
44*7a9b709eSPawan Gupta    arg_vmlinux = os.sys.argv[1]
45*7a9b709eSPawan Gupta    if not os.path.exists(arg_vmlinux):
46*7a9b709eSPawan Gupta        ksft.test_result_fail(f"its_ret_alignment.py: vmlinux not found at user-supplied path: {arg_vmlinux}")
47*7a9b709eSPawan Gupta        ksft.exit_fail()
48*7a9b709eSPawan Gupta    os.makedirs(f"/usr/lib/debug/lib/modules/{os.uname().release}", exist_ok=True)
49*7a9b709eSPawan Gupta    os.system(f'cp {arg_vmlinux} /usr/lib/debug/lib/modules/$(uname -r)/vmlinux')
50*7a9b709eSPawan Gupta
51*7a9b709eSPawan Guptavmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux"
52*7a9b709eSPawan Guptaif not os.path.exists(vmlinux):
53*7a9b709eSPawan Gupta    ksft.test_result_fail(f"its_ret_alignment.py: vmlinux not found at {vmlinux}")
54*7a9b709eSPawan Gupta    ksft.exit_fail()
55*7a9b709eSPawan Gupta
56*7a9b709eSPawan Guptaksft.print_msg(f"Using vmlinux: {vmlinux}")
57*7a9b709eSPawan Gupta
58*7a9b709eSPawan Guptarethunks_start_vmlinux, rethunks_sec_offset, size = c.get_section_info(vmlinux, '.return_sites')
59*7a9b709eSPawan Guptaksft.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*7a9b709eSPawan Gupta
61*7a9b709eSPawan Guptasites_offset = c.get_patch_sites(vmlinux, rethunks_sec_offset, size)
62*7a9b709eSPawan Guptatotal_rethunk_tests = len(sites_offset)
63*7a9b709eSPawan Guptaksft.print_msg(f"Found {total_rethunk_tests} rethunk sites")
64*7a9b709eSPawan Gupta
65*7a9b709eSPawan Guptaprog = c.get_runtime_kernel()
66*7a9b709eSPawan Guptarethunks_start_kcore = prog.symbol('__return_sites').address
67*7a9b709eSPawan Guptaksft.print_msg(f'kcore: __rethunk_sites: 0x{rethunks_start_kcore:x}')
68*7a9b709eSPawan Gupta
69*7a9b709eSPawan Guptaits_return_thunk = prog.symbol('its_return_thunk').address
70*7a9b709eSPawan Guptaksft.print_msg(f'kcore: its_return_thunk: 0x{its_return_thunk:x}')
71*7a9b709eSPawan Gupta
72*7a9b709eSPawan Guptatests_passed = 0
73*7a9b709eSPawan Guptatests_failed = 0
74*7a9b709eSPawan Guptatests_unknown = 0
75*7a9b709eSPawan Guptatests_skipped = 0
76*7a9b709eSPawan Gupta
77*7a9b709eSPawan Guptawith open(vmlinux, 'rb') as f:
78*7a9b709eSPawan Gupta    elffile = ELFFile(f)
79*7a9b709eSPawan Gupta    text_section = elffile.get_section_by_name('.text')
80*7a9b709eSPawan Gupta
81*7a9b709eSPawan Gupta    for i in range(len(sites_offset)):
82*7a9b709eSPawan Gupta        site = rethunks_start_kcore + sites_offset[i]
83*7a9b709eSPawan Gupta        vmlinux_site = rethunks_start_vmlinux + sites_offset[i]
84*7a9b709eSPawan Gupta        try:
85*7a9b709eSPawan Gupta            passed = unknown = failed = skipped = False
86*7a9b709eSPawan Gupta
87*7a9b709eSPawan Gupta            symbol = identify_address(prog, site)
88*7a9b709eSPawan Gupta            vmlinux_insn = c.get_instruction_from_vmlinux(elffile, text_section, text_section['sh_addr'], vmlinux_site)
89*7a9b709eSPawan Gupta            kcore_insn = list(cap.disasm(prog.read(site, 16), site))[0]
90*7a9b709eSPawan Gupta
91*7a9b709eSPawan Gupta            insn_end = site + kcore_insn.size - 1
92*7a9b709eSPawan Gupta
93*7a9b709eSPawan Gupta            safe_site = insn_end & 0x20
94*7a9b709eSPawan Gupta            site_status = "" if safe_site else "(unsafe)"
95*7a9b709eSPawan Gupta
96*7a9b709eSPawan Gupta            ksft.print_msg(f"\nSite {i}: {symbol} <0x{site:x}> {site_status}")
97*7a9b709eSPawan Gupta            ksft.print_msg(f"\tvmlinux: 0x{vmlinux_insn.address:x}:\t{vmlinux_insn.mnemonic}\t{vmlinux_insn.op_str}")
98*7a9b709eSPawan Gupta            ksft.print_msg(f"\tkcore:   0x{kcore_insn.address:x}:\t{kcore_insn.mnemonic}\t{kcore_insn.op_str}")
99*7a9b709eSPawan Gupta
100*7a9b709eSPawan Gupta            if safe_site:
101*7a9b709eSPawan Gupta                tests_passed += 1
102*7a9b709eSPawan Gupta                passed = True
103*7a9b709eSPawan Gupta                ksft.print_msg(f"\tPASSED: At safe address")
104*7a9b709eSPawan Gupta                continue
105*7a9b709eSPawan Gupta
106*7a9b709eSPawan Gupta            if "jmp" in kcore_insn.mnemonic:
107*7a9b709eSPawan Gupta                passed = True
108*7a9b709eSPawan Gupta            elif "ret" not in kcore_insn.mnemonic:
109*7a9b709eSPawan Gupta                skipped = True
110*7a9b709eSPawan Gupta
111*7a9b709eSPawan Gupta            if passed:
112*7a9b709eSPawan Gupta                ksft.print_msg(f"\tPASSED: Found {kcore_insn.mnemonic} {kcore_insn.op_str}")
113*7a9b709eSPawan Gupta                tests_passed += 1
114*7a9b709eSPawan Gupta            elif skipped:
115*7a9b709eSPawan Gupta                ksft.print_msg(f"\tSKIPPED: Found '{kcore_insn.mnemonic}'")
116*7a9b709eSPawan Gupta                tests_skipped += 1
117*7a9b709eSPawan Gupta            elif unknown:
118*7a9b709eSPawan Gupta                ksft.print_msg(f"UNKNOWN: An unknown instruction: {kcore_insn}")
119*7a9b709eSPawan Gupta                tests_unknown += 1
120*7a9b709eSPawan Gupta            else:
121*7a9b709eSPawan Gupta                ksft.print_msg(f'\t************* FAILED *************')
122*7a9b709eSPawan Gupta                ksft.print_msg(f"\tFound {kcore_insn.mnemonic} {kcore_insn.op_str}")
123*7a9b709eSPawan Gupta                ksft.print_msg(f'\t**********************************')
124*7a9b709eSPawan Gupta                tests_failed += 1
125*7a9b709eSPawan Gupta        except Exception as e:
126*7a9b709eSPawan Gupta            ksft.print_msg(f"UNKNOWN: An unexpected error occurred: {e}")
127*7a9b709eSPawan Gupta            tests_unknown += 1
128*7a9b709eSPawan Gupta
129*7a9b709eSPawan Guptaksft.print_msg(f"\n\nSummary:")
130*7a9b709eSPawan Guptaksft.print_msg(f"PASSED: \t{tests_passed} \t/ {total_rethunk_tests}")
131*7a9b709eSPawan Guptaksft.print_msg(f"FAILED: \t{tests_failed} \t/ {total_rethunk_tests}")
132*7a9b709eSPawan Guptaksft.print_msg(f"SKIPPED: \t{tests_skipped} \t/ {total_rethunk_tests}")
133*7a9b709eSPawan Guptaksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_rethunk_tests}")
134*7a9b709eSPawan Gupta
135*7a9b709eSPawan Guptaif tests_failed == 0:
136*7a9b709eSPawan Gupta    ksft.test_result_pass("All ITS return thunk sites passed.")
137*7a9b709eSPawan Guptaelse:
138*7a9b709eSPawan Gupta    ksft.test_result_fail(f"{tests_failed} failed sites need ITS return thunks.")
139*7a9b709eSPawan Guptaksft.finished()
140