ELFFile: Skip embedded PS-X EXE header segments

Ignore the metadata PT_LOAD segment emitted by PS1 linker scripts and
validate loadable segment ranges before invoking the loader callback.

Fixes kernel crash when loading.
pull/3782/head
Stenzek 2 months ago
parent c7f282f184
commit 3bb39f4e33
No known key found for this signature in database

@ -141,6 +141,48 @@ ELFFile::DataArray CreateOutOfRangeProgramHeaderELFData()
return data; return data;
} }
ELFFile::DataArray CreateELFDataWithEmbeddedPSXExecutableHeader()
{
auto data = CreateValidELFData();
data.resize(0x400);
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
// Make the first loadable segment contain an embedded PS-X EXE header, as produced by some PS1 linker scripts.
phdr[0].p_offset = 0;
phdr[0].p_vaddr = 0x80000000;
phdr[0].p_filesz = 0x100;
phdr[0].p_memsz = 0x100;
phdr[1].p_type = ELFFile::PT_LOAD;
phdr[1].p_offset = 0x100;
phdr[1].p_vaddr = 0x80010000;
phdr[1].p_filesz = 0x100;
phdr[1].p_memsz = 0x100;
ehdr->e_shoff = 0x300;
ehdr->e_shnum = 4;
auto* shdr = reinterpret_cast<ELFFile::Elf32_Shdr*>(data.data() + ehdr->e_shoff);
std::memset(shdr, 0, sizeof(ELFFile::Elf32_Shdr) * ehdr->e_shnum);
shdr[1].sh_name = 1;
shdr[1].sh_type = ELFFile::SHT_PROGBITS;
shdr[1].sh_addr = 0x80010000;
shdr[1].sh_offset = 0x100;
shdr[1].sh_size = 0x100;
shdr[2].sh_name = 7;
shdr[2].sh_type = ELFFile::SHT_STRTAB;
shdr[2].sh_offset = 0x200;
shdr[2].sh_size = 0x30;
shdr[3].sh_name = 17;
shdr[3].sh_type = ELFFile::SHT_PROGBITS;
shdr[3].sh_addr = 0x80000000;
shdr[3].sh_offset = 0;
shdr[3].sh_size = 0x100;
char* strtab = reinterpret_cast<char*>(data.data() + shdr[2].sh_offset);
strcpy(strtab + 17, ".PSX_EXE_Header");
return data;
}
class ELFParserTest : public ::testing::Test class ELFParserTest : public ::testing::Test
{ {
protected: protected:
@ -365,6 +407,24 @@ TEST_F(ELFParserTest, LoadExecutableSections)
EXPECT_EQ(loaded_sections[0].data[0], 0xAAu); // Check first byte of our fake code EXPECT_EQ(loaded_sections[0].data[0], 0xAAu); // Check first byte of our fake code
} }
TEST_F(ELFParserTest, SkipsEmbeddedPSXExecutableHeader)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(CreateELFDataWithEmbeddedPSXExecutableHeader(), &error));
std::vector<u32> loaded_addresses;
ASSERT_TRUE(elf.LoadExecutableSections(
[&loaded_addresses](std::span<const u8>, u32 dest_vaddr, u32, Error*) {
loaded_addresses.push_back(dest_vaddr);
return true;
},
&error));
ASSERT_EQ(loaded_addresses.size(), 1u);
EXPECT_EQ(loaded_addresses[0], 0x80010000u);
}
TEST_F(ELFParserTest, MissingEntryPoint) TEST_F(ELFParserTest, MissingEntryPoint)
{ {
ELFFile elf; ELFFile elf;
@ -390,3 +450,33 @@ TEST_F(ELFParserTest, OutOfRangeProgramHeader)
EXPECT_FALSE(result); EXPECT_FALSE(result);
EXPECT_TRUE(error.IsValid()); EXPECT_TRUE(error.IsValid());
} }
TEST_F(ELFParserTest, RejectsProgramHeaderWithSmallerMemorySize)
{
auto data = CreateValidELFData();
const auto* ehdr = reinterpret_cast<const ELFFile::Elf32_Ehdr*>(data.data());
auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
phdr[0].p_memsz = phdr[0].p_filesz - 1;
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(data), &error));
EXPECT_FALSE(elf.LoadExecutableSections([](std::span<const u8>, u32, u32, Error*) { return true; }, &error));
EXPECT_TRUE(error.IsValid());
}
TEST_F(ELFParserTest, RejectsProgramHeaderAddressOverflow)
{
auto data = CreateValidELFData();
const auto* ehdr = reinterpret_cast<const ELFFile::Elf32_Ehdr*>(data.data());
auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
phdr[0].p_vaddr = 0xFFFFFFF0;
phdr[0].p_memsz = 0x20;
phdr[0].p_filesz = 0x20;
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(data), &error));
EXPECT_FALSE(elf.LoadExecutableSections([](std::span<const u8>, u32, u32, Error*) { return true; }, &error));
EXPECT_TRUE(error.IsValid());
}

