fix(relinker): keep the Windows TLS rewrite from moving truncated branches

A short TLS access followed by a near branch with an operand-size prefix
decided the branch was relative from its flow kind and opcode alone, then
re-encoded it to the fall-through address or wrote its rel32 over the prefix
and opcode. Such a branch now counts as one that cannot move.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TFMtUC4L4wMS4eeEqaNSR
This commit is contained in:
oskarlgsandClaude Opus 5.5 committed 2026-10-17 21:55:21 +00:00
1 parent 206aaa56cb
commit aed1ffbae8
2 files changed
+6 -2

No files matched your search

@@ -158,8 +158,8 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
throw Domain::RelinkerException("Short guest TLS instruction ends its segment", header.Offset + offset);
const auto next = decoder.DecodeInstruction(bytes + length, header.FileSize - offset - length);
const bool relativeBranch =
(next.FlowKind == Codegen::ControlFlowKind::ConditionalBranch && (next.IsTwoByteOpcode ? next.Opcode >= 0x80 && next.Opcode <= 0x8f : next.Opcode >= 0x70 && next.Opcode <= 0x7f)) ||
(next.FlowKind == Codegen::ControlFlowKind::UnconditionalJump && !next.HasRipRelativeDisp && !next.IsTwoByteOpcode && (next.Opcode == 0xe9 || next.Opcode == 0xeb));
(next.FlowKind == Codegen::ControlFlowKind::ConditionalBranch && !next.TruncatesBranchTarget && (next.IsTwoByteOpcode ? next.Opcode >= 0x80 && next.Opcode <= 0x8f : next.Opcode >= 0x70 && next.Opcode <= 0x7f)) ||
(next.FlowKind == Codegen::ControlFlowKind::UnconditionalJump && !next.TruncatesBranchTarget && !next.HasRipRelativeDisp && !next.IsTwoByteOpcode && (next.Opcode == 0xe9 || next.Opcode == 0xeb));
if (!relativeBranch && (next.FlowKind != Codegen::ControlFlowKind::Sequential || next.HasBranchTarget || next.SegmentPrefix != 0))
throw Domain::RelinkerException("Short guest TLS instruction is followed by an instruction that cannot move", header.Offset + offset + length);
std::vector<std::uint8_t> movedBytes(bytes + length, bytes + length + next.Length);
@@ -747,6 +747,10 @@ def main():
"short-before-tls": bytes.fromhex("64 48 8b 03 64 48 8b 03 c3"),
"short-alu-before-return": bytes.fromhex("64 48 33 03 c3"),
"short-before-loop": bytes.fromhex("64 48 33 03 e2 00"),
"short-before-word-jcc8": bytes.fromhex("64 48 33 03 66 75 00 c3"),
"short-before-word-jmp8": bytes.fromhex("64 48 33 03 66 eb 00 c3"),
"short-before-word-jmp16": bytes.fromhex("64 48 33 03 66 e9 00 00 c3"),
"short-before-word-jcc16": bytes.fromhex("64 48 33 03 66 0f 85 00 00 c3"),
}.items():
convert(name, make_image("register", "unwind", body=instruction),
"Short guest TLS instruction is followed by an instruction that cannot move", error_offset=0x1244)