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 22import os, sys, argparse 23from pathlib import Path 24 25this_dir = os.path.dirname(os.path.realpath(__file__)) 26sys.path.insert(0, this_dir + '/../../kselftest') 27import ksft 28import common as c 29 30bug = "indirect_target_selection" 31mitigation = c.get_sysfs(bug) 32if 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 36c.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_ret_alignment.py") 37 38from elftools.elf.elffile import ELFFile 39from drgn.helpers.common.memory import identify_address 40 41cap = c.init_capstone() 42 43if 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 51vmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux" 52if 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 56ksft.print_msg(f"Using vmlinux: {vmlinux}") 57 58rethunks_start_vmlinux, rethunks_sec_offset, size = c.get_section_info(vmlinux, '.return_sites') 59ksft.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 61sites_offset = c.get_patch_sites(vmlinux, rethunks_sec_offset, size) 62total_rethunk_tests = len(sites_offset) 63ksft.print_msg(f"Found {total_rethunk_tests} rethunk sites") 64 65prog = c.get_runtime_kernel() 66rethunks_start_kcore = prog.symbol('__return_sites').address 67ksft.print_msg(f'kcore: __rethunk_sites: 0x{rethunks_start_kcore:x}') 68 69its_return_thunk = prog.symbol('its_return_thunk').address 70ksft.print_msg(f'kcore: its_return_thunk: 0x{its_return_thunk:x}') 71 72tests_passed = 0 73tests_failed = 0 74tests_unknown = 0 75tests_skipped = 0 76 77with 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 129ksft.print_msg(f"\n\nSummary:") 130ksft.print_msg(f"PASSED: \t{tests_passed} \t/ {total_rethunk_tests}") 131ksft.print_msg(f"FAILED: \t{tests_failed} \t/ {total_rethunk_tests}") 132ksft.print_msg(f"SKIPPED: \t{tests_skipped} \t/ {total_rethunk_tests}") 133ksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_rethunk_tests}") 134 135if tests_failed == 0: 136 ksft.test_result_pass("All ITS return thunk sites passed.") 137else: 138 ksft.test_result_fail(f"{tests_failed} failed sites need ITS return thunks.") 139ksft.finished() 140