mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-30 12:25:49 +00:00
Outputs whose input carries no PT_GNU_STACK get one appended with PF_R|PF_W and alignment 16 so the kernel does not fall back to an executable stack marking. RequiredProgramHeaderCount and WriteLayout now derive from one shared header list, so the reserved slot and the written header cannot disagree at any PhNum boundary, when a free input slot should be reused, or when the RELRO coverage guard reduces the kept count. Space exhaustion beyond the 16-bit header limit fails loudly instead of silently skipping the header.
155 lines
9.7 KiB
C++
155 lines
9.7 KiB
C++
#include <elfpatcher/general/ElfConstants.hpp>
|
|
#include <elfpatcher/general/ProgramHeaderLayoutBuilder.hpp>
|
|
#include <io/ByteWriter.hpp>
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
class KeepAll : public Elfpatcher::ISegmentFilter {
|
|
public:
|
|
bool ShouldSkip(const Domain::ProgramHeader&) const override { return false; }
|
|
};
|
|
|
|
static void Require(bool ok, const char* what) { if (!ok) throw std::runtime_error(std::string("program layout: ") + what); }
|
|
|
|
static Domain::ProgramHeader Load(std::uint64_t vaddr, std::uint64_t memorySize, std::uint64_t fileSize, std::uint32_t flags) {
|
|
Domain::ProgramHeader load{};
|
|
load.Type = 1; load.Flags = flags; load.Offset = vaddr; load.MappedAddress = vaddr;
|
|
load.PhysicalAddress = vaddr; load.FileSize = fileSize; load.MemorySize = memorySize; load.Alignment = 0x1000;
|
|
return load;
|
|
}
|
|
|
|
static std::vector<Domain::ProgramHeader> MappedLoads(const std::vector<std::uint8_t>& buf, std::uint64_t tableOffset, std::uint16_t count) {
|
|
std::vector<Domain::ProgramHeader> loads;
|
|
for (std::uint16_t index = 0; index < count; ++index) {
|
|
const auto* entry = buf.data() + tableOffset + index * 56;
|
|
if (*reinterpret_cast<const std::uint32_t*>(entry) != 1)
|
|
continue;
|
|
Domain::ProgramHeader ph{};
|
|
ph.Flags = *reinterpret_cast<const std::uint32_t*>(entry + 4);
|
|
ph.Offset = *reinterpret_cast<const std::uint64_t*>(entry + 8);
|
|
ph.MappedAddress = *reinterpret_cast<const std::uint64_t*>(entry + 16);
|
|
ph.FileSize = *reinterpret_cast<const std::uint64_t*>(entry + 32);
|
|
ph.MemorySize = *reinterpret_cast<const std::uint64_t*>(entry + 40);
|
|
ph.Alignment = *reinterpret_cast<const std::uint64_t*>(entry + 48);
|
|
loads.push_back(ph);
|
|
}
|
|
return loads;
|
|
}
|
|
|
|
static void RequireExtraBlockStartsOnItsOwnPage(std::vector<Domain::ProgramHeader> loads, std::uint64_t extraFileOffset) {
|
|
std::sort(loads.begin(), loads.end(), [](const Domain::ProgramHeader& left, const Domain::ProgramHeader& right) {
|
|
return left.MappedAddress < right.MappedAddress;
|
|
});
|
|
std::size_t extraIndex = loads.size();
|
|
for (std::size_t index = 0; index < loads.size(); ++index) {
|
|
Require(loads[index].FileSize <= loads[index].MemorySize, "load file size above its memory size");
|
|
Require(loads[index].MappedAddress % loads[index].Alignment == loads[index].Offset % loads[index].Alignment, "load vaddr and offset differ modulo alignment");
|
|
if (loads[index].Offset == extraFileOffset)
|
|
extraIndex = index;
|
|
}
|
|
Require(extraIndex < loads.size(), "no load at the extra block offset");
|
|
const auto& extraBlock = loads[extraIndex];
|
|
Require(extraBlock.MemorySize >= extraBlock.FileSize, "extra block memory size below its file size");
|
|
for (std::size_t index = 0; index < loads.size(); ++index) {
|
|
if (index == extraIndex)
|
|
continue;
|
|
const auto& other = loads[index];
|
|
if (other.MappedAddress < extraBlock.MappedAddress) {
|
|
Require((other.MappedAddress + other.FileSize - 1) / 0x1000 < extraBlock.MappedAddress / 0x1000, "load below the extra block ends on its page");
|
|
} else {
|
|
Require(other.MappedAddress >= extraBlock.MappedAddress + extraBlock.FileSize, "load above the extra block overlaps it");
|
|
}
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
auto byteWriter = std::make_shared<Io::ByteWriter>();
|
|
Elfpatcher::ProgramHeaderLayoutBuilder builder(std::make_shared<KeepAll>(), byteWriter);
|
|
constexpr auto maximum = std::numeric_limits<std::uint64_t>::max();
|
|
Domain::ProgramHeader load{};
|
|
load.Type = 1; load.MappedAddress = maximum - 3; load.MemorySize = 8;
|
|
bool rejected = false;
|
|
try { builder.ComputeExtraBlockVaddr({load}, 0x1000); } catch (const std::exception&) { rejected = true; }
|
|
Require(rejected, "overflowing load end accepted");
|
|
load.MappedAddress = 0x1000; load.MemorySize = 0x1000;
|
|
Require(builder.ComputeExtraBlockVaddr({load}, 0x1003) == 0x2003, "vaddr for aligned end 0x2000, offset 0x1003");
|
|
load.MemorySize = 0x1800;
|
|
Require(builder.ComputeExtraBlockVaddr({load}, 0x8A00) == 0x3A00, "vaddr for end 0x2800, offset 0x8A00");
|
|
Require(builder.ComputeExtraBlockVaddr({load}, 0x8800) == 0x3800, "vaddr for end 0x2800, offset 0x8800");
|
|
load.MappedAddress = 0x1000; load.MemorySize = 0x1000;
|
|
Require(builder.ComputeExtraBlockVaddr({load}, 0x1000) == 0x2000, "vaddr for aligned end 0x2000, offset 0x1000");
|
|
Elfpatcher::ProgramHeaderLayoutRequest valid{64, 56, 7, {load}, 0x1000, 0x2000, 0x100, 0x1000, 16, 0x1010, 8};
|
|
std::vector<std::uint8_t> output(0x1100, 0xA5);
|
|
Require(builder.WriteLayout(output, valid) == 7, "header count for a valid request");
|
|
for (int kind = 0; kind < 11; ++kind) {
|
|
auto request = valid;
|
|
switch (kind) {
|
|
case 0: request.PhEntSize = 55; break;
|
|
case 1: request.PhOff = maximum - 10; break;
|
|
case 2: request.PhOff = 0x10F0; break;
|
|
case 3: request.ExtraBlockVaddr = maximum - 10; break;
|
|
case 4: request.DynamicSegmentSize = maximum; break;
|
|
case 5: request.InterpSize = maximum; break;
|
|
case 6: request.DynamicSegmentOffset = maximum; break;
|
|
case 7: request.InterpOffset = maximum; break;
|
|
case 8: request.ExtraBlockOffset = maximum; break;
|
|
case 9: request.ExtraBlockSize = 0x1000; break;
|
|
case 10: request.PhOff = 0xF00; request.ExtraBlockVaddr = maximum - 0x1fff; break;
|
|
}
|
|
std::vector<std::uint8_t> bytes(0x1100, 0xA5);
|
|
const auto before = bytes;
|
|
rejected = false;
|
|
try { builder.WriteLayout(bytes, request); } catch (const std::exception&) { rejected = true; }
|
|
Require(rejected && bytes == before, "invalid request accepted or output changed");
|
|
}
|
|
const std::uint16_t tableCount = 3 + 5;
|
|
const auto writeLayout = [&builder, tableCount](const std::vector<Domain::ProgramHeader>& headers, std::uint64_t extraFileOffset, std::uint64_t extraFileSize) {
|
|
const std::uint64_t tableSize = tableCount * 56;
|
|
std::vector<std::uint8_t> bytes(std::max<std::uint64_t>(0xB000, extraFileOffset + tableSize + extraFileSize), 0);
|
|
Elfpatcher::ProgramHeaderLayoutRequest request{extraFileOffset, 56, tableCount, headers, extraFileOffset,
|
|
builder.ComputeExtraBlockVaddr(headers, extraFileOffset), tableSize + extraFileSize,
|
|
extraFileOffset, 0x20, extraFileOffset + 0x20, 8};
|
|
const std::uint16_t written = builder.WriteLayout(bytes, request);
|
|
Require(written == tableCount, "written header count");
|
|
RequireExtraBlockStartsOnItsOwnPage(MappedLoads(bytes, extraFileOffset, written), extraFileOffset);
|
|
};
|
|
writeLayout({Load(0x1000, 0x1800, 0x1800, 6), Load(0x3000, 0x1000, 0x1000, 4)}, 0x8A00, 0x400);
|
|
writeLayout({Load(0x1000, 0x8000, 0x800, 6), Load(0xA000, 0x1000, 0x1000, 4)}, 0x8A00, 0x400);
|
|
writeLayout({Load(0x1000, 0x8000, 0x800, 6), Load(0xA000, 0x1000, 0x1000, 4)}, 0x8000, 0x400);
|
|
writeLayout({Load(0x1000, 0x2000, 0x800, 6), Load(0x3000, 0x3000, 0x1000, 4)}, 0x8A00, 0x400);
|
|
writeLayout({Load(0x1000, 0x1200, 0x1200, 5), Load(0x2400, 0x1000, 0x1000, 6)}, 0x8A00, 0x400);
|
|
writeLayout({Load(0x1000, 0x3000, 0x1000, 5), Load(0x3000, 0x4000, 0x1000, 6)}, 0x8A00, 0x400);
|
|
writeLayout({Load(0x1000, 0x1000, 0x1000, 5), Load(0x2000, 0x1000, 0x0400, 6)}, 0x8800, 0x400);
|
|
const std::vector<Domain::ProgramHeader> singleLoad{Load(0x1000, 0x1800, 0x1800, 6)};
|
|
Require(builder.RequiredProgramHeaderCount(singleLoad) == 7, "required count must reserve a slot for the synthetic stack");
|
|
const auto writeSingle = [&builder, &singleLoad](std::uint16_t slots) {
|
|
std::vector<std::uint8_t> table(0xB000, 0);
|
|
Elfpatcher::ProgramHeaderLayoutRequest request{0x8A00, 56, slots, singleLoad, 0x8A00,
|
|
builder.ComputeExtraBlockVaddr(singleLoad, 0x8A00), static_cast<std::uint64_t>(slots) * 56 + 0x400, 0x8A00, 0x20, 0x8A20, 8};
|
|
const std::uint16_t written = builder.WriteLayout(table, request);
|
|
return std::pair(written, std::move(table));
|
|
};
|
|
const auto [written, table] = writeSingle(builder.RequiredProgramHeaderCount(singleLoad));
|
|
Require(written == 7, "layout must write exactly the required header count");
|
|
std::size_t stacks = 0;
|
|
for (std::uint16_t index = 0; index < written; ++index) {
|
|
const auto entry = 0x8A00 + index * 56;
|
|
if (*reinterpret_cast<const std::uint32_t*>(table.data() + entry) != Elfpatcher::PT_GNU_STACK) continue;
|
|
++stacks;
|
|
Require(*reinterpret_cast<const std::uint32_t*>(table.data() + entry + 4) == (Elfpatcher::PF_R | Elfpatcher::PF_W)
|
|
&& *reinterpret_cast<const std::uint64_t*>(table.data() + entry + 16) == 0
|
|
&& *reinterpret_cast<const std::uint64_t*>(table.data() + entry + 48) == 16,
|
|
"synthetic stack must be RW with alignment 16 and no mapping");
|
|
}
|
|
Require(stacks == 1, "layout must synthesize exactly one PT_GNU_STACK when the input has none");
|
|
bool stackSlotRejected = false;
|
|
try { (void)writeSingle(6); } catch (const std::exception&) { stackSlotRejected = true; }
|
|
Require(stackSlotRejected, "layout must refuse to drop the synthetic stack when its slot is missing");
|
|
std::cout << "21 program layout cases passed\n";
|
|
} catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; }
|
|
}
|