diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp index 40802530e..dc0099f1d 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp @@ -51,6 +51,7 @@ inline constexpr std::size_t kRelaPltAlignment = 8; inline constexpr std::size_t kGotAlignment = 8; inline constexpr std::size_t kGotReservedSlots = 3; +inline constexpr std::uint64_t kElfHeaderSize = 64; inline constexpr std::uint64_t kDefaultLoadAlignment = 0x1000; inline constexpr std::uint64_t kPhdrHeaderAlignment = 8; inline constexpr std::uint64_t kDynamicHeaderAlignment = 8; diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/IProgramHeaderLayoutBuilder.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/IProgramHeaderLayoutBuilder.hpp index 75b2ae1e1..fcdb2eca0 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/IProgramHeaderLayoutBuilder.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/IProgramHeaderLayoutBuilder.hpp @@ -13,6 +13,8 @@ namespace Elfpatcher { virtual std::uint64_t ComputeExtraBlockVaddr(const std::vector& originalHeaders, std::uint64_t extraBlockOffset) const = 0; + virtual std::size_t RequiredProgramHeaderCount(const std::vector& originalHeaders) const = 0; + virtual std::uint16_t WriteLayout(std::vector& buf, const ProgramHeaderLayoutRequest& request) const = 0; }; diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/ProgramHeaderLayoutBuilder.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/ProgramHeaderLayoutBuilder.hpp index 6c86cf4cf..189256c86 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/ProgramHeaderLayoutBuilder.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/ProgramHeaderLayoutBuilder.hpp @@ -17,6 +17,8 @@ namespace Elfpatcher { std::uint64_t ComputeExtraBlockVaddr(const std::vector& originalHeaders, std::uint64_t extraBlockOffset) const override; + std::size_t RequiredProgramHeaderCount(const std::vector& originalHeaders) const override; + std::uint16_t WriteLayout( std::vector& buf, const ProgramHeaderLayoutRequest& request @@ -32,6 +34,7 @@ namespace Elfpatcher { [[nodiscard]] Domain::ProgramHeader _makeDynamicHeader(std::uint64_t offset, std::uint64_t vaddr, std::uint64_t size) const; [[nodiscard]] Domain::ProgramHeader _makeInterpHeader(std::uint64_t offset, std::uint64_t vaddr, std::uint64_t size) const; [[nodiscard]] std::uint32_t _fixLoadFlags(std::uint32_t originalFlags) const; + [[nodiscard]] std::vector _keptHeaders(const std::vector& originalHeaders) const; void _sortLoadHeaders(std::vector& headers) const; void _writeProgramHeader(std::vector& buf, std::size_t offset, const Domain::ProgramHeader& ph) const; }; diff --git a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp index a5dd1d79f..c02856a8f 100644 --- a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp @@ -132,19 +132,25 @@ std::uint64_t ProgramHeaderLayoutBuilder::ComputeExtraBlockVaddr( return vaddr; } +std::vector ProgramHeaderLayoutBuilder::_keptHeaders(const std::vector& originalHeaders) const { + std::vector kept; + for (const auto& ph : originalHeaders) { + if (!_segmentFilter->ShouldSkip(ph)) + kept.push_back(ph); + } + return kept; +} + +std::size_t ProgramHeaderLayoutBuilder::RequiredProgramHeaderCount(const std::vector& originalHeaders) const { + return _keptHeaders(originalHeaders).size() + kSyntheticProgramHeaderCount; +} + std::uint16_t ProgramHeaderLayoutBuilder::WriteLayout( std::vector& buf, const ProgramHeaderLayoutRequest& request ) const { - std::vector kept; - std::size_t frameCount = 0; - for (const auto& ph : request.OriginalHeaders) { - if (_segmentFilter->ShouldSkip(ph)) - continue; - kept.push_back(ph); - if (ph.Type == PT_GNU_EH_FRAME) - frameCount++; - } + auto kept = _keptHeaders(request.OriginalHeaders); + const auto frameCount = std::count_if(kept.begin(), kept.end(), [](const Domain::ProgramHeader& ph) { return ph.Type == PT_GNU_EH_FRAME; }); const bool keepFrames = kept.size() + kSyntheticProgramHeaderCount <= request.PhNum; if (!keepFrames && frameCount > 0) { @@ -165,9 +171,13 @@ std::uint16_t ProgramHeaderLayoutBuilder::WriteLayout( }; if (request.PhEntSize < 56) throw Domain::RelinkerException("Program header entry is too small"); - const std::uint64_t headerBlockSize = checkedEnd(request.PhOff, static_cast(neededPh) * request.PhEntSize); - if (headerBlockSize > buf.size()) + const std::uint64_t tableEnd = checkedEnd(request.PhOff, static_cast(neededPh) * request.PhEntSize); + if (tableEnd > buf.size()) throw Domain::RelinkerException("Program header table exceeds the output file"); + const bool tableInExtraBlock = request.PhOff >= request.ExtraBlockOffset; + if (tableInExtraBlock && tableEnd - request.ExtraBlockOffset > request.ExtraBlockSize) + throw Domain::RelinkerException("Program header table does not fit within the extra block"); + const std::uint64_t headerBlockSize = tableInExtraBlock ? kElfHeaderSize : tableEnd; if (request.ExtraBlockOffset > buf.size() || request.ExtraBlockSize > buf.size() - request.ExtraBlockOffset) throw Domain::RelinkerException("Extra block exceeds the output file"); std::uint64_t headerBlockAlign = kDefaultLoadAlignment; @@ -194,7 +204,8 @@ std::uint16_t ProgramHeaderLayoutBuilder::WriteLayout( const std::uint64_t interpVaddr = request.ExtraBlockVaddr + (request.InterpOffset - request.ExtraBlockOffset); std::vector layout; - layout.push_back(_makePhdrHeader(request.PhOff, headerBlockVaddr + request.PhOff, static_cast(neededPh) * request.PhEntSize)); + const std::uint64_t phdrVaddr = tableInExtraBlock ? request.ExtraBlockVaddr + (request.PhOff - request.ExtraBlockOffset) : headerBlockVaddr + request.PhOff; + layout.push_back(_makePhdrHeader(request.PhOff, phdrVaddr, static_cast(neededPh) * request.PhEntSize)); layout.push_back(_makeInterpHeader(request.InterpOffset, interpVaddr, request.InterpSize)); for (auto ph : kept) { if (ph.Type == PT_LOAD) diff --git a/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp b/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp index 1db4b2741..4fd051eba 100644 --- a/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp +++ b/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp @@ -187,12 +187,22 @@ std::vector LinuxElfPatcher::Patch( for (char i : kInterp) buf.push_back(static_cast(i)); + std::uint64_t tableOff = phOff; + std::uint16_t tableSlots = phNum; + if (const auto required = _programHeaderLayoutBuilder->RequiredProgramHeaderCount(originalHeaders); required > phNum && required <= std::numeric_limits::max()) { + alignBuf(buf, kPhdrHeaderAlignment); + tableOff = static_cast(buf.size()); + tableSlots = static_cast(required); + buf.resize(buf.size() + required * phEntSize); + _byteWriter->WriteU64(buf, kEhdrPhOffOffset, tableOff); + } + const std::uint64_t extraBlockSize = static_cast(buf.size()) - extraBlockOff; ProgramHeaderLayoutRequest layoutRequest{}; - layoutRequest.PhOff = phOff; + layoutRequest.PhOff = tableOff; layoutRequest.PhEntSize = phEntSize; - layoutRequest.PhNum = phNum; + layoutRequest.PhNum = tableSlots; layoutRequest.OriginalHeaders = originalHeaders; layoutRequest.ExtraBlockOffset = extraBlockOff; layoutRequest.ExtraBlockVaddr = extraBlockVaddr; 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 10abad32d..ee1635a0d 100644 --- a/core/relinker/relinker/tests/test_linux_eh_frame_header.py +++ b/core/relinker/relinker/tests/test_linux_eh_frame_header.py @@ -38,8 +38,9 @@ def program_headers(elf): headers = [struct.unpack_from("= len(image), "The grown program header table must be moved past the input" + assert "PT_GNU_EH_FRAME" not in result.stderr, result.stderr + assert elf[table_end:table_end + 56] == image[table_end:table_end + 56], "Bytes after the input program header table changed" image = fixture(6, with_frame=False) table_end = 64 + 6 * 56 @@ -114,17 +118,14 @@ def main(): assert elf[table_end:table_end + 56] == image[table_end:table_end + 56], "Bytes after the program header table changed" for with_frame in (False, True): - source = Path(directory) / ("insufficient-slots-" + str(with_frame) + ".elf") - output = source.with_suffix(".out") image = fixture(5, with_frame=with_frame) image[64 + 5 * 56:64 + 6 * 56] = bytes([0xa5]) * 56 - source.write_bytes(image) - result = subprocess.run([str(relinker), "--skip-sce-module", str(source), str(output)], capture_output=True, text=True, timeout=20) - assert result.returncode == 2, (result.returncode, result.stdout, result.stderr) - expected = "No free program header slot for PT_GNU_EH_FRAME" if with_frame else "Not enough program header slots: need 6, available 5" - assert expected in result.stderr, result.stderr - assert not output.exists(), "Insufficient program header slots must not produce an executable" - assert source.read_bytes() == image, "Input changed while rejecting insufficient program header slots" + result, elf = relink(relinker, directory, "few-slots-" + str(with_frame), image) + headers = program_headers(elf) + assert len(headers) == (7 if with_frame else 6), headers + assert [header for header in headers if header[0] == PT_GNU_EH_FRAME] == ([EH_FRAME_HEADER] if with_frame else []), headers + assert struct.unpack_from("= len(image), "The grown program header table must be moved past the input" + assert elf[64 + 5 * 56:64 + 6 * 56] == image[64 + 5 * 56:64 + 6 * 56], "Bytes after the input program header table changed" image = fixture(5, with_frame=False) headers = image[64:64 + 4 * 56] diff --git a/core/relinker/relinker/tests/test_linux_entry_argv.py b/core/relinker/relinker/tests/test_linux_entry_argv.py index 61c042681..82c043c19 100644 --- a/core/relinker/relinker/tests/test_linux_entry_argv.py +++ b/core/relinker/relinker/tests/test_linux_entry_argv.py @@ -56,11 +56,21 @@ def main(): result = subprocess.run([str(relinker), "--skip-sce-module", str(source), str(output)], capture_output=True, text=True, timeout=20) assert result.returncode == 0, (result.stdout, result.stderr) + relocated_source = Path(directory) / "relocated-table.elf" + relocated = Path(directory) / "relocated-table.out" + image = argv_fixture() + struct.pack_into("= len(image), "The program header table was not moved" if sys.platform.startswith("linux") and platform.machine() in ("x86_64", "AMD64"): - output.chmod(0o755) - for arguments, expected in ((["Z"], -signal.SIGTRAP), (["Z", "extra"], -signal.SIGILL)): - executed = subprocess.run([str(output), *arguments], capture_output=True, timeout=20) - assert executed.returncode == expected, (arguments, executed.returncode) + for executable in (output, relocated): + executable.chmod(0o755) + for arguments, expected in ((["Z"], -signal.SIGTRAP), (["Z", "extra"], -signal.SIGILL)): + executed = subprocess.run([str(executable), *arguments], capture_output=True, timeout=20) + assert executed.returncode == expected, (executable.name, arguments, executed.returncode) for address, error in ((0x7FF00000, None), (0x80000000, "Entry stub call exceeds rel32 range")): source.write_bytes(far_load_fixture(address)) far = Path(directory) / ("far-%x.elf" % address)