mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
fix(relinker): accept PT_DYNAMIC data after DT_NULL
The dynamic-segment parsers refused a PT_DYNAMIC whose p_filesz is not a multiple of 16 before reading it. elfldr v0.27 from ps5-payload-dev declares 0x1468 bytes, the tail of its PT_LOAD, with DT_NULL at entry 21; FreeBSD rtld and glibc stop at DT_NULL and never read p_filesz. Read whole 16-byte entries within the declared size, stop at DT_NULL, and keep refusing a segment that ends without one, in the executable reader, the code analysis, the relative relocation index, the strict NID filter and the guest module reader. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
60465afd3a
commit
68f5474263
7 files changed
+57
-19
No files matched your search
@@ -71,11 +71,11 @@ std::set<Domain::VirtualAddress> CodeInstructionCollector::Collect(const std::ve
|
||||
for (const auto& header : headers) {
|
||||
if (header.Type == 0x61000000) sceBase = header.Offset;
|
||||
if (header.Type != 2) continue;
|
||||
if (hasDynamic || header.FileSize % 16 != 0) throw Domain::RelinkerException("Code analysis: invalid dynamic segment");
|
||||
if (hasDynamic) throw Domain::RelinkerException("Code analysis: invalid dynamic segment");
|
||||
hasDynamic = true;
|
||||
range(header.Offset, header.FileSize);
|
||||
bool terminated = false;
|
||||
for (std::uint64_t offset = header.Offset; offset < header.Offset + header.FileSize; offset += 16) {
|
||||
for (std::uint64_t offset = header.Offset; header.Offset + header.FileSize - offset >= 16; offset += 16) {
|
||||
const auto tag = Io::ReadU64(bytes, offset);
|
||||
if (tag == 0) { terminated = true; break; }
|
||||
const auto value = Io::ReadU64(bytes, offset + 8);
|
||||
|
||||
@@ -175,7 +175,6 @@ std::unique_ptr<IRelativeRelocationIndex> BuildRelativeRelocationIndex(
|
||||
std::uint64_t segOff = read64(elfBytes, phPos + 8);
|
||||
std::uint64_t segSz = read64(elfBytes, phPos + 32);
|
||||
if (segOff > elfBytes.size() || segSz > elfBytes.size() - segOff) throw RelinkerException("PT_DYNAMIC segment out of bounds");
|
||||
if (segSz % 16 != 0) throw RelinkerException("Invalid dynamic segment size", segOff);
|
||||
|
||||
std::optional<VirtualAddress> relaVa;
|
||||
std::optional<std::uint64_t> relaSz;
|
||||
|
||||
@@ -34,9 +34,8 @@ public:
|
||||
throw RelinkerException("Strict filter: invalid load segment size", segment.Offset);
|
||||
if (segment.Type != 2) continue;
|
||||
if (!tags.empty()) throw RelinkerException("Strict filter: multiple dynamic segments");
|
||||
if (segment.FileSize % 16 != 0) throw RelinkerException("Strict filter: truncated dynamic segment", segment.Offset);
|
||||
bool terminated = false;
|
||||
for (std::uint64_t offset = 0; offset < segment.FileSize; offset += 16) {
|
||||
for (std::uint64_t offset = 0; segment.FileSize - offset >= 16; offset += 16) {
|
||||
const auto tag = read<std::int64_t>(segment.Offset + offset);
|
||||
if (tag == 0) {
|
||||
terminated = true;
|
||||
|
||||
@@ -61,14 +61,14 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
|
||||
tls = &header;
|
||||
}
|
||||
}
|
||||
if (loads.empty() || dynamic == nullptr || dynamic->FileSize % 16 != 0) fail("Missing or invalid ELF load/dynamic segments");
|
||||
if (loads.empty() || dynamic == nullptr) fail("Missing or invalid ELF load/dynamic segments");
|
||||
std::map<std::uint64_t, std::uint64_t> tags;
|
||||
std::vector<std::uint64_t> needed;
|
||||
std::vector<std::uint64_t> moduleImports;
|
||||
std::vector<std::uint64_t> ps4ModuleImports;
|
||||
std::vector<std::uint64_t> moduleExports;
|
||||
bool terminated = false;
|
||||
for (std::uint64_t offset = dynamic->Offset; offset < dynamic->Offset + dynamic->FileSize; offset += 16) {
|
||||
for (std::uint64_t offset = dynamic->Offset; dynamic->Offset + dynamic->FileSize - offset >= 16; offset += 16) {
|
||||
const auto tag = Io::ReadU64(bytes, offset);
|
||||
const auto value = Io::ReadU64(bytes, offset + 8);
|
||||
if (tag == 0) { terminated = true; break; }
|
||||
|
||||
@@ -270,15 +270,11 @@ std::vector<DynamicTag> ElfReader::ReadDynamicTags(const ProgramHeader& dynamicH
|
||||
if (!_rangeFits(dynamicHeader.Offset, dynamicHeader.FileSize, _fileBuffer.size())) {
|
||||
throw RelinkerException("Dynamic segment out of bounds", dynamicHeader.Offset);
|
||||
}
|
||||
if (dynamicHeader.FileSize % 16 != 0) {
|
||||
throw RelinkerException("Invalid dynamic segment size", dynamicHeader.Offset);
|
||||
}
|
||||
|
||||
std::vector<DynamicTag> tags;
|
||||
FileByteOffset offset = dynamicHeader.Offset;
|
||||
const FileByteOffset end = dynamicHeader.Offset + dynamicHeader.FileSize;
|
||||
|
||||
while (offset < end) {
|
||||
while (end - offset >= 16) {
|
||||
DynamicTag tag;
|
||||
tag.Tag = static_cast<std::int64_t>(_readU64At(offset));
|
||||
tag.Value = _readU64At(offset + 0x08);
|
||||
|
||||
@@ -75,7 +75,7 @@ def main():
|
||||
convert(f"{index}-module-name", None, "Unsupported Windows guest filename", flags, module=guest_fixture(PLAIN_SITE))
|
||||
module = guest_fixture(PLAIN_SITE)
|
||||
struct.pack_into("<QQ", module, 176 + 32, 17, 17)
|
||||
convert(f"{index}-module", None, "Missing or invalid ELF load/dynamic segments", flags, module=module)
|
||||
convert(f"{index}-module", None, "Unterminated dynamic segment", flags, module=module)
|
||||
convert(f"{index}-module-syscall", None, "Forbidden syscall instruction", flags, module=guest_fixture(b"\x0f\x05" + b"\x90" * 4), module_name="broken.prx")
|
||||
if failures:
|
||||
raise AssertionError(failures)
|
||||
|
||||
@@ -4,6 +4,8 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_guest_intel_trampolines import main_fixture
|
||||
from test_guest_needed_modules import module_with_needed
|
||||
from test_optional_plt import fixture as optional_plt_fixture
|
||||
|
||||
|
||||
@@ -16,17 +18,24 @@ def fixture():
|
||||
return image
|
||||
|
||||
|
||||
def strict_fixture():
|
||||
image = optional_plt_fixture([(4, 0x6a0)])
|
||||
struct.pack_into("<I", image, 64 + 4, 5)
|
||||
struct.pack_into("<IIII", image, 0x6a0, 1, 1, 0, 0)
|
||||
return image
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
failures = []
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-dynamic-segment-") as directory:
|
||||
work = Path(directory)
|
||||
|
||||
def convert(name, image, error=None):
|
||||
def convert(name, image, error=None, modes=([], ["--windows"], ["--macos"], ["unused-filter=1"])):
|
||||
source = work / (name + ".elf")
|
||||
source.write_bytes(image)
|
||||
for mode in ([], ["--windows"]):
|
||||
output = work / (name + (".exe" if mode else ".out"))
|
||||
for mode in modes:
|
||||
output = work / (name + "." + ("-".join(flag.lstrip("-") for flag in mode) or "linux"))
|
||||
result = subprocess.run([str(relinker), "--skip-sce-module", *mode, str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
valid = result.returncode == 0 and output.exists() if error is None else (
|
||||
@@ -60,9 +69,14 @@ def main():
|
||||
struct.pack_into("<Q", malformed, 120 + 32, declared_size)
|
||||
convert(name, malformed, "Dynamic segment out of bounds")
|
||||
|
||||
partial = fixture()
|
||||
struct.pack_into("<Q", partial, 120 + 32, size + 1)
|
||||
convert("partial-entry", partial, "Invalid dynamic segment size")
|
||||
trailing = fixture()
|
||||
struct.pack_into("<QQ", trailing, 120 + 32, size + 0x18, size + 0x18)
|
||||
trailing[0x400 + size:0x400 + size + 0x18] = bytes(range(1, 0x19))
|
||||
convert("trailing-bytes-after-null", trailing)
|
||||
|
||||
cut = fixture()
|
||||
struct.pack_into("<QQ", cut, 120 + 32, size - 8, size - 8)
|
||||
convert("partial-terminator", cut, "Unterminated dynamic segment")
|
||||
|
||||
missing_null = fixture()
|
||||
struct.pack_into("<QQ", missing_null, 120 + 32, size - 16, size - 16)
|
||||
@@ -72,6 +86,36 @@ def main():
|
||||
struct.pack_into("<QQ", empty, 120 + 32, 0, 0)
|
||||
convert("empty", empty, "Unterminated dynamic segment")
|
||||
|
||||
strict_modes = (["unused-filter=2"], ["--windows", "unused-filter=2"], ["--macos", "unused-filter=2"])
|
||||
strict_size = struct.unpack_from("<Q", strict_fixture(), 120 + 32)[0]
|
||||
strict = strict_fixture()
|
||||
struct.pack_into("<QQ", strict, 120 + 32, strict_size + 0x18, strict_size + 0x18)
|
||||
strict[0x400 + strict_size:0x400 + strict_size + 0x18] = bytes(range(1, 0x19))
|
||||
convert("strict-trailing-bytes-after-null", strict, modes=strict_modes)
|
||||
|
||||
strict = strict_fixture()
|
||||
struct.pack_into("<QQ", strict, 120 + 32, strict_size - 8, strict_size - 8)
|
||||
convert("strict-partial-terminator", strict, "Unterminated dynamic segment", strict_modes)
|
||||
|
||||
for name, delta, error in (("module-trailing-bytes", 8, None),
|
||||
("module-partial-terminator", -8, "Unterminated dynamic segment")):
|
||||
module = module_with_needed((b"libkernel.prx",), (0,))
|
||||
declared = struct.unpack_from("<Q", module, 176 + 32)[0] + delta
|
||||
struct.pack_into("<QQ", module, 176 + 32, declared, declared)
|
||||
for mode in ([], ["--windows"], ["--macos"]):
|
||||
case = work / (name + "-" + (mode[0].lstrip("-") if mode else "linux"))
|
||||
(case / "prx").mkdir(parents=True)
|
||||
(case / "prx" / "consumer.prx").write_bytes(module)
|
||||
source = case / "input.elf"
|
||||
source.write_bytes(main_fixture())
|
||||
output = case / "output"
|
||||
result = subprocess.run([str(relinker), *mode, str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
valid = result.returncode == 0 and output.exists() if error is None else (
|
||||
result.returncode == 2 and error in result.stderr and not output.exists())
|
||||
if not valid:
|
||||
failures.append((name, mode, result.returncode, result.stdout, result.stderr))
|
||||
|
||||
if failures:
|
||||
raise AssertionError(failures)
|
||||
print("Dynamic segment tests passed")
|
||||
|
||||
Reference in new issue
Block a user