Merge pull request #4717 from Ondra97/codex/fix-relro-unused-filter
fix(relinker): map standalone RELRO in unused import filtering
This commit is contained in:
commit
f5cda839ec
|
|
@ -1,4 +1,5 @@
|
|||
#include <relinker/analysis/UnusedNidFilter/IEntryPointCollector.hpp>
|
||||
#include <relinker/parsing/ElfReader.hpp>
|
||||
#include <elfpatcher/general/ElfConstants.hpp>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
|
@ -75,11 +76,9 @@ public:
|
|||
if (phEntSize < 56) throw RelinkerException("ELF program header entry too small");
|
||||
|
||||
std::vector<LoadSegment> loads;
|
||||
for (std::uint16_t i = 0; i < phCount; ++i) {
|
||||
std::size_t phPos = static_cast<std::size_t>(phOff) + i * phEntSize;
|
||||
if (phPos + 56 > elfBytes.size()) throw RelinkerException("Program header out of bounds");
|
||||
if (read32(elfBytes, phPos) != PT_LOAD) continue;
|
||||
loads.push_back({read64(elfBytes, phPos + 16), read64(elfBytes, phPos + 8), read64(elfBytes, phPos + 32)});
|
||||
for (const auto& header : ElfReader(elfBytes).ReadProgramHeaders()) {
|
||||
if (header.Type != PT_LOAD) continue;
|
||||
loads.push_back({header.MappedAddress, header.Offset, header.FileSize});
|
||||
}
|
||||
|
||||
for (std::uint16_t i = 0; i < phCount; ++i) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <relinker/analysis/UnusedNidFilter/IRelativeRelocationIndex.hpp>
|
||||
#include <relinker/parsing/ElfReader.hpp>
|
||||
#include <elfpatcher/general/ElfConstants.hpp>
|
||||
#include <unordered_map>
|
||||
#include <cstring>
|
||||
|
|
@ -147,12 +148,10 @@ std::unique_ptr<IRelativeRelocationIndex> BuildRelativeRelocationIndex(
|
|||
hasSceDynlibData = true;
|
||||
sceTableBase = read64(elfBytes, phPos + 8);
|
||||
}
|
||||
if (type != PT_LOAD) continue;
|
||||
LoadSegment seg;
|
||||
seg.fileOffset = read64(elfBytes, phPos + 8);
|
||||
seg.vaddr = read64(elfBytes, phPos + 16);
|
||||
seg.fileSize = read64(elfBytes, phPos + 32);
|
||||
loads.push_back(seg);
|
||||
}
|
||||
for (const auto& header : ElfReader(elfBytes).ReadProgramHeaders()) {
|
||||
if (header.Type != PT_LOAD) continue;
|
||||
loads.push_back({header.MappedAddress, header.Offset, header.FileSize});
|
||||
}
|
||||
|
||||
std::unordered_map<VirtualAddress, VirtualAddress> targets;
|
||||
|
|
|
|||
|
|
@ -437,7 +437,9 @@ void relativeRelocationTableBounds() {
|
|||
bytes[6] = 1;
|
||||
write<std::uint16_t>(bytes, 16, 3);
|
||||
write<std::uint16_t>(bytes, 18, 62);
|
||||
write<std::uint32_t>(bytes, 20, 1);
|
||||
write<std::uint64_t>(bytes, 32, 64);
|
||||
write<std::uint16_t>(bytes, 52, 64);
|
||||
write<std::uint16_t>(bytes, 54, 56);
|
||||
write<std::uint16_t>(bytes, 56, 2);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import struct
|
||||
|
|
@ -41,6 +42,29 @@ def relink(relinker, work, name, image, options):
|
|||
return result, output
|
||||
|
||||
|
||||
def filtered_fixture(extra, lifecycle):
|
||||
image = fixture(extra)
|
||||
if not lifecycle:
|
||||
image[0x1100:0x1118] = image[0x700:0x718]
|
||||
struct.pack_into("<Q", image, 0x400 + 4 * 16 + 8, 0x1100)
|
||||
struct.pack_into("<qQqQ", image, 0x470, 0x6100003f, 24, 0, 0)
|
||||
struct.pack_into("<QQ", image, 64 + (1 + len(extra)) * 56 + 32, 9 * 16, 9 * 16)
|
||||
return image
|
||||
image[0x200] = 0xc3
|
||||
image[0x210:0x218] = b"\x48\x8b\x05" + struct.pack("<i", 0x1300 - 0x217) + b"\xc3"
|
||||
image[0x600:0x605] = b"\0foo\0"
|
||||
struct.pack_into("<IBBHQQ", image, 0x638, 1, 0x12, 0, 0, 0, 0)
|
||||
struct.pack_into("<QQq", image, 0x700, 0x1200, 8, 0x210)
|
||||
struct.pack_into("<QQq", image, 0x718, 0x1300, (1 << 32) | 6, 0)
|
||||
tags = [(5, 0x600), (10, 5), (6, 0x620), (11, 24), (7, 0x700), (8, 48), (9, 24),
|
||||
(25, 0x1200), (27, 8), (0x6100003f, 48), (0, 0)]
|
||||
dynamic = 64 + (1 + len(extra)) * 56
|
||||
struct.pack_into("<QQ", image, dynamic + 32, len(tags) * 16, len(tags) * 16)
|
||||
for index, tag in enumerate(tags):
|
||||
struct.pack_into("<qQ", image, 0x400 + index * 16, *tag)
|
||||
return image
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
standalone = [(PT_SCE_RELRO, 4, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000)]
|
||||
|
|
@ -61,6 +85,14 @@ def main():
|
|||
if target == "windows" and os.name == "nt":
|
||||
run = subprocess.run([str(output)], capture_output=True, timeout=20)
|
||||
assert run.returncode == 42, (name, run.returncode, run.stderr)
|
||||
for lifecycle in (False, True):
|
||||
for level in (0, 1, 2):
|
||||
case = f"{name}-filter-{level}-{'lifecycle' if lifecycle else 'relocations'}"
|
||||
result, output = relink(relinker, work, case, filtered_fixture(extra, lifecycle),
|
||||
["--registry", f"unused-filter={level}"])
|
||||
assert result.returncode == 0, (case, result.stdout, result.stderr)
|
||||
registry = json.loads(output.with_suffix(".registry.json").read_text())
|
||||
assert [entry["nid"] for entry in registry] == (["foo"] if lifecycle else []), (case, registry)
|
||||
for name, load in (("tail", (PT_LOAD, 6, 0x1000, 0x1000, 0x800, 0x800, 0x1000)),
|
||||
("head", (PT_LOAD, 6, 0x1800, 0x1800, 0x800, 0x800, 0x1000))):
|
||||
for options in ([], ["--windows"]):
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
- [SDK memory-management syscall traps](../../../core/relinker/relinker/src/analysis/SyscallScanner.cpp) under `--trap-sdk-syscall-stubs` recognize three exact instruction sequences from [ps5-payload-dev SDK libc/mman.c](https://github.com/ps5-payload-dev/sdk/blob/12cf3dda40/libc/mman.c), observed in Nuvio Maxd 0.5.15 (PPSA99288): syscall 74 and the two syscall 477 paths, with their argument preparation and carry-flag error handling. Matching requires a decoded instruction boundary and the complete sequence inside the scanned code range. Other SDK/compiler output remains rejected. The matched syscall becomes `UD2`; this does not implement mmap or mprotect and a call still faults.
|
||||
- `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead.
|
||||
- The length-changing path of the [instruction rewriter](../../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length.
|
||||
- A `PT_SCE_RELRO` segment that no `PT_LOAD` covers, the PS4 layout, is [loaded](../../../core/relinker/relinker/src/parsing/ElfReader.cpp) as a read-write `PT_LOAD` and stays writable after relocation; FreeBSD rtld makes `PT_GNU_RELRO` read-only once relocation is done, and what the console does with `PT_SCE_RELRO` is not checked. The `unused-filter=1` and `unused-filter=2` readers parse the program headers themselves and do not load it.
|
||||
- A `PT_SCE_RELRO` segment that no `PT_LOAD` covers, the PS4 layout, is [loaded](../../../core/relinker/relinker/src/parsing/ElfReader.cpp) as a read-write `PT_LOAD` and stays writable after relocation; FreeBSD rtld makes `PT_GNU_RELRO` read-only once relocation is done, and what the console does with `PT_SCE_RELRO` is not checked.
|
||||
- In executables with a `PT_SCE_DYNLIBDATA` segment, the PS4 layout, the executable segment also holds read-only data after the code (CUSA00900: 37 MiB after its last function). The [relinker pipeline](../../../core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp) therefore scans for syscalls and import call sites only up to the end of the PLT that follows the last function of the unwind table; code after the PLT without an unwind entry is not scanned. `--to-intel` and the `unused-filter=1` and `unused-filter=2` analyses are not limited this way.
|
||||
- In executables and guest modules with a `PT_SCE_DYNLIBDATA` segment, the [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp) of the Windows and macOS outputs follows code only from the end of `PT_INTERP` to the end of the PLT after the last function of the unwind table, the end the pipeline's scans above use; a guest module with PLT relocations and no PLT after its last unwind function fails as the executable does. Code after that PLT is not analyzed: a TLS access in it is not rewritten, an entry point or pointer into it is ignored, and a function symbol or init/fini array entry pointing into it fails the conversion.
|
||||
- A relocation against a dynamic symbol entry that is all zero, like the null symbol, takes the value 0 plus its addend, as the gABI defines for `STN_UNDEF` ([pipeline](../../../core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp)). The Linux output imports such an entry as an unnamed weak symbol, which the host loader resolves to 0; the Windows and macOS outputs write the value into the slot ([relocation builder](../../../core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp)). What the console's loader does with such an entry is not measured. OliSe Player (PPSA01153) has seven of them, and its code calls through those slots only when they are not 0 ([#4437](https://github.com/boykopovar/AnyPS5/issues/4437)).
|
||||
|
|
|
|||
Loading…
Reference in New Issue