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