diff --git a/FEXCore/Source/Interface/Core/Frontend.cpp b/FEXCore/Source/Interface/Core/Frontend.cpp index 02cfdb100..b2e7ee1bd 100644 --- a/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/FEXCore/Source/Interface/Core/Frontend.cpp @@ -1091,13 +1091,21 @@ Decoder::DecodedBlockStatus Decoder::DecodeInstruction(uint64_t PC) { if (ErrorDuringDecoding != DecodedBlockStatus::SUCCESS || HitNonExecutableRange || HitBadRelocation) [[unlikely]] { // Put an invalid instruction in the stream so the core can raise SIGILL if hit // Error while decoding instruction. We don't know the table or instruction size + const auto InstSize = DecodeInst->InstSize; DecodeInst->TableInfo = nullptr; - auto Result = ErrorDuringDecoding != DecodedBlockStatus::SUCCESS ? ErrorDuringDecoding : - DecodeInst->InstSize ? DecodedBlockStatus::PARTIAL_DECODE_INST : - HitNonExecutableRange ? DecodedBlockStatus::NOEXEC_INST : - DecodedBlockStatus::BAD_RELOCATION; DecodeInst->InstSize = 0; - return Result; + + // A decode error can be caused by substituting zero for an inaccessible + // instruction byte, so the instruction fetch fault takes priority. + if (HitNonExecutableRange) { + return InstSize ? DecodedBlockStatus::PARTIAL_DECODE_INST : DecodedBlockStatus::NOEXEC_INST; + } + + if (HitBadRelocation) { + return DecodedBlockStatus::BAD_RELOCATION; + } + + return ErrorDuringDecoding; } else if (!DecodeInst->TableInfo || (DecodeInst->TableInfo->Type == TYPE_INST && !DecodeInst->TableInfo->OpcodeDispatcher.OpDispatch)) { // If there wasn't an error during decoding but we have no dispatcher for the instruction then claim invalid instruction. return DecodedBlockStatus::INVALID_INST; diff --git a/unittests/FEXLinuxTests/tests/signal/instruction_fetch_fault.64.cpp b/unittests/FEXLinuxTests/tests/signal/instruction_fetch_fault.64.cpp new file mode 100644 index 000000000..98b9e00cc --- /dev/null +++ b/unittests/FEXLinuxTests/tests/signal/instruction_fetch_fault.64.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include +#include +#include +#include + +static jmp_buf LongJump {}; +static volatile sig_atomic_t CaughtSignal {}; + +static void SignalHandler(int Signal, siginfo_t*, void*) { + CaughtSignal = Signal; + longjmp(LongJump, 1); +} + +TEST_CASE("Signals: Instruction fetch faults take priority over decode errors") { + struct sigaction Act {}; + Act.sa_sigaction = SignalHandler; + Act.sa_flags = SA_SIGINFO; + + REQUIRE(sigaction(SIGILL, &Act, nullptr) == 0); + REQUIRE(sigaction(SIGSEGV, &Act, nullptr) == 0); + + const auto PageSize = sysconf(_SC_PAGESIZE); + REQUIRE(PageSize > 0); + + void* Ptr = mmap(nullptr, PageSize * 2, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + REQUIRE(Ptr != MAP_FAILED); + + auto Code = static_cast(Ptr) + PageSize - 2; + // movzx r8d, byte [rax] + Code[0] = 0x44; + Code[1] = 0x0f; + Code[2] = 0xb6; + Code[3] = 0x00; + Code[4] = 0xc3; + + REQUIRE(mprotect(static_cast(Ptr) + PageSize, PageSize, PROT_NONE) == 0); + + using FuncPtr = void (*)(); + auto Func = reinterpret_cast(Code); + + CaughtSignal = 0; + if (setjmp(LongJump) == 0) { + Func(); + FAIL("Cross-page instruction unexpectedly executed"); + } + + CHECK(CaughtSignal == SIGSEGV); + REQUIRE(munmap(Ptr, PageSize * 2) == 0); +}