diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp index 9a4d99657..39de6fdf6 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp @@ -59,15 +59,19 @@ constexpr std::size_t RelaEntSize = 24; void collectRelativeEntries( const std::vector& elfBytes, const std::vector& loads, - VirtualAddress tableVaddr, + std::uint64_t tableAddress, std::uint64_t tableSize, + bool sceTable, + std::uint64_t sceTableBase, std::unordered_map& out ) { - if (tableSize == 0) return; + if (tableSize == 0 && !sceTable) return; if (tableSize % RelaEntSize != 0) throw RelinkerException("Invalid relocation table size", tableSize); - std::uint64_t tableFileOff = vaddrToFileOffset(loads, tableVaddr, tableSize); + if (sceTable && tableAddress > std::numeric_limits::max() - sceTableBase) + throw RelinkerException("Relocation table file offset overflow", tableAddress); + std::uint64_t tableFileOff = sceTable ? sceTableBase + tableAddress : vaddrToFileOffset(loads, tableAddress, tableSize); if (tableFileOff > elfBytes.size()) throw RelinkerException("Relocation table out of bounds", tableFileOff); if (tableSize > elfBytes.size() - tableFileOff) @@ -128,12 +132,21 @@ std::unique_ptr BuildRelativeRelocationIndex( std::uint16_t phCount = read16(elfBytes, 56); if (phEntSize < 56) throw RelinkerException("ELF program header entry too small"); + if (phOff > elfBytes.size() || phCount > (elfBytes.size() - phOff) / phEntSize) + throw RelinkerException("Program header out of bounds"); std::vector loads; + std::uint64_t sceTableBase = 0; + bool hasSceDynlibData = false; for (std::uint16_t i = 0; i < phCount; ++i) { std::size_t phPos = static_cast(phOff) + i * phEntSize; if (phPos + 56 > elfBytes.size()) throw RelinkerException("Program header out of bounds"); std::uint32_t type = read32(elfBytes, phPos); + if (type == PT_SCE_DYNLIBDATA) { + if (hasSceDynlibData) throw RelinkerException("Multiple PT_SCE_DYNLIBDATA segments", phPos); + hasSceDynlibData = true; + sceTableBase = read64(elfBytes, phPos + 8); + } if (type != PT_LOAD) continue; LoadSegment seg; seg.fileOffset = read64(elfBytes, phPos + 8); @@ -159,6 +172,8 @@ std::unique_ptr BuildRelativeRelocationIndex( std::uint64_t relaSz = 0; VirtualAddress jmprelVa = 0; std::uint64_t pltrelsz = 0; + bool relaSce = false; + bool jmprelSce = false; for (std::uint64_t off = 0; off <= segSz && segSz - off >= 16; off += 16) { std::size_t pos = static_cast(segOff + off); @@ -166,14 +181,20 @@ std::unique_ptr BuildRelativeRelocationIndex( std::uint64_t val = read64(elfBytes, pos + 8); if (tag == DT_NULL) break; - if (tag == DT_RELA || tag == DT_OS_RELA) relaVa = val; + if (tag == DT_RELA || tag == DT_OS_RELA) { + relaVa = val; + relaSce = tag == DT_OS_RELA; + } if (tag == DT_RELASZ || tag == DT_OS_RELASZ) relaSz = val; - if (tag == DT_JMPREL || tag == DT_OS_JMPREL) jmprelVa = val; + if (tag == DT_JMPREL || tag == DT_OS_JMPREL) { + jmprelVa = val; + jmprelSce = tag == DT_OS_JMPREL; + } if (tag == DT_PLTRELSZ || tag == DT_OS_PLTRELSZ) pltrelsz = val; } - collectRelativeEntries(elfBytes, loads, relaVa, relaSz, targets); - collectRelativeEntries(elfBytes, loads, jmprelVa, pltrelsz, targets); + collectRelativeEntries(elfBytes, loads, relaVa, relaSz, relaSce, sceTableBase, targets); + collectRelativeEntries(elfBytes, loads, jmprelVa, pltrelsz, jmprelSce, sceTableBase, targets); break; } diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp index 9561c3dc9..cf163ac55 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp @@ -25,6 +25,11 @@ public: requireRange(segment.Offset, segment.FileSize); if (segment.MemorySize > std::numeric_limits::max() - segment.MappedAddress) throw RelinkerException("Strict filter: segment address overflow", segment.MappedAddress); + if (segment.Type == 0x61000000) { + if (hasSceDynlibData) throw RelinkerException("Strict filter: multiple PT_SCE_DYNLIBDATA segments", segment.Offset); + hasSceDynlibData = true; + sceTableBase = segment.Offset; + } if (segment.Type == 1 && segment.FileSize > segment.MemorySize) throw RelinkerException("Strict filter: invalid load segment size", segment.Offset); if (segment.Type != 2) continue; @@ -136,6 +141,8 @@ private: ElfReader reader; std::vector headers; std::map tags; + std::uint64_t sceTableBase = 0; + bool hasSceDynlibData = false; void requireRange(std::uint64_t offset, std::uint64_t size) const { if (offset > bytes.size() || size > bytes.size() - offset) throw RelinkerException("Strict filter: file range out of bounds", offset); @@ -168,7 +175,10 @@ private: std::optional table(std::int64_t standardTag, std::int64_t osTag) const { const auto address = value(standardTag, osTag); if (!address) return std::nullopt; - return tags.contains(osTag) ? *address : fileOffset(*address, 0); + if (!tags.contains(osTag)) return fileOffset(*address, 0); + if (*address > std::numeric_limits::max() - sceTableBase) + throw RelinkerException("Strict filter: SCE table file offset overflow", *address); + return sceTableBase + *address; } }; diff --git a/core/relinker/relinker/tests/StrictNidFilterTests.cpp b/core/relinker/relinker/tests/StrictNidFilterTests.cpp index 284bc616d..f62ec3f09 100644 --- a/core/relinker/relinker/tests/StrictNidFilterTests.cpp +++ b/core/relinker/relinker/tests/StrictNidFilterTests.cpp @@ -9,7 +9,9 @@ #include #include #include +#include #include +#include namespace { @@ -465,6 +467,117 @@ void relativeRelocationTableBounds() { requireFailure([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(overflowDynamicOffset); }, "PT_DYNAMIC offset near UINT64_MAX was accepted"); } +void sceDynlibDataOffsets() { + auto input = fixture(); + emit(input, 0, {0xC3}); + importThunk(input, 16, 0x2000); + auto bytes = elfFixture(input); + bytes.resize(0xA00); + write(bytes, 56, 4); + write(bytes, 176, 2); + write(bytes, 180, 6); + write(bytes, 184, 0x300); + write(bytes, 192, 0x2000); + write(bytes, 208, 0x40); + write(bytes, 216, 0x40); + write(bytes, 224, 8); + write(bytes, 232, 0x61000000); + write(bytes, 236, 4); + write(bytes, 240, 0x500); + write(bytes, 264, 0x500); + write(bytes, 272, 0x500); + write(bytes, 280, 0x10); + write(bytes, 0x300, 0x6100002F); + write(bytes, 0x308, 0x400); + write(bytes, 0x310, 0x61000031); + write(bytes, 0x318, 24); + write(bytes, 0x320, 0x61000033); + write(bytes, 0x328, 24); + write(bytes, 0x900, 0x2040); + write(bytes, 0x908, 8); + write(bytes, 0x910, 0x1010); + + std::string failures; + try { + const auto index = Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(bytes); + if (index->TargetOfSlot(0x2040) != 0x1010) failures += "SCE relative relocation was not indexed; "; + } catch (const std::exception& error) { + failures += std::string("SCE relative relocation index: ") + error.what() + "; "; + } + try { + const std::vector references = {{"live", {}, 6, 0x900, 0x2000, 0}}; + const auto filtered = Relinker::MakeStrictUnusedNidFilter()->Filter(references, bytes, input.Text, input.TextVaddr); + if (filtered.size() != 1) failures += "SCE relocation target did not keep its import"; + } catch (const std::exception& error) { + failures += std::string("SCE strict filter: ") + error.what(); + } + require(failures.empty(), failures.c_str()); + + auto noSegment = bytes; + write(noSegment, 232, 0x6FFFFF01); + write(noSegment, 0x308, 0x900); + require(Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(noSegment)->TargetOfSlot(0x2040) == 0x1010, + "SCE relocation without PT_SCE_DYNLIBDATA was not read at its absolute file offset"); + const std::vector references = {{"live", {}, 6, 0x900, 0x2000, 0}}; + require(Relinker::MakeStrictUnusedNidFilter()->Filter(references, noSegment, input.Text, input.TextVaddr).size() == 1, + "Strict filter lost an SCE relocation without PT_SCE_DYNLIBDATA"); + + auto jmprel = bytes; + write(jmprel, 208, 0x60); + write(jmprel, 216, 0x60); + write(jmprel, 0x318, 0); + write(jmprel, 0x330, 0x61000029); + write(jmprel, 0x338, 0x430); + write(jmprel, 0x340, 0x6100002D); + write(jmprel, 0x348, 24); + write(jmprel, 0x930, 0x2050); + write(jmprel, 0x938, 8); + write(jmprel, 0x940, 0x1010); + require(Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(jmprel)->TargetOfSlot(0x2050) == 0x1010, + "SCE JMPREL table was not read at its segment-relative file offset"); + write(jmprel, 0x338, 0x4F0); + requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(jmprel); }, + "SCE JMPREL table past the end of the file was accepted"); + + auto emptyJmprel = jmprel; + write(emptyJmprel, 0x338, 0x430); + write(emptyJmprel, 0x348, 0); + require(!Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyJmprel)->TargetOfSlot(0x2050), + "Valid empty SCE JMPREL table was rejected or parsed"); + write(emptyJmprel, 0x338, 0x501); + requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyJmprel); }, + "Empty SCE JMPREL table past the file was accepted"); + write(emptyJmprel, 0x338, std::numeric_limits::max()); + requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyJmprel); }, + "Empty SCE JMPREL table with overflowing offset was accepted"); + + auto emptyRela = bytes; + write(emptyRela, 0x318, 0); + require(!Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyRela)->TargetOfSlot(0x2040), + "Valid empty SCE RELA table was rejected or parsed"); + write(emptyRela, 0x308, 0x501); + requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyRela); }, + "Empty SCE RELA table past the file was accepted"); + write(emptyRela, 0x308, std::numeric_limits::max()); + requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyRela); }, + "Empty SCE RELA table with overflowing offset was accepted"); + + auto overflow = bytes; + write(overflow, 0x308, std::numeric_limits::max()); + requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(overflow); }, + "SCE relocation file offset overflow was accepted"); + requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, overflow, input.Text, input.TextVaddr); }, + "Strict filter accepted an overflowing SCE table offset"); + + auto duplicate = bytes; + write(duplicate, 56, 5); + write(duplicate, 288, 0x61000000); + requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(duplicate); }, + "Duplicate PT_SCE_DYNLIBDATA segments were accepted"); + requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, duplicate, input.Text, input.TextVaddr); }, + "Strict filter accepted duplicate PT_SCE_DYNLIBDATA segments"); +} + } int main() { @@ -486,6 +599,7 @@ int main() { filterCallbackDataImports(); filterAndPltCompaction(); relativeRelocationTableBounds(); + sceDynlibDataOffsets(); std::cout << "Strict NID filter tests passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 2124dbe5e..9d00c9a6f 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -477,7 +477,6 @@ Throughout the project, every function at every stage either **does exactly what - [Image loads](../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) (shader recompiler) - an image_load reads a sampled binding, also of an image the shader stores to, so in a primitive-ordered pixel shader it misses an earlier overlapping primitive's image_store in the same draw. - [PA_SC_CONSERVATIVE_RASTERIZATION_CNTL](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 0x6001 uses Vulkan overestimation on devices that add at most 1/256 pixel and keep degenerate triangles; the console's uncertainty region and degenerate handling are unmeasured. A value with `OVER_RAST_ENABLE` and `UNDER_RAST_ENABLE` clear is decoded as disabled even when `UNCERTAINTY_REGION_MODE` or the `OUTER_`/`INNER_UNCERTAINTY_EDGERULE_OVERRIDE` bits (19:16) are set, as PPSA01474 writes 0xe6000; that these fields have no effect without over- or underestimation is assumed from their names, not measured. - The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test. -- The [relinker pipeline](../../core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp) reads `DT_SCE_*` table addresses as offsets into the `PT_SCE_DYNLIBDATA` segment, where PS4 executables (CUSA00900) keep them; without that segment they are still read as file offsets. It rejects multiple `PT_SCE_DYNLIBDATA` segments, matching the single segment in the [documented PS4 ELF layout](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/wiki/PS4-ELF-Specification). The `unused-filter=1` and `unused-filter=2` analyses do not: [RelativeRelocationIndex](../../core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp) reads `DT_SCE_RELA` and `DT_SCE_JMPREL` as virtual addresses and [StrictNidFilter](../../core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp) reads the SCE tables as file offsets, so they misread a PS4 executable's tables. - The libc [SSE4a trap emulation](../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. - [getifaddrs](../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists the host's IPv4 and IPv6 addresses in FreeBSD's `ifaddrs` and sockaddr layouts. FreeBSD's per-interface AF_LINK entries (MAC, index, MTU) and their `if_data` are not produced, so interfaces without an address are omitted. Names are the host's (Linux `lo`, `eth0`; Windows NDIS names such as `ethernet_32769`, or `if` when one does not fit 15 bytes), not the console's. A Linux point-to-point address without a peer loses `IFF_POINTOPOINT`, and an IPv4 broadcast address the host omits is computed from the netmask. On Windows the flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST`, derived from the adapter: PPP and tunnel adapters get neither `IFF_BROADCAST` nor `IFF_POINTOPOINT` because their peer address is not reported, and an address whose prefix length the host reports as unspecified gets a host mask. - [gethostname](../../core/libs/prx/libc/src/Hostname.cpp) (libc) reports the fixed guest name `AnyPS5` without querying the host machine name. Buffer copying, null queries and `ENAMETOOLONG` follow [FreeBSD gethostname](https://github.com/freebsd/freebsd-src/blob/main/lib/libc/gen/gethostname.c); the console's configurable hostname and its integration with `sysctl` and name resolution are unverified and not implemented.