Skip to content

fix(relinker): require the Linux entry, relocation offsets and DT_PLTGOT in PT_LOAD memory - #10402

Open
roboterspinne wants to merge 1 commit into
boykopovar:mainfrom
roboterspinne:fix/relinker-linux-load-bounds
Open

roboterspinne wants to merge 1 commit into
boykopovar:mainfrom
roboterspinne:fix/relinker-linux-load-bounds

Conversation

@roboterspinne

Copy link
Copy Markdown
Contributor

What

The Linux output did not check that e_entry, relocation offsets and DT_PLTGOT lie in PT_LOAD memory: the relink returned 0 and the output crashed at load. Windows and macOS already reject such inputs through WindowsLoadImage (WindowsLoadImage.cpp:57-70, used for relocation targets in WindowsRelocationBuilder.cpp:31 and for DT_PLTGOT in WindowsPePatcher.cpp:43-44 and MacOsMachOPatcher.cpp:65-66).

LinuxElfPatcher::Patch now checks, before both output paths (fixed base and --lazy-binding) and with the same rule as GetRva (the whole range inside one PT_LOAD's MemorySize):

  • e_entry: 1 byte in a PT_LOAD with PF_X.
  • Every RELA and JMPREL r_offset except R_X86_64_NONE, over the bytes ld.so writes: 4 for PC32 and 32, the reference symbol's st_size (at least 1) for COPY, 8 otherwise.
  • DT_PLTGOT when a JMPREL table exists: 8 bytes, or 24 with --lazy-binding, where glibc's elf_machine_runtime_setup writes GOT[1] and GOT[2].

Messages name the field, for example e_entry 0x7ffff000 is not inside an executable PT_LOAD segment and RELA r_offset 0xffc (8 bytes) does not fit inside a PT_LOAD segment. ElfReader turns a PT_SCE_RELRO that no PT_LOAD covers into a PT_LOAD before Patch, so the usual PS5 GOT layout stays accepted. Windows, macOS, guest modules and the Linux output layout are unchanged.

test_linux_load_alignment.py and test_linux_eh_frame_header.py set e_entry to 0x4000, a file offset that their only PT_LOAD maps at virtual address 0. They now use 0 and check the same things as before.

Tested

Ubuntu 24.04 x86_64, glibc 2.39, relinker-only Release build (-DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON); synthetic fixtures only, no title or homebrew.

  • New linux_load_bounds (0.2 s), in the default mode and with --lazy-binding: 17 rejected cases (entry outside a load or in a non-executable one; RELA, JMPREL and COPY offsets outside a load or crossing its end; DT_PLTGOT outside a load; lazy DT_PLTGOT too close to its end) and 23 accepted ones (4-byte types and COPY in the last bytes of a load, offsets in .bss and in an uncovered PT_SCE_RELRO, R_X86_64_NONE outside a load, eager DT_PLTGOT at 0xff0). With LinuxElfPatcher.cpp from main, all 17 rejected cases relink with rc 0.
  • ctest --test-dir build -j2: 241/241 passed.
  • On main, the test_linux_entry_argv fixture with e_entry = 0x7ffff000 and the test_optional_plt fixture with a RELATIVE r_offset = 0x7ffff000 relink with rc 0 and the outputs exit with SIGSEGV. On this branch both fail with rc 2 and the messages above.
  • The open PRs that touch LinuxElfPatcher.cpp or these tests (fix(linux): enter the guest entry on a main stack at the console's address #7532, fix(relinker): reject unsupported extended program header counts #10341, refactor(relinker): remove relocation types validation already rejects #10357) apply on top of this branch with git apply --3way --check.
  • python3 tools/check_conventions.py --base upstream/main with this title and description passed.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code)

…GOT in PT_LOAD memory

The Windows and macOS patchers reject an entry point outside executable
PT_LOAD memory and a relocation offset or DT_PLTGOT outside PT_LOAD memory
through WindowsLoadImage::GetRva. The Linux patcher copied these values
unchecked and reported success; with a bad entry or relocation offset the
output crashed with SIGSEGV at load.

LinuxElfPatcher::Patch now requires e_entry in an executable load and every
non-NONE RELA and JMPREL r_offset and DT_PLTGOT to fit inside one PT_LOAD's
MemorySize, in the fixed-base and --lazy-binding paths. A relocation needs
the bytes ld.so writes: 4 for PC32 and 32, the output symbol's st_size (at
least 1) for COPY, and 8 otherwise. DT_PLTGOT needs 8 bytes with eager
binding and 24 with --lazy-binding, where ld.so writes GOT[1] and GOT[2].
Two test fixtures that set e_entry to a file offset outside their only
PT_LOAD now use the matching virtual address.
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5021 core/relinker/CMakeLists.txt
PR 6044 core/relinker/CMakeLists.txt
PR 7173 core/relinker/CMakeLists.txt
PR 9408 core/relinker/CMakeLists.txt

