diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 5e7e207cb9f..89de29e8bef 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -564,6 +564,8 @@ if(BUILD_TESTING) set_tests_properties(macos_tls_alu PROPERTIES TIMEOUT 60) add_test(NAME linux_load_alignment COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_load_alignment.py $) + add_test(NAME linux_load_bounds COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_load_bounds.py $) add_test(NAME linux_eh_frame_header COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_eh_frame_header.py $) add_test(NAME linux_dynamic_strings COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp b/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp index 2c406e732be..f9f88f60476 100644 --- a/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp +++ b/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp @@ -22,6 +22,8 @@ namespace Elfpatcher::Linux { namespace { constexpr std::uint64_t kLinuxUserAddressEnd = 0x800000000000; +constexpr std::uint64_t kLazyPltGotBytes = 24; +constexpr std::uint64_t kPltGotEntryBytes = 8; std::string Hex(const std::uint64_t value) { std::ostringstream text; @@ -101,6 +103,19 @@ void RequireFileRange(const std::vector& buf, const Domain::Progra throw Domain::RelinkerException("PT_LOAD at " + Hex(load.MappedAddress) + " has file bytes outside the input"); } +void RequireLoaded(const std::vector& headers, const std::uint64_t address, const std::uint64_t size, const std::uint32_t flags, const std::string& what) { + for (const auto& header : headers) + if (header.Type == PT_LOAD && (header.Flags == 0 || (header.Flags & flags) == flags) && address >= header.MappedAddress && address - header.MappedAddress < header.MemorySize && size <= header.MemorySize - (address - header.MappedAddress)) + return; + const auto range = size > 1 ? " (" + std::to_string(size) + " bytes) does not fit inside " : std::string(" is not inside "); + throw Domain::RelinkerException(what + " " + Hex(address) + range + ((flags & PF_X) != 0 ? "an executable" : "a") + " PT_LOAD segment"); +} + +std::uint64_t RelocationWidth(const std::vector& relocations, const std::size_t entry) { + const auto type = static_cast(Io::ReadU64(relocations, entry + 8)); + return type == R_X86_64_PC32 || type == R_X86_64_32 ? 4 : 8; +} + void SeparateSharedLoadPages(std::vector& buf, std::vector& headers) { std::vector loads; for (std::size_t index = 0; index < headers.size(); ++index) { @@ -356,6 +371,12 @@ std::vector LinuxElfPatcher::Patch( throw Domain::RelinkerException("--trace-imports requires libc.prx among the libraries"); if (_fixedBase) RequireOnlyDependencies(dynSection.DynamicSegmentData); + RequireLoaded(originalHeaders, Io::ReadU64(sourceElf, kEhdrEntryOffset), 1, PF_X, "e_entry"); + for (const auto& [relocationTable, name] : {std::pair{&dynSection.RelaData, "RELA r_offset"}, std::pair{&dynSection.RelaPltData, "JMPREL r_offset"}}) + for (std::size_t entry = 0; entry < relocationTable->size(); entry += kRelaEntrySize) + RequireLoaded(originalHeaders, Io::ReadU64(*relocationTable, entry), RelocationWidth(*relocationTable, entry), 0, name); + if (!dynSection.RelaPltData.empty()) + RequireLoaded(originalHeaders, originalPltGotVaddr, lazyBinding ? kLazyPltGotBytes : kPltGotEntryBytes, 0, "DT_PLTGOT"); const auto traced = _traceImports ? TracedImports(dynSection) : std::vector{}; const std::uint64_t base = _fixedBase.value_or(0); const auto address = [this, base](const std::uint64_t vaddr) { diff --git a/core/relinker/relinker/tests/test_linux_eh_frame_header.py b/core/relinker/relinker/tests/test_linux_eh_frame_header.py index bb34e1e852d..cfe4cf6053f 100644 --- a/core/relinker/relinker/tests/test_linux_eh_frame_header.py +++ b/core/relinker/relinker/tests/test_linux_eh_frame_header.py @@ -18,7 +18,7 @@ def fixture(header_count, with_frame=True): image = bytearray(0x8000) image[:16] = b"\x7fELF\x02\x01\x01" + bytes(9) struct.pack_into(" 1;" in result.stdout, (name, result.stdout) + return True assert result.returncode == 0 and output.exists(), (name, result.returncode, result.stdout, result.stderr) registry = json.loads(output.with_suffix(".registry.json").read_text()) kept = len(registry)