File Index
Files and directories
Sign in
FILE

its_indirect_alignment.py

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/x86/bugs/its_indirect_alignment.py PYTHON
  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
 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"
 31
 32mitigation = c.get_sysfs(bug)
 33if 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
 37if c.sysfs_has("spectre_v2", "Retpolines"):
 38    ksft.test_result_skip("Skipping its_indirect_alignment.py: Retpolines deployed")
 39    ksft.finished()
 40
 41c.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_indirect_alignment.py")
 42
 43from elftools.elf.elffile import ELFFile
 44from drgn.helpers.common.memory import identify_address
 45
 46cap = c.init_capstone()
 47
 48if 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
 56vmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux"
 57if 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
 61ksft.print_msg(f"Using vmlinux: {vmlinux}")
 62
 63retpolines_start_vmlinux, retpolines_sec_offset, size = c.get_section_info(vmlinux, '.retpoline_sites')
 64ksft.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
 66sites_offset = c.get_patch_sites(vmlinux, retpolines_sec_offset, size)
 67total_retpoline_tests = len(sites_offset)
 68ksft.print_msg(f"Found {total_retpoline_tests} retpoline sites")
 69
 70prog = c.get_runtime_kernel()
 71retpolines_start_kcore = prog.symbol('__retpoline_sites').address
 72ksft.print_msg(f'kcore: __retpoline_sites: 0x{retpolines_start_kcore:x}')
 73
 74x86_indirect_its_thunk_r15 = prog.symbol('__x86_indirect_its_thunk_r15').address
 75ksft.print_msg(f'kcore: __x86_indirect_its_thunk_r15: 0x{x86_indirect_its_thunk_r15:x}')
 76
 77tests_passed = 0
 78tests_failed = 0
 79tests_unknown = 0
 80
 81with 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
141ksft.print_msg(f"\n\nSummary:")
142ksft.print_msg(f"PASS:    \t{tests_passed} \t/ {total_retpoline_tests}")
143ksft.print_msg(f"FAIL:    \t{tests_failed} \t/ {total_retpoline_tests}")
144ksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_retpoline_tests}")
145
146if tests_failed == 0:
147    ksft.test_result_pass("All ITS return thunk sites passed")
148else:
149    ksft.test_result_fail(f"{tests_failed} ITS return thunk sites failed")
150ksft.finished()
~/ Read-only file preview.