@roboterspinne

Copy link
Copy Markdown
Contributor Author

Checked manually: this PR adds two lines at 527-530 of core/relinker/CMakeLists.txt. #5021 changes lines 505-515, #9408 lines 542-549, and #6044 rewrites 415-511 and 541 onward. No overlap.

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the Linux PT_LOAD validation and the new synthetic test matrix. The checks are applied before fixed-base and lazy-binding output, skip R_X86_64_NONE, account for PC32/32 and COPY widths, and include BSS/RELRO cases. The existing tests were corrected from file offsets to their mapped virtual entry addresses. Hosted relinker matrix and conventions pass; full product builds are skipped by repository policy. No blocking issue found.

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking correctness issue in the new relocation-width check: RelocationWidth() returns 8 for R_X86_64_32S, but the x86-64 psABI specifies a 32-bit relocation field for that type. This repository accepts R_X86_64_32S in RebasedRelocations(), so a valid 32S relocation whose 4-byte field ends at the PT_LOAD boundary will be rejected as requiring 8 mapped bytes. Please classify R_X86_64_32S as width 4 and add a boundary fixture analogous to abs32-past-load-end that succeeds when the 4-byte field fits. The ABI’s relocation table lists R_X86_64_32S as word32: https://refspecs.linuxfoundation.org/elf/x86_64-SysV-psABI.pdf

@roboterspinne

Copy link
Copy Markdown
Contributor Author

Blocking correctness issue in the new relocation-width check: RelocationWidth() returns 8 for R_X86_64_32S, but the x86-64 psABI specifies a 32-bit relocation field for that type. This repository accepts R_X86_64_32S in RebasedRelocations(), so a valid 32S relocation whose 4-byte field ends at the PT_LOAD boundary will be rejected as requiring 8 mapped bytes. Please classify R_X86_64_32S as width 4 and add a boundary fixture analogous to abs32-past-load-end that succeeds when the 4-byte field fits. The ABI’s relocation table lists R_X86_64_32S as word32: https://refspecs.linuxfoundation.org/elf/x86_64-SysV-psABI.pdf

Thanks for the close look. R_X86_64_32S cannot reach RelocationWidth(): since #9220, ValidationPolicy rejects type 11 for every DT_RELA and DT_JMPREL entry (RelinkerPipeline.cpp:349) before LinuxElfPatcher::Patch runs, and Patch only sees tables built from validated entries. On this branch, a 32S relocation fails with Unsupported relocation type 0xb and leaves no output, whether it points into the last 4 bytes of a load or elsewhere, in the default mode and with --lazy-binding, so a boundary fixture that accepts a 32S relocation cannot be built. test_linux_fixed_base.py (unsupported-type) and test_executable_tls_relocations.py already pin that rejection. I left 32S out on purpose: a case R_X86_64_32S here would be unreachable, and #10357 removes that constant because the fixed-base whitelist's 32S case became unreachable for the same reason. If you still want the width listed, I can add it, but it could not be tested from the command line.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants