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