From 5fc33449559fb47e5c2ecce600447c4cc4121a09 Mon Sep 17 00:00:00 2001 From: Nciibi Date: Fri, 30 Oct 2026 12:49:14 +0100 Subject: [PATCH] fix(relinker): restrict 32-bit wide-store splits to absolute operands --- .../include/codegen/x86/X64OpcodeConstants.hpp | 2 ++ core/relinker/codegen/src/x86/WideStoreOperands.cpp | 13 +++++++++++-- .../codegen/tests/Amd64OnlyConverterTests.cpp | 7 +++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp index 951a99b18e..08363b0d1e 100644 --- a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp +++ b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp @@ -215,6 +215,8 @@ inline constexpr std::uint8_t ModRmRmRipRelative = 0x05; inline constexpr std::uint8_t Grp3RegTestMax = 0x01; inline constexpr std::uint8_t SibBaseMask = 0x07; inline constexpr std::uint8_t SibBaseDisp32 = 0x05; +inline constexpr std::uint8_t SibIndexMask = 0x38; +inline constexpr std::uint8_t SibIndexNone = 0x20; inline constexpr std::size_t Disp8Size = 1; inline constexpr std::size_t Disp32Size = 4; diff --git a/core/relinker/codegen/src/x86/WideStoreOperands.cpp b/core/relinker/codegen/src/x86/WideStoreOperands.cpp index c48feebe3e..df22eb8456 100644 --- a/core/relinker/codegen/src/x86/WideStoreOperands.cpp +++ b/core/relinker/codegen/src/x86/WideStoreOperands.cpp @@ -79,6 +79,7 @@ std::optional DecodeVexWideStore(const std::uint8_t* data, co } const bool ripRelative = mod == ModRmModIndirect && rm == ModRmRmRipRelative; const bool absolute = mod == ModRmModIndirect && sib.has_value() && (*sib & SibBaseMask) == SibBaseDisp32; + const bool absoluteNoIndex = absolute && (*sib & SibIndexMask) == SibIndexNone; const std::size_t displacementSize = mod == ModRmModDisp8 ? Disp8Size : (mod == ModRmModDisp32 || ripRelative || absolute) ? Disp32Size : 0; if (cursor + displacementSize != length) return std::nullopt; @@ -113,8 +114,16 @@ std::optional DecodeVexWideStore(const std::uint8_t* data, co highDisplacementSize = highMod == ModRmModDisp8 ? Disp8Size : Disp32Size; } } - if (!ripRelative && !addressSize32 && highDisplacement > std::numeric_limits::max()) - throw CodegenException("256-bit store displacement leaves no room for its upper half"); + const bool address32Absolute = addressSize32 && absoluteNoIndex; + const auto unsignedHighDisplacement = static_cast(static_cast(displacement)) + kHalfBytes; + if (!ripRelative) { + if (address32Absolute) { + if (unsignedHighDisplacement > std::numeric_limits::max()) + throw CodegenException("256-bit store displacement leaves no room for its upper half"); + } else if (highDisplacement > std::numeric_limits::max()) { + throw CodegenException("256-bit store displacement leaves no room for its upper half"); + } + } high.push_back(static_cast((highMod << ModRmModShift) | reg | rm)); if (sib) high.push_back(*sib); diff --git a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp index 290c9842ce..27cf7b5fff 100644 --- a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp +++ b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp @@ -843,8 +843,11 @@ void wideStoreSplit() { const auto absolute32Split = Codegen::DecodeVexWideStore(absolute32.data(), absolute32.size()); require(absolute32Split && absolute32Split->Low.Bytes == Bytes{0x67, 0xC5, 0xF8, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0x7F} && absolute32Split->High.Bytes == Bytes{0x67, 0xC4, 0xE3, 0x7D, 0x19, 0x04, 0x25, 0x08, 0x00, 0x00, 0x80, 0x01}, "A 32-bit absolute 256-bit store was not split across the signed displacement boundary"); const Bytes disp32 = {0x67, 0xC5, 0xFC, 0x11, 0x80, 0xF8, 0xFF, 0xFF, 0x7F}; - const auto disp32Split = Codegen::DecodeVexWideStore(disp32.data(), disp32.size()); - require(disp32Split && disp32Split->Low.Bytes == Bytes{0x67, 0xC5, 0xF8, 0x11, 0x80, 0xF8, 0xFF, 0xFF, 0x7F} && disp32Split->High.Bytes == Bytes{0x67, 0xC4, 0xE3, 0x7D, 0x19, 0x80, 0x08, 0x00, 0x00, 0x80, 0x01}, "A 32-bit base-relative 256-bit store was not split across the signed displacement boundary"); + requireFailure([&] { (void)Codegen::DecodeVexWideStore(disp32.data(), disp32.size()); }, "A 32-bit base-relative 256-bit store whose upper half can wrap was split"); + const Bytes indexed32 = {0x67, 0xC5, 0xFC, 0x11, 0x04, 0x0D, 0xF8, 0xFF, 0xFF, 0x7F}; + requireFailure([&] { (void)Codegen::DecodeVexWideStore(indexed32.data(), indexed32.size()); }, "A 32-bit indexed 256-bit store whose upper half can wrap was split"); + const Bytes absolute32Overflow = {0x67, 0xC5, 0xFC, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0xFF}; + requireFailure([&] { (void)Codegen::DecodeVexWideStore(absolute32Overflow.data(), absolute32Overflow.size()); }, "A 32-bit absolute 256-bit store whose upper half crosses the address boundary was split"); const Bytes absolute64 = {0xC5, 0xFC, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0x7F}; requireFailure([&] { (void)Codegen::DecodeVexWideStore(absolute64.data(), absolute64.size()); }, "A 64-bit absolute 256-bit store whose upper half has no signed displacement was kept"); requireFailure([] { Codegen::StubBodyBuilder body; body.RipRelative(Bytes{0x90}, 0, 0); }, "A RIP-relative displacement outside its instruction was relocated");