diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 05c5230549..e9d1087390 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -481,7 +481,7 @@ if(BUILD_TESTING) foreach(opcode IN ITEMS add or adc sbb and sub xor cmp) list(APPEND tlsCoverageGroups alu_${opcode}) endforeach() - list(APPEND tlsCoverageGroups gs_traps) + list(APPEND tlsCoverageGroups gs_traps gs_traps_implicit) foreach(group IN LISTS tlsCoverageGroups) add_test(NAME tls_function_coverage_${group} COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_function_coverage.py $ ${group}) diff --git a/core/relinker/codegen/include/codegen/CodegenTypes.hpp b/core/relinker/codegen/include/codegen/CodegenTypes.hpp index 72bace4267..021f2bf059 100644 --- a/core/relinker/codegen/include/codegen/CodegenTypes.hpp +++ b/core/relinker/codegen/include/codegen/CodegenTypes.hpp @@ -68,6 +68,7 @@ struct DecodedInstructionInfo { bool AddressSize32; std::size_t RipRelativeDispOffset; bool HasModRm; + bool HasMemoryOperand; std::uint8_t ModRmByte; bool HasBranchTarget; std::int64_t BranchDisp; diff --git a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp index 7a9a14a364..c6db778a9e 100644 --- a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp +++ b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp @@ -146,6 +146,7 @@ inline constexpr std::uint8_t TwoByteMovdMovq = 0x7E; inline constexpr std::uint8_t TwoByteMovqMmxXmm = 0x6F; inline constexpr std::uint8_t TwoByteMovdqu = 0x7F; inline constexpr std::uint8_t TwoByteGrp7 = 0x01; +inline constexpr std::uint8_t TwoByteMaskmov = 0xF7; inline constexpr std::uint8_t TwoByteShiftImm8Min = 0x70; inline constexpr std::uint8_t TwoByteShiftImm8Max = 0x73; inline constexpr std::uint8_t ThreeByteEscape38 = 0x38; diff --git a/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp b/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp index c6cc9de4ac..b22c3f8726 100644 --- a/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp +++ b/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp @@ -356,6 +356,7 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction( std::size_t pos = 0; bool operandSizeOverride = false; + bool repPrefix = false; while (pos < info.Length) { std::uint8_t b = data[pos]; @@ -374,6 +375,8 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction( info.AddressSize32 = true; if (b == PrefixOperandSize) operandSizeOverride = true; + if (b == PrefixRep) + repPrefix = true; info.RexPrefix = 0; ++pos; continue; @@ -393,6 +396,8 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction( info.ModRmRegField = (modrm >> ModRmRegShift) & ModRmRegMask; const std::uint8_t mod = registerOnly ? ModRmModRegister : (modrm >> ModRmModShift) & ModRmModMask; const std::uint8_t rm = modrm & ModRmRmMask; + if (mod != ModRmModRegister) + info.HasMemoryOperand = true; if (mod == ModRmModIndirect && rm == ModRmRmRipRelative) { info.HasRipRelativeDisp = true; info.RipRelativeDispOffset = modrmPos + 1; @@ -404,9 +409,17 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction( const bool xop = op == XopPrefix && pos < info.Length && (data[pos] & Vex3MapMask) >= XopMapMin; if (op == OneByteVex2 || op == OneByteVex3 || op == EvexPrefix || xop) { - pos += (op == OneByteVex2 ? 1 : (op == OneByteVex3 || xop ? 2 : EvexPrefixLength - 1)) + 1; - if (pos < info.Length) + const bool vex = op == OneByteVex2 || op == OneByteVex3; + const std::size_t vectorOpcodeOffset = pos + (op == OneByteVex2 ? 1 : (op == OneByteVex3 || xop ? 2 : EvexPrefixLength - 1)); + const std::uint8_t vexMap = op == OneByteVex2 ? 1 : (data[pos] & (op == EvexPrefix ? EvexMapMask : Vex3MapMask)); + const std::uint8_t vectorOpcode = vectorOpcodeOffset < info.Length ? data[vectorOpcodeOffset] : 0; + pos = vectorOpcodeOffset + 1; + if (pos < info.Length) { readModRm(pos, false); + if (vex && vexMap == 1 && vectorOpcode == TwoByteMaskmov && + ((info.ModRmByte >> ModRmModShift) & ModRmModMask) == ModRmModRegister) + info.HasMemoryOperand = true; + } info.FlowKind = ControlFlowKind::Sequential; return info; } @@ -480,6 +493,8 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction( info.ModRmRegField = reg; std::uint8_t mod = (modrm >> ModRmModShift) & ModRmModMask; std::uint8_t rm = modrm & ModRmRmMask; + if (mod != ModRmModRegister) + info.HasMemoryOperand = true; if (reg == 2) { if (mod == ModRmModIndirect && rm == ModRmRmRipRelative) { info.FlowKind = ControlFlowKind::Call; @@ -592,6 +607,21 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction( if (hasModRm && pos < info.Length) readModRm(pos, twoByteOpcode && op >= TwoByteMovCrDrMin && op <= TwoByteMovCrDrMax); + if (info.HasModRm && twoByteOpcode) { + const std::uint8_t mod = (info.ModRmByte >> ModRmModShift) & ModRmModMask; + const std::uint8_t reg = (info.ModRmByte >> ModRmRegShift) & ModRmRegMask; + if (op == TwoByteMaskmov && mod == ModRmModRegister) + info.HasMemoryOperand = true; + else if (op == TwoByteGrp7 && (info.ModRmByte == 0xC8 || info.ModRmByte == 0xFA || info.ModRmByte == 0xFC)) + info.HasMemoryOperand = true; + else if (op == TwoByteGrp15 && repPrefix && reg == 6 && mod == ModRmModRegister) + info.HasMemoryOperand = true; + } + + if (!twoByteOpcode && ((op >= 0x6c && op <= 0x6f) || (op >= 0xa0 && op <= 0xa7) || + (op >= 0xaa && op <= 0xaf) || op == 0xd7)) + info.HasMemoryOperand = true; + info.FlowKind = ControlFlowKind::Sequential; return info; } diff --git a/core/relinker/codegen/tests/DecoderLengthTests.cpp b/core/relinker/codegen/tests/DecoderLengthTests.cpp index 63bca4bfe0..3ad1299b0d 100644 --- a/core/relinker/codegen/tests/DecoderLengthTests.cpp +++ b/core/relinker/codegen/tests/DecoderLengthTests.cpp @@ -117,6 +117,32 @@ int main() { const auto threeDNowInfo = decoder.DecodeInstruction(threeDNow.data(), threeDNow.size()); require(threeDNowInfo.HasModRm && threeDNowInfo.ModRmByte == 0x0d, "3DNow! ModRM was not reported"); require(threeDNowInfo.HasRipRelativeDisp && threeDNowInfo.RipRelativeDispOffset == 3, "3DNow! RIP-relative displacement was not reported"); + for (const auto& testCase : std::vector>{ + {"6590", false}, + {"654889c8", false}, + {"65488b0c2578030000", true}, + {"65a4", true}, + {"650fa2", false}, + {"650ff7c1", true}, + {"65660ff7c1", true}, + {"65c5f9f7c1", true}, + {"65c4e179f7c1", true}, + {"650f01c8", true}, + {"650f01fa", true}, + {"650f01fc", true}, + {"650f01c9", false}, + {"650f01fd", false}, + {"65f30faef0", true}, + {"f20faef0", false}, + {"660faef0", false}, + {"f30faee8", false}, + {"ff10", true}, + {"ffe0", false}, + }) { + const auto bytes = parse(testCase.first); + require(decoder.DecodeInstruction(bytes.data(), bytes.size()).HasMemoryOperand == testCase.second, + "Wrong memory operand presence for " + std::string(testCase.first)); + } std::cout << "Decoder length tests passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index 4c80a48eae..43a00cd7af 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -274,7 +274,8 @@ TlsScan ScanTls(const std::vector& source, const std::vector& source, co } if (info.SegmentPrefix == 0x65 && !insidePrevious) { - gsTraps.emplace_back(rva, info.Length); + if (info.HasMemoryOperand) + gsTraps.emplace_back(rva, info.Length); continue; } if (info.SegmentPrefix != 0 && !(insidePrevious && info.SegmentPrefix != 0x64)) { diff --git a/core/relinker/relinker/tests/test_macos_gs_traps.py b/core/relinker/relinker/tests/test_macos_gs_traps.py index 8b9c21512d..de0426bc5f 100644 --- a/core/relinker/relinker/tests/test_macos_gs_traps.py +++ b/core/relinker/relinker/tests/test_macos_gs_traps.py @@ -15,6 +15,22 @@ FORMS = { "gs-store": bytes.fromhex("65 48 89 04 25 30 00 00 00"), "gs-alu": bytes.fromhex("65 48 33 04 25 28 00 00 00"), "gs-short-register-address": bytes.fromhex("65 48 8b 00"), + "gs-maskmovdqu": bytes.fromhex("65 66 0f f7 c1"), + "gs-maskmovq": bytes.fromhex("65 0f f7 c1"), + "gs-vmaskmovdqu": bytes.fromhex("65 c5 f9 f7 c1"), + "gs-monitor": bytes.fromhex("65 0f 01 c8"), + "gs-monitorx": bytes.fromhex("65 0f 01 fa"), + "gs-clzero": bytes.fromhex("65 0f 01 fc"), + "gs-umonitor": bytes.fromhex("65 f3 0f ae f0"), +} + +NON_MEMORY_FORMS = { + "gs-nop": bytes.fromhex("65 90"), + "gs-register-move": bytes.fromhex("65 48 89 c8"), + "gs-cpuid": bytes.fromhex("65 0f a2"), + "gs-mwait": bytes.fromhex("65 0f 01 c9"), + "gs-rdpru": bytes.fromhex("65 0f 01 fd"), + "gs-umwait": bytes.fromhex("65 f2 0f ae f0"), } @@ -38,12 +54,20 @@ def check_trapped(name, code, instruction): raise SystemExit(f"{name}: the GS access was left in the code") +def check_untouched(name, code, instruction): + if instruction + b"\xc3" not in code: + raise SystemExit(f"{name}: a non-memory GS instruction was replaced") + + def main(): relinker = Path(sys.argv[1]).resolve() with tempfile.TemporaryDirectory(prefix="anyps5-macos-gs-") as directory: for name, instruction in FORMS.items(): image = make_image("register", "unwind", body=instruction + b"\xc3") check_trapped(name, convert(relinker, directory, name, image), instruction) + for name, instruction in NON_MEMORY_FORMS.items(): + image = make_image("register", "unwind", body=instruction + b"\xc3") + check_untouched(name, convert(relinker, directory, name, image), instruction) instruction = FORMS["gs-load-rcx"] image = make_image("register", "unwind", body=instruction + b"\xc3") struct.pack_into("