its_indirect_alignment.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  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. # Install dependencies
  11. # add-apt-repository ppa:michel-slm/kernel-utils
  12. # apt update
  13. # apt install -y python3-drgn python3-pyelftools python3-capstone
  14. #
  15. # Best to copy the vmlinux at a standard location:
  16. # mkdir -p /usr/lib/debug/lib/modules/$(uname -r)
  17. # cp $VMLINUX /usr/lib/debug/lib/modules/$(uname -r)/vmlinux
  18. #
  19. # Usage: ./its_indirect_alignment.py [vmlinux]
  20. import os, sys, argparse
  21. from pathlib import Path
  22. this_dir = os.path.dirname(os.path.realpath(__file__))
  23. sys.path.insert(0, this_dir + '/../../kselftest')
  24. import ksft
  25. import common as c
  26. bug = "indirect_target_selection"
  27. mitigation = c.get_sysfs(bug)
  28. if not mitigation or "Aligned branch/return thunks" not in mitigation:
  29. ksft.test_result_skip("Skipping its_indirect_alignment.py: Aligned branch/return thunks not enabled")
  30. ksft.finished()
  31. if c.sysfs_has("spectre_v2", "Retpolines"):
  32. ksft.test_result_skip("Skipping its_indirect_alignment.py: Retpolines deployed")
  33. ksft.finished()
  34. c.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_indirect_alignment.py")
  35. from elftools.elf.elffile import ELFFile
  36. from drgn.helpers.common.memory import identify_address
  37. cap = c.init_capstone()
  38. if len(os.sys.argv) > 1:
  39. arg_vmlinux = os.sys.argv[1]
  40. if not os.path.exists(arg_vmlinux):
  41. ksft.test_result_fail(f"its_indirect_alignment.py: vmlinux not found at argument path: {arg_vmlinux}")
  42. ksft.exit_fail()
  43. os.makedirs(f"/usr/lib/debug/lib/modules/{os.uname().release}", exist_ok=True)
  44. os.system(f'cp {arg_vmlinux} /usr/lib/debug/lib/modules/$(uname -r)/vmlinux')
  45. vmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux"
  46. if not os.path.exists(vmlinux):
  47. ksft.test_result_fail(f"its_indirect_alignment.py: vmlinux not found at {vmlinux}")
  48. ksft.exit_fail()
  49. ksft.print_msg(f"Using vmlinux: {vmlinux}")
  50. retpolines_start_vmlinux, retpolines_sec_offset, size = c.get_section_info(vmlinux, '.retpoline_sites')
  51. ksft.print_msg(f"vmlinux: Section .retpoline_sites (0x{retpolines_start_vmlinux:x}) found at 0x{retpolines_sec_offset:x} with size 0x{size:x}")
  52. sites_offset = c.get_patch_sites(vmlinux, retpolines_sec_offset, size)
  53. total_retpoline_tests = len(sites_offset)
  54. ksft.print_msg(f"Found {total_retpoline_tests} retpoline sites")
  55. prog = c.get_runtime_kernel()
  56. retpolines_start_kcore = prog.symbol('__retpoline_sites').address
  57. ksft.print_msg(f'kcore: __retpoline_sites: 0x{retpolines_start_kcore:x}')
  58. x86_indirect_its_thunk_r15 = prog.symbol('__x86_indirect_its_thunk_r15').address
  59. ksft.print_msg(f'kcore: __x86_indirect_its_thunk_r15: 0x{x86_indirect_its_thunk_r15:x}')
  60. tests_passed = 0
  61. tests_failed = 0
  62. tests_unknown = 0
  63. with open(vmlinux, 'rb') as f:
  64. elffile = ELFFile(f)
  65. text_section = elffile.get_section_by_name('.text')
  66. for i in range(0, len(sites_offset)):
  67. site = retpolines_start_kcore + sites_offset[i]
  68. vmlinux_site = retpolines_start_vmlinux + sites_offset[i]
  69. passed = unknown = failed = False
  70. try:
  71. vmlinux_insn = c.get_instruction_from_vmlinux(elffile, text_section, text_section['sh_addr'], vmlinux_site)
  72. kcore_insn = list(cap.disasm(prog.read(site, 16), site))[0]
  73. operand = kcore_insn.op_str
  74. insn_end = site + kcore_insn.size - 1 # TODO handle Jcc.32 __x86_indirect_thunk_\reg
  75. safe_site = insn_end & 0x20
  76. site_status = "" if safe_site else "(unsafe)"
  77. ksft.print_msg(f"\nSite {i}: {identify_address(prog, site)} <0x{site:x}> {site_status}")
  78. ksft.print_msg(f"\tvmlinux: 0x{vmlinux_insn.address:x}:\t{vmlinux_insn.mnemonic}\t{vmlinux_insn.op_str}")
  79. ksft.print_msg(f"\tkcore: 0x{kcore_insn.address:x}:\t{kcore_insn.mnemonic}\t{kcore_insn.op_str}")
  80. if (site & 0x20) ^ (insn_end & 0x20):
  81. ksft.print_msg(f"\tSite at safe/unsafe boundary: {str(kcore_insn.bytes)} {kcore_insn.mnemonic} {operand}")
  82. if safe_site:
  83. tests_passed += 1
  84. passed = True
  85. ksft.print_msg(f"\tPASSED: At safe address")
  86. continue
  87. if operand.startswith('0xffffffff'):
  88. thunk = int(operand, 16)
  89. if thunk > x86_indirect_its_thunk_r15:
  90. insn_at_thunk = list(cap.disasm(prog.read(thunk, 16), thunk))[0]
  91. operand += ' -> ' + insn_at_thunk.mnemonic + ' ' + insn_at_thunk.op_str + ' <dynamic-thunk?>'
  92. if 'jmp' in insn_at_thunk.mnemonic and thunk & 0x20:
  93. ksft.print_msg(f"\tPASSED: Found {operand} at safe address")
  94. passed = True
  95. if not passed:
  96. if kcore_insn.operands[0].type == capstone.CS_OP_IMM:
  97. operand += ' <' + prog.symbol(int(operand, 16)) + '>'
  98. if '__x86_indirect_its_thunk_' in operand:
  99. ksft.print_msg(f"\tPASSED: Found {operand}")
  100. else:
  101. ksft.print_msg(f"\tPASSED: Found direct branch: {kcore_insn}, ITS thunk not required.")
  102. passed = True
  103. else:
  104. unknown = True
  105. if passed:
  106. tests_passed += 1
  107. elif unknown:
  108. ksft.print_msg(f"UNKNOWN: unexpected operand: {kcore_insn}")
  109. tests_unknown += 1
  110. else:
  111. ksft.print_msg(f'\t************* FAILED *************')
  112. ksft.print_msg(f"\tFound {kcore_insn.bytes} {kcore_insn.mnemonic} {operand}")
  113. ksft.print_msg(f'\t**********************************')
  114. tests_failed += 1
  115. except Exception as e:
  116. ksft.print_msg(f"UNKNOWN: An unexpected error occurred: {e}")
  117. tests_unknown += 1
  118. ksft.print_msg(f"\n\nSummary:")
  119. ksft.print_msg(f"PASS: \t{tests_passed} \t/ {total_retpoline_tests}")
  120. ksft.print_msg(f"FAIL: \t{tests_failed} \t/ {total_retpoline_tests}")
  121. ksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_retpoline_tests}")
  122. if tests_failed == 0:
  123. ksft.test_result_pass("All ITS return thunk sites passed")
  124. else:
  125. ksft.test_result_fail(f"{tests_failed} ITS return thunk sites failed")
  126. ksft.finished()