From 7424949b8bb2dd04011a683dbc1949b8ee6cd7de Mon Sep 17 00:00:00 2001 From: roboterspinne <229981445+roboterspinne@users.noreply.github.com> Date: Sat, 24 Oct 2026 19:47:43 +0200 Subject: [PATCH] fix(relinker): refuse branches into trapped guest GS instructions on Windows Windows output replaced each guest GS memory instruction with UD2 and NOP fill without checking the decoded direct branch targets, so a branch past the first byte of such an instruction was converted and landed in the second UD2 byte or the NOP fill. A branch target strictly inside a trapped GS instruction is now refused with "Branch enters a guest GS instruction", the rule macOS applies since #7706 and this builder already applies to its FS rewrites. The branch targets are sorted before the trap loop, which runs before the PT_TLS early returns, so images without PT_TLS are covered; guest modules go through the same builder. A branch to the first byte still converts and reaches the UD2. --- .../elfpatcher/src/windows/WindowsTlsBuilder.cpp | 11 +++++++---- .../relinker/tests/test_tls_function_coverage.py | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp index b78a010b40..f923bafd63 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp @@ -221,8 +221,14 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co } report(9000); - for (const auto& [rva, length] : gsTraps) + std::sort(branchTargets.begin(), branchTargets.end()); + branchTargets.erase(std::unique(branchTargets.begin(), branchTargets.end()), branchTargets.end()); + for (const auto& [rva, length] : gsTraps) { + const auto target = std::upper_bound(branchTargets.begin(), branchTargets.end(), rva); + if (target != branchTargets.end() && *target < rva + length) + throw Domain::RelinkerException("Branch enters a guest GS instruction", *target); trapInstruction(sections, rva, length); + } if (tls == nullptr) { if (!accesses.empty()) throw Domain::RelinkerException("Guest TLS access without PT_TLS"); @@ -274,9 +280,6 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co code.PatchBranch(skipCallback, code.GetRva()); code.Emit({0xc3}); - std::sort(branchTargets.begin(), branchTargets.end()); - branchTargets.erase(std::unique(branchTargets.begin(), branchTargets.end()), branchTargets.end()); - std::uint64_t patched = 0; for (const auto& access : accesses) { if (patched % 256 == 0) report(9000 + 900 * patched / accesses.size()); diff --git a/core/relinker/relinker/tests/test_tls_function_coverage.py b/core/relinker/relinker/tests/test_tls_function_coverage.py index 4687c088bf..8c78d020bd 100644 --- a/core/relinker/relinker/tests/test_tls_function_coverage.py +++ b/core/relinker/relinker/tests/test_tls_function_coverage.py @@ -777,6 +777,8 @@ def main(): conflicting[232:344] = unwind[232:344] struct.pack_into("