diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 4e8e1a4ad..5c4c4396f 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -345,7 +345,7 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_plt.py $) add_test(NAME empty_tls COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $) - set(tlsCoverageGroups metadata padding register_loads displacement_bounds alu_execution register_address_loads rejected) + set(tlsCoverageGroups metadata padding register_loads displacement_bounds alu_execution register_address_loads moved_branch rejected) foreach(register IN ITEMS 0 1 2 3 5 6 7 8 9 10 11 12 13 14 15) list(APPEND tlsCoverageGroups displacement_r${register}) endforeach() diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp index df1ce0fea..e37c631dc 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp @@ -23,6 +23,8 @@ struct MovedInstruction { std::uint32_t Rva; std::vector Bytes; std::optional RipDisplacement; + std::optional BranchTarget; + std::size_t BranchDisplacementOffset = 0; }; struct TlsAccess { @@ -145,9 +147,28 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co if (length >= header.FileSize - offset) throw Domain::RelinkerException("Short guest TLS instruction ends its segment", header.Offset + offset); const auto next = decoder.DecodeInstruction(bytes + length, header.FileSize - offset - length); - if (next.FlowKind != Codegen::ControlFlowKind::Sequential || next.HasBranchTarget || next.SegmentPrefix != 0) + 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)); + 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); - moved.push_back({CheckedRva(rva + length), std::vector(bytes + length, bytes + length + next.Length), next.HasRipRelativeDisp ? std::optional(next.RipRelativeDispOffset) : std::nullopt}); + std::vector movedBytes(bytes + length, bytes + length + next.Length); + std::optional branchTarget; + std::size_t branchDisplacementOffset = 0; + if (relativeBranch) { + const auto target = static_cast(rva) + static_cast(length + next.Length) + next.BranchDisp; + if (target < 0 || target > std::numeric_limits::max()) + throw Domain::RelinkerException("Short guest TLS instruction is followed by a branch beyond the address space", header.Offset + offset + length); + branchTarget = static_cast(target); + if (!next.IsTwoByteOpcode && next.Opcode != 0xe9) { + movedBytes = next.Opcode == 0xeb ? std::vector{0xe9} : std::vector{0x0f, static_cast(0x80 | (next.Opcode & 0xf))}; + branchDisplacementOffset = movedBytes.size(); + movedBytes.insert(movedBytes.end(), 4, 0); + } else { + branchDisplacementOffset = next.Length - 4; + } + } + moved.push_back({CheckedRva(rva + length), std::move(movedBytes), next.HasRipRelativeDisp ? std::optional(next.RipRelativeDispOffset) : std::nullopt, branchTarget, branchDisplacementOffset}); length += next.Length; } accesses.push_back({rva, header.Offset + offset, length, false, 0, addressRegister, 0, isAlu ? bytes[position] : std::uint8_t{0}, *address, wide, std::move(moved)}); @@ -357,6 +378,12 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co throw Domain::RelinkerException("Moved RIP-relative instruction exceeds rel32 range from its TLS stub", access.FileOffset); Io::WriteU32(bytes, *instruction.RipDisplacement, static_cast(static_cast(displacement))); } + if (instruction.BranchTarget) { + const auto displacement = static_cast(*instruction.BranchTarget) - (static_cast(code.GetRva()) + static_cast(bytes.size())); + if (displacement < std::numeric_limits::min() || displacement > std::numeric_limits::max()) + throw Domain::RelinkerException("Moved branch exceeds rel32 range from its TLS stub", access.FileOffset); + Io::WriteU32(bytes, instruction.BranchDisplacementOffset, static_cast(static_cast(displacement))); + } for (const auto byte : bytes) code.Emit({byte}); } diff --git a/core/relinker/relinker/tests/test_tls_function_coverage.py b/core/relinker/relinker/tests/test_tls_function_coverage.py index 5abf3e195..8061ee163 100644 --- a/core/relinker/relinker/tests/test_tls_function_coverage.py +++ b/core/relinker/relinker/tests/test_tls_function_coverage.py @@ -511,7 +511,7 @@ def add_alias(image, size, begin=0x1200): def main(): groups = ("metadata", "padding", "register_loads", "displacement_bounds", "alu_execution", - "register_address_loads", "rejected") + "register_address_loads", "moved_branch", "rejected") groups += tuple(f"displacement_r{register}" for register in range(16) if register != 4) groups += tuple(f"alu_{name}" for name in ALU_READ) parser = argparse.ArgumentParser(description="Run Windows guest TLS function coverage fixtures") @@ -699,12 +699,35 @@ def main(): stub = pe_bytes_at(pe, 0x10000 + address + 5 + struct.unpack_from("= 0, (name, stub.hex()) + displacement = struct.unpack_from("