From 5b8e3b51b07abb6660610903d5e3aca5c4054847 Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 3 Sep 2026 23:01:51 +0200 Subject: [PATCH] cross-vendor TLS fix: 0x66 prefixes are forced into 64bit call on AMD --- src/loader/jit.h | 13 +++++++------ src/loader/runtimeLinker.cpp | 15 +++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/loader/jit.h b/src/loader/jit.h index 7d5a2f1fc..dc62e8336 100644 --- a/src/loader/jit.h +++ b/src/loader/jit.h @@ -69,21 +69,22 @@ struct Call9 { template void SetFunc(Handler func) { auto func_addr = reinterpret_cast(reinterpret_cast(func)); - auto rip_addr = reinterpret_cast(&code[5]); + auto rip_addr = reinterpret_cast(&code[6]); auto offset64 = func_addr - rip_addr; auto offset32 = static_cast(static_cast(offset64) & 0xffffffffu); - *reinterpret_cast(&code[1]) = offset32; + *reinterpret_cast(&code[2]) = offset32; } - void SetOutputReg(uint8_t reg) { code[7] = 0xc0u | (reg & 7u); } + void SetOutputReg(uint8_t reg) { code[8] = 0xc0u | (reg & 7u); } static uint64_t GetSize() { return 9; } - // call func + // rex.w; call func // mov rax,rax - // nop - uint8_t code[9] = {0xE8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xC0, 0x90}; + // REX.W is required because AMD processors honor a retained 0x66 operand-size + // prefix on a near call and would otherwise execute callw. + uint8_t code[9] = {0x48, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xC0}; }; struct TlsRegStub { diff --git a/src/loader/runtimeLinker.cpp b/src/loader/runtimeLinker.cpp index c1e03d02d..fa4343ba2 100644 --- a/src/loader/runtimeLinker.cpp +++ b/src/loader/runtimeLinker.cpp @@ -1143,7 +1143,8 @@ static void PatchProgram(Program* program, uint64_t address, uint64_t size) { } } - if (!program->elf->IsShared() && program->tls.handler_vaddr != 0) { + if (!program->elf->IsShared() && program->tls.handler_vaddr != 0 && + size >= Jit::Call9::GetSize()) { // Replace: // 66 66 66 // mov , qword ptr fs:[0x00] @@ -1151,7 +1152,8 @@ static void PatchProgram(Program* program, uint64_t address, uint64_t size) { // call // mov ,rax // nop ... - const uint8_t tls_pattern[5] = {0x64, 0x48, 0x8B, 0x00, 0x25}; + const uint8_t tls_pattern[5] = {0x64, 0x48, 0x8B, 0x00, 0x25}; + const uint8_t zero_displacement[4] = {}; EXIT_IF(Jit::Call9::GetSize() != 9); @@ -1173,14 +1175,15 @@ static void PatchProgram(Program* program, uint64_t address, uint64_t size) { const uint8_t modrm = inst_ptr[3]; if (memcmp(inst_ptr, tls_pattern, 3) == 0 && (modrm & 0xc7u) == 0x04u && inst_ptr[4] == tls_pattern[4] && - *reinterpret_cast(inst_ptr + 5) == 0) { - LOGF("Patch tls at addr: [%016" PRIx64 "]\n", - reinterpret_cast(inst_ptr)); + memcmp(inst_ptr + 5, zero_displacement, sizeof(zero_displacement)) == 0) { + LOGF("Patch tls at addr: [%016" PRIx64 "]\n", reinterpret_cast(inst_ptr)); const auto reg = (modrm >> 3u) & 7u; EXIT_NOT_IMPLEMENTED(reg == 4u); - // A raw scan can encounter a 0x66 in the preceding instruction. + // A raw scan can encounter a 0x66 in the preceding instruction, so do not + // overwrite it. Call9 starts with REX.W to neutralize genuine 0x66 + // prefixes on AMD processors (before it could turn E8 into callw 16bit). auto* code = new (inst_ptr) Jit::Call9; code->SetFunc(reg == 0 ? program->tls.handler_vaddr