its_ret_alignment.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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. import os, sys, argparse
  22. from pathlib import Path
  23. this_dir = os.path.dirname(os.path.realpath(__file__))
  24. sys.path.insert(0, this_dir + '/../../kselftest')
  25. import ksft
  26. import common as c
  27. bug = "indirect_target_selection"
  28. mitigation = c.get_sysfs(bug)
  29. if not mitigation or "Aligned branch/return thunks" not in mitigation:
  30. ksft.test_result_skip("Skipping its_ret_alignment.py: Aligned branch/return thunks not enabled")
  31. ksft.finished()
  32. c.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_ret_alignment.py")
  33. from elftools.elf.elffile import ELFFile
  34. from drgn.helpers.common.memory import identify_address
  35. cap = c.init_capstone()
  36. if len(os.sys.argv) > 1:
  37. arg_vmlinux = os.sys.argv[1]
  38. if not os.path.exists(arg_vmlinux):
  39. ksft.test_result_fail(f"its_ret_alignment.py: vmlinux not found at user-supplied path: {arg_vmlinux}")
  40. ksft.exit_fail()
  41. os.makedirs(f"/usr/lib/debug/lib/modules/{os.uname().release}", exist_ok=True)
  42. os.system(f'cp {arg_vmlinux} /usr/lib/debug/lib/modules/$(uname -r)/vmlinux')
  43. vmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux"
  44. if not os.path.exists(vmlinux):
  45. ksft.test_result_fail(f"its_ret_alignment.py: vmlinux not found at {vmlinux}")
  46. ksft.exit_fail()
  47. ksft.print_msg(f"Using vmlinux: {vmlinux}")
  48. rethunks_start_vmlinux, rethunks_sec_offset, size = c.get_section_info(vmlinux, '.return_sites')
  49. 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}")
  50. sites_offset = c.get_patch_sites(vmlinux, rethunks_sec_offset, size)
  51. total_rethunk_tests = len(sites_offset)
  52. ksft.print_msg(f"Found {total_rethunk_tests} rethunk sites")
  53. prog = c.get_runtime_kernel()
  54. rethunks_start_kcore = prog.symbol('__return_sites').address
  55. ksft.print_msg(f'kcore: __rethunk_sites: 0x{rethunks_start_kcore:x}')
  56. its_return_thunk = prog.symbol('its_return_thunk').address
  57. ksft.print_msg(f'kcore: its_return_thunk: 0x{its_return_thunk:x}')
  58. tests_passed = 0
  59. tests_failed = 0
  60. tests_unknown = 0
  61. tests_skipped = 0
  62. with open(vmlinux, 'rb') as f:
  63. elffile = ELFFile(f)
  64. text_section = elffile.get_section_by_name('.text')
  65. for i in range(len(sites_offset)):
  66. site = rethunks_start_kcore + sites_offset[i]
  67. vmlinux_site = rethunks_start_vmlinux + sites_offset[i]
  68. try:
  69. passed = unknown = failed = skipped = False
  70. symbol = identify_address(prog, site)
  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. insn_end = site + kcore_insn.size - 1
  74. safe_site = insn_end & 0x20
  75. site_status = "" if safe_site else "(unsafe)"
  76. ksft.print_msg(f"\nSite {i}: {symbol} <0x{site:x}> {site_status}")
  77. ksft.print_msg(f"\tvmlinux: 0x{vmlinux_insn.address:x}:\t{vmlinux_insn.mnemonic}\t{vmlinux_insn.op_str}")
  78. ksft.print_msg(f"\tkcore: 0x{kcore_insn.address:x}:\t{kcore_insn.mnemonic}\t{kcore_insn.op_str}")
  79. if safe_site:
  80. tests_passed += 1
  81. passed = True
  82. ksft.print_msg(f"\tPASSED: At safe address")
  83. continue
  84. if "jmp" in kcore_insn.mnemonic:
  85. passed = True
  86. elif "ret" not in kcore_insn.mnemonic:
  87. skipped = True
  88. if passed:
  89. ksft.print_msg(f"\tPASSED: Found {kcore_insn.mnemonic} {kcore_insn.op_str}")
  90. tests_passed += 1
  91. elif skipped:
  92. ksft.print_msg(f"\tSKIPPED: Found '{kcore_insn.mnemonic}'")
  93. tests_skipped += 1
  94. elif unknown:
  95. ksft.print_msg(f"UNKNOWN: An unknown instruction: {kcore_insn}")
  96. tests_unknown += 1
  97. else:
  98. ksft.print_msg(f'\t************* FAILED *************')
  99. ksft.print_msg(f"\tFound {kcore_insn.mnemonic} {kcore_insn.op_str}")
  100. ksft.print_msg(f'\t**********************************')
  101. tests_failed += 1
  102. except Exception as e:
  103. ksft.print_msg(f"UNKNOWN: An unexpected error occurred: {e}")
  104. tests_unknown += 1
  105. ksft.print_msg(f"\n\nSummary:")
  106. ksft.print_msg(f"PASSED: \t{tests_passed} \t/ {total_rethunk_tests}")
  107. ksft.print_msg(f"FAILED: \t{tests_failed} \t/ {total_rethunk_tests}")
  108. ksft.print_msg(f"SKIPPED: \t{tests_skipped} \t/ {total_rethunk_tests}")
  109. ksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_rethunk_tests}")
  110. if tests_failed == 0:
  111. ksft.test_result_pass("All ITS return thunk sites passed.")
  112. else:
  113. ksft.test_result_fail(f"{tests_failed} failed sites need ITS return thunks.")
  114. ksft.finished()