diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 4660de2490c..d96297766cc 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -421,6 +421,9 @@ if(BUILD_TESTING) endforeach() add_test(NAME tls_diagnostics COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_diagnostics.py $) + add_test(NAME macos_tls_alu COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_tls_alu.py $) + set_tests_properties(macos_tls_alu PROPERTIES TIMEOUT 60) add_test(NAME linux_load_alignment COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_load_alignment.py $) add_test(NAME linux_eh_frame_header COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index 838d0d6c8bb..f5ddcbd47da 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -200,8 +200,13 @@ struct TlsAccess { std::uint32_t Immediate; std::uint8_t Register; std::uint32_t Displacement; + std::uint8_t AluOpcode; }; +bool IsAluReadOpcode(std::uint8_t opcode) { + return opcode == 0x03 || opcode == 0x0b || opcode == 0x13 || opcode == 0x1b || opcode == 0x23 || opcode == 0x2b || opcode == 0x33 || opcode == 0x3b; +} + struct TlsScan { const Domain::ProgramHeader* Tls = nullptr; std::vector Accesses; @@ -239,14 +244,15 @@ TlsScan ScanTls(const std::vector& source, const std::vector(((bytes[position + 1] >> 3) & 7) | ((info.RexPrefix & 4) << 1)) : std::uint8_t{4}; const bool loadValue = supportedPrefixes && (info.RexPrefix == 0x48 || info.RexPrefix == 0x4c) && loadRegister != 4 && bytes[position] == 0x8b && (bytes[position + 1] & 0xc7) == 0x04 && bytes[position + 2] == 0x25; const bool storeImmediate = supportedPrefixes && !hasOperandSizePrefix && (info.RexPrefix == 0 || info.RexPrefix == 0x40) && info.Length - position == 11 && bytes[position] == 0xc7 && bytes[position + 1] == 0x04 && bytes[position + 2] == 0x25 && Io::ReadU32(source, header.Offset + offset + position + 3) == 0x28; - if (!loadValue && !storeImmediate) { + const bool aluRead = supportedPrefixes && (info.RexPrefix == 0x48 || info.RexPrefix == 0x4c) && loadRegister != 4 && IsAluReadOpcode(bytes[position]) && (bytes[position + 1] & 0xc7) == 0x04 && bytes[position + 2] == 0x25; + if (!loadValue && !storeImmediate && !aluRead) { std::ostringstream message; message << "Unsupported macOS guest TLS instruction (bytes:" << std::hex << std::setfill('0'); for (std::size_t index = 0; index < info.Length; ++index) message << ' ' << std::setw(2) << static_cast(bytes[index]); message << ')'; throw Domain::RelinkerException(message.str(), header.Offset + offset); } - scan.Accesses.push_back({rva, info.Length, storeImmediate, storeImmediate ? Io::ReadU32(source, header.Offset + offset + position + 7) : 0, storeImmediate ? std::uint8_t{0} : loadRegister, storeImmediate ? 0 : Io::ReadU32(source, header.Offset + offset + position + 3)}); + scan.Accesses.push_back({rva, info.Length, storeImmediate, storeImmediate ? Io::ReadU32(source, header.Offset + offset + position + 7) : 0, storeImmediate ? std::uint8_t{0} : loadRegister, storeImmediate ? 0 : Io::ReadU32(source, header.Offset + offset + position + 3), aluRead ? bytes[position] : std::uint8_t{0}}); } } return scan; @@ -481,8 +487,13 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { const auto stub = code.GetRva(); const bool preserveAccumulator = access.StoreImmediate || access.Register != 0; code.Emit({0x48, 0x8d, 0x64, 0x24, 0x80}); - code.Emit({0x9c}); - if (preserveAccumulator) code.Emit({0x50}); + if (access.AluOpcode != 0) { + code.Emit({0x50}); + code.Emit({0x9c}); + } else { + code.Emit({0x9c}); + if (preserveAccumulator) code.Emit({0x50}); + } code.Rip({0x48, 0x8b, 0x05}, metaRva + static_cast(MetaTlsKey)); code.Emit({0x65, 0x48, 0x8b, 0x04, 0xc5, 0, 0, 0, 0}); code.Emit({0x48, 0x85, 0xc0}); @@ -497,10 +508,21 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { code.Emit({0x48, 0x8b, 0x80}); code.U32(access.Displacement); } - if (!access.StoreImmediate && access.Register != 0) - code.Emit({static_cast(0x48 | (access.Register >> 3)), 0x89, static_cast(0xc0 | (access.Register & 7))}); - if (preserveAccumulator) code.Emit({0x58}); - code.Emit({0x9d}); + if (access.AluOpcode != 0 && access.Register == 0) { + code.Emit({0x9d}); + code.Emit({0x48, 0x87, 0x04, 0x24}); + code.Emit({0x48, access.AluOpcode, 0x04, 0x24}); + code.Emit({0x48, 0x8d, 0x64, 0x24, 0x08}); + } else if (access.AluOpcode != 0) { + code.Emit({0x9d}); + code.Emit({static_cast(0x48 | ((access.Register >> 3) << 2)), access.AluOpcode, static_cast(0xc0 | ((access.Register & 7) << 3))}); + code.Emit({0x58}); + } else { + if (!access.StoreImmediate && access.Register != 0) + code.Emit({static_cast(0x48 | (access.Register >> 3)), 0x89, static_cast(0xc0 | (access.Register & 7))}); + if (preserveAccumulator) code.Emit({0x58}); + code.Emit({0x9d}); + } code.Emit({0x48, 0x8d, 0xa4, 0x24, 0x80, 0, 0, 0}); code.Rip({0xe9}, static_cast(access.Rva + access.Length)); diff --git a/core/relinker/relinker/tests/test_macos_tls_alu.py b/core/relinker/relinker/tests/test_macos_tls_alu.py new file mode 100644 index 00000000000..e0199cecf38 --- /dev/null +++ b/core/relinker/relinker/tests/test_macos_tls_alu.py @@ -0,0 +1,58 @@ +"""Check that `relinker --macos` rewrites ALU instructions that read guest TLS through %fs.""" +from pathlib import Path +import subprocess +import sys +import tempfile + +from test_macos_output import MachO +from test_tls_function_coverage import make_image + + +FORMS = { + "sub-rax-stack-guard": (bytes.fromhex("64 48 2b 04 25 28 00 00 00"), bytes.fromhex("9d 48 87 04 24 48 2b 04 24 48 8d 64 24 08")), + "sub-rcx-stack-guard": (bytes.fromhex("64 48 2b 0c 25 28 00 00 00"), bytes.fromhex("9d 48 2b c8 58")), + "xor-r10-stack-guard": (bytes.fromhex("64 4c 33 14 25 28 00 00 00"), bytes.fromhex("9d 4c 33 d0 58")), + "adc-rdx-tls-block": (bytes.fromhex("64 48 13 14 25 f8 ff ff ff"), bytes.fromhex("9d 48 13 d0 58")), + "cmp-r8-thread-pointer": (bytes.fromhex("64 4c 3b 04 25 00 00 00 00"), bytes.fromhex("9d 4c 3b c0 58")), +} + + +def convert(relinker, directory, name, image): + source = Path(directory) / f"{name}.elf" + output = Path(directory) / name + source.write_bytes(bytes(image)) + result = subprocess.run([str(relinker), "--macos", "--skip-sce-module", str(source), str(output)], + capture_output=True, text=True, timeout=60) + if result.returncode != 0: + raise SystemExit(f"{name}: relinker failed: {result.stdout}{result.stderr}") + data = output.read_bytes() + return b"".join(data[lo:hi] for lo, hi in MachO(data).executable_ranges()) + + +def check_outside_block_refused(relinker, directory): + image = make_image("register", "unwind", body=bytes.fromhex("64 48 2b 04 25 00 ff ff ff c3")) + source = Path(directory) / "outside-block.elf" + output = Path(directory) / "outside-block" + source.write_bytes(bytes(image)) + result = subprocess.run([str(relinker), "--macos", "--skip-sce-module", str(source), str(output)], + capture_output=True, text=True, timeout=60) + message = result.stdout + result.stderr + if result.returncode != 2 or "is not in the thread TLS block" not in message or output.exists(): + raise SystemExit(f"outside-block: an ALU read outside the thread TLS block was not refused: {message}") + + +def main(): + relinker = Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="anyps5-macos-tls-alu-") as directory: + for name, (instruction, operation) in FORMS.items(): + code = convert(relinker, directory, name, make_image("register", "unwind", body=instruction + b"\xc3")) + if instruction in code: + raise SystemExit(f"{name}: the %fs access was left in the code") + if operation not in code: + raise SystemExit(f"{name}: the stub does not apply the operation to the guest TLS value") + check_outside_block_refused(relinker, directory) + print("macOS TLS ALU checks passed") + + +if __name__ == "__main__": + main()