Merge pull request #6410 from DavidSamir/fix/relinker-macos-tls-overlapping-decode

fix(relinker): skip prefixed decodings inside an earlier instruction in the macOS TLS scan
This commit is contained in:
Adrià authored and GitHub committed 2026-10-19 01:58:54 +02:00
commit ea41ee4031
3 files changed
+57 -1

No files matched your search

+3
View File
@@ -523,6 +523,9 @@ if(BUILD_TESTING)
set_tests_properties(needed_libraries PROPERTIES TIMEOUT 30)
add_test(NAME macos_output COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_output.py $<TARGET_FILE:relinker>)
add_test(NAME macos_tls_overlap COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_tls_overlap.py $<TARGET_FILE:relinker>)
set_tests_properties(macos_tls_overlap PROPERTIES TIMEOUT 60)
add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $<TARGET_FILE:relinker>)
add_library(guest_module_args_host SHARED elfpatcher/tests/GuestModuleArgsHost.cpp)
@@ -218,16 +218,19 @@ TlsScan ScanTls(const std::vector<std::uint8_t>& source, const std::vector<Domai
scan.Tls = &header;
}
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;
if (target >= 0 && target <= std::numeric_limits<std::uint32_t>::max()) scan.BranchTargets.insert(static_cast<std::uint32_t>(target));
}
if (info.SegmentPrefix == 0) continue;
if (info.SegmentPrefix == 0 || (insidePrevious && info.SegmentPrefix != 0x64)) continue;
const auto position = info.OpcodeOffset;
bool hasOperandSizePrefix = false;
bool supportedPrefixes = info.SegmentPrefix == 0x64;
@@ -0,0 +1,50 @@
"""Check the macOS TLS scan against decodings that start inside another instruction."""
from pathlib import Path
import subprocess
import sys
import tempfile
from test_macos_output import MachO
from test_tls_function_coverage import TLS_LOAD, add_alias, make_image
def convert(relinker, directory, name, image):
source = Path(directory) / f"{name}.elf"
output = Path(directory) / name
source.write_bytes(bytes(image))
result = subprocess.run([str(relinker), "--macos", "--skip-sce-module", str(source), str(output)],
capture_output=True, text=True, timeout=60)
return result, output
def check_split_function(relinker, directory):
immediate = bytes.fromhex("48 b9 00 00 00 65 2e 62 69 6e")
body = TLS_LOAD + bytes.fromhex("eb 00 8b 40 f0") + immediate + b"\xc3" * 8
image = add_alias(make_image("register", "symbol", 0x55, body), 0x0c, 0x1256)
result, output = convert(relinker, directory, "function-begins-inside-instruction", image)
if result.returncode != 0:
raise SystemExit(f"function-begins-inside-instruction: relinker failed: {result.stdout}{result.stderr}")
data = output.read_bytes()
code = b"".join(data[lo:hi] for lo, hi in MachO(data).executable_ranges())
if immediate not in code:
raise SystemExit("function-begins-inside-instruction: the instruction around the GS-prefixed decoding changed")
def check_gs_access_refused(relinker, directory):
image = make_image("register", "unwind", body=bytes.fromhex("65 48 8b 04 25 28 00 00 00") + b"\xc3")
result, output = convert(relinker, directory, "gs-access", image)
message = result.stdout + result.stderr
if result.returncode != 2 or "Unsupported macOS guest TLS instruction" not in message or output.exists():
raise SystemExit(f"gs-access: a GS access at an instruction start was not refused: {message}")
def main():
relinker = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="anyps5-macos-tls-overlap-") as directory:
check_split_function(relinker, directory)
check_gs_access_refused(relinker, directory)
print("macOS TLS overlap checks passed")
if __name__ == "__main__":
main()