fix(relinker): tolerate unwind ranges that split an instruction

A static branch into a skipped function tail is accepted when an
instruction decodes there within its executable segment, so a range that
ends inside an instruction no longer stops code analysis.

The Windows TLS rewriter skips a non-FS segment-prefixed instruction that
starts inside an earlier collected instruction: it is an alternative
decoding from a range that begins mid-instruction, not a TLS access.
This commit is contained in:
Gjon Gojcevic 2026-10-07 19:37:40 -04:00 committed by Adrià Franch
parent b1c5f10df8
commit 90dcd6256c
3 changed files with 28 additions and 4 deletions

View File

@ -106,11 +106,14 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
}
if (header.Type != 1 || (header.Flags & 1) == 0)
continue;
std::uint64_t decodedEnd = 0;
for (auto instruction = instructions.lower_bound(header.MappedAddress); instruction != instructions.end() && *instruction - header.MappedAddress < header.FileSize; ++instruction) {
const auto offset = *instruction - header.MappedAddress;
const auto* bytes = source.data() + header.Offset + offset;
const auto info = decoder.DecodeInstruction(bytes, header.FileSize - offset);
const auto rva = image.GetRva(header.MappedAddress + offset, info.Length);
const bool insidePrevious = *instruction < decodedEnd;
decodedEnd = std::max(decodedEnd, *instruction + info.Length);
if (info.HasBranchTarget && !info.HasRipRelativeDisp) {
const auto target = static_cast<std::int64_t>(rva) + static_cast<std::int64_t>(info.Length) + info.BranchDisp;
@ -118,7 +121,7 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
branchTargets.insert(static_cast<std::uint32_t>(target));
}
if (info.SegmentPrefix != 0) {
if (info.SegmentPrefix != 0 && !(insidePrevious && info.SegmentPrefix != 0x64)) {
const auto position = info.OpcodeOffset;
bool hasOperandSizePrefix = false;
bool supportedPrefixes = info.SegmentPrefix == 0x64;

View File

@ -247,9 +247,21 @@ std::set<Domain::VirtualAddress> CodeInstructionCollector::Collect(const std::ve
position = next;
}
}
const auto decodesInSegment = [&](std::uint64_t address) {
for (const auto& header : headers) {
if (header.Type != 1 || (header.Flags & 1) == 0 || address < header.MappedAddress || address - header.MappedAddress >= header.FileSize) continue;
const auto offset = address - header.MappedAddress;
try {
return decoder.DecodeInstruction(bytes.data() + header.Offset + offset, header.FileSize - offset).Length != 0;
} catch (const Codegen::CodegenException&) {
return false;
}
}
return false;
};
for (const auto target : staticTargets) {
for (const auto& [skipBegin, skipEnd] : skipped) {
if (skipBegin <= target && target < skipEnd)
if (skipBegin <= target && target < skipEnd && !decodesInSegment(target))
throw Domain::RelinkerException("Code analysis: branch into skipped range tail", target);
}
}

View File

@ -382,8 +382,8 @@ def pe_bytes_at(pe, rva, size):
raise AssertionError(f"Unmapped PE RVA {rva:#x}")
def add_alias(image, size):
struct.pack_into("<IBBHQQ", image, 0x650, 0, 0x12, 0, 1, 0x1200, size)
def add_alias(image, size, begin=0x1200):
struct.pack_into("<IBBHQQ", image, 0x650, 0, 0x12, 0, 1, begin, size)
struct.pack_into("<IIIII", image, 0x680, 1, 3, 1, 0, 0)
return image
@ -438,6 +438,15 @@ def main():
alias_tail[0x1258:0x1260] = bytes.fromhex("64 8b 04 25 28 00 00 00")
convert("symbol-alias-unreachable-tls-tail", alias_tail,
"Unsupported Windows guest TLS instruction", error_offset=0x1258)
for metadata in ("unwind", "symbol"):
convert(f"branch-into-cut-tail-{metadata}",
make_image("register", metadata, 0x50, TLS_LOAD + bytes.fromhex("eb 00 8b 40 f0 c3")))
convert("branch-into-undecodable-tail",
make_image("register", "unwind", 0x50, TLS_LOAD + bytes.fromhex("eb 01 66 0f 78 c0 01 02 c3")),
"Code analysis: branch into skipped range tail", error_offset=0x124f)
split_body = TLS_LOAD + bytes.fromhex("eb 00 8b 40 f0 48 b9 00 00 00 65 2e 62 69 6e") + b"\xc3" * 8
convert("function-begins-inside-instruction",
add_alias(make_image("register", "symbol", 0x55, split_body), 0x0c, 0x1256))
overlapping = make_image("register", "unwind")
overlapping[0x1200:0x1205] = b"\xe9" + struct.pack("<i", 0x1245 - 0x1205)
convert("overlapping-entry", overlapping, "Code analysis: overlapping instruction boundaries")