@ -7,10 +7,13 @@
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include <limits>
LOG_CHANNEL(FileLoader); LOG_CHANNEL(FileLoader);
static constexpr const u8 EXPECTED_ELF_HEADER[4] = {'\177', 'E', 'L', 'F'}; static constexpr const u8 EXPECTED_ELF_HEADER[4] = {'\177', 'E', 'L', 'F'};
static constexpr s64 MAX_ELF_FILE_SIZE = 32 * 1024 * 1024; static constexpr s64 MAX_ELF_FILE_SIZE = 32 * 1024 * 1024;
static constexpr std::string_view PSX_EXE_HEADER_SECTION_NAME = ".PSX_EXE_Header";
ELFFile::ELFFile() = default; ELFFile::ELFFile() = default;
@ -175,10 +178,47 @@ bool ELFFile::LoadExecutableSections(const LoadExecutableSectionCallback& callba
continue; continue;
} }
// Some PS1 linker scripts place an embedded PS-X EXE header in a PT_LOAD segment at the start of the ELF. This
// segment is metadata for a standalone PS-X EXE and must not overwrite the already-running kernel when sideloading.
if (phdr->p_offset == 0)
{
bool contains_psx_exe_header = false;
for (u32 section_index = 0; section_index < GetSectionCount(); section_index++)
{
const Elf32_Shdr* shdr = GetSectionHeader(section_index);
if (!shdr || GetSectionName(*shdr) != PSX_EXE_HEADER_SECTION_NAME || shdr->sh_offset >= phdr->p_filesz ||
shdr->sh_size > (phdr->p_filesz - shdr->sh_offset))
{
continue;
}
contains_psx_exe_header = true;
break;
}
if (contains_psx_exe_header)
{
DEV_LOG("Skipping embedded PS-X EXE header segment at 0x{:08X}", phdr->p_vaddr);
continue;
}
}
if (phdr->p_memsz < phdr->p_filesz)
{
Error::SetStringFmt(error, "Program header {} has a memory size smaller than its file size.", i);
return false;
}
if (phdr->p_memsz > 0 && phdr->p_vaddr > (std::numeric_limits<u32>::max() - (phdr->p_memsz - 1)))
{
Error::SetStringFmt(error, "Program header {} address range overflows.", i);
return false;
}
std::span<const u8> data; std::span<const u8> data;
if (phdr->p_filesz > 0) if (phdr->p_filesz > 0)
{ {
if ((phdr->p_offset + static_cast<size_t>(phdr->p_filesz)) > m_data.size()) if (phdr->p_offset > m_data.size() || phdr->p_filesz > (m_data.size() - phdr->p_offset))
{ {
Error::SetStringFmt(error, "Program header {} is out of file range {} {} {}", i, phdr->p_offset, phdr->p_filesz, Error::SetStringFmt(error, "Program header {} is out of file range {} {} {}", i, phdr->p_offset, phdr->p_filesz,
m_data.size()); m_data.size());
@ -188,10 +228,10 @@ bool ELFFile::LoadExecutableSections(const LoadExecutableSectionCallback& callba
data = m_data.cspan(phdr->p_offset, phdr->p_filesz); data = m_data.cspan(phdr->p_offset, phdr->p_filesz);
} }
if (!callback(data, phdr->p_vaddr, std::max(phdr->p_memsz, phdr->p_filesz), error)) if (!callback(data, phdr->p_vaddr, phdr->p_memsz, error))
return false; return false;
loaded_entry |= (entry >= phdr->p_vaddr && entry < (phdr->p_vaddr + phdr->p_memsz)); loaded_entry |= (entry >= phdr->p_vaddr && (entry - phdr->p_vaddr) < phdr->p_memsz);
} }
if (!loaded_entry) if (!loaded_entry)

Loading…
Cancel
Save