ELF: bound the segment table, and reject ELFs with no loadable segment

segmentVAddr is a 32-entry array, but LoadInto filled it from e_phnum, which is
a u16 - so an ELF declaring 65535 program headers wrote 65535 u32s into it,
straight through the rest of the ElfReader object. The only check standing in
front of that verified the program headers fit in the file, which a ~2MB crafted
PRX satisfies. Rejected up front now: LoadRelocations already ignores segment
numbers at or past the array size, so a module with more than that couldn't be
relocated correctly anyway. Real modules have a handful - PSP_Header::nsegments
is a u8 and no more than 4 are ever used.

Second one from the same loop: with no PT_LOAD segment at all, totalStart stayed
0xFFFFFFFF and totalEnd 0, so totalSize came out as 1 (0 - 0xFFFFFFFF) and the
loader went on to allocate a 1-byte block at 0xFFFFFFFF.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
This commit is contained in:
Henrik RydgårdandClaude Opus 5 committed 2026-08-19 19:19:06 +02:00
1 parent b00e239e41
commit 7e79334c18
1 file changed
+16
+16
View File
@@ -438,6 +438,14 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
}
// e_phnum is a u16, but we can only record the load address of ARRAY_SIZE(segmentVAddr) segments,
// and the relocation code can't refer to segments beyond that either (see LoadRelocations). Real
// PSP modules have a handful - PSP_Header::nsegments is a u8 and no more than 4 are ever used.
if (GetNumSegments() > (int)ARRAY_SIZE(segmentVAddr)) {
ERROR_LOG(Log::Loader, "ELF has %d segments, we support at most %d", GetNumSegments(), (int)ARRAY_SIZE(segmentVAddr));
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
}
// e_ident[EI_VERSION] is ignored
// Should we relocate?
@@ -467,9 +475,11 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
entryPoint = header->e_entry;
u32 totalStart = 0xFFFFFFFF;
u32 totalEnd = 0;
int numLoadSegments = 0;
for (int i = 0; i < header->e_phnum; i++) {
const Elf32_Phdr *p = &segments[i];
if (p->p_type == PT_LOAD) {
numLoadSegments++;
if (p->p_vaddr < totalStart) {
totalStart = p->p_vaddr;
firstSegAlign = p->p_align;
@@ -478,6 +488,12 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
totalEnd = p->p_vaddr + p->p_memsz;
}
}
// Without this, totalStart stays 0xFFFFFFFF and totalEnd 0, so totalSize would come out as 1
// and we'd go on to allocate at 0xFFFFFFFF.
if (numLoadSegments == 0) {
ERROR_LOG(Log::Loader, "ELF has no loadable segments");
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
}
totalSize = totalEnd - totalStart;
// If a load address is specified that's in regular RAM, override kernel module status