Core: validate standalone ELF input ranges
ElfReader trusted table offsets and counts from standalone ELF files. Malformed input could make it read and write past the loaded file buffer. Validate the ELF header, table ranges, segment data, section data, and string-table references before accessing them. Invalid files use the existing executable boot failure path.
This commit is contained in:
1 parent
da1aad5188
commit
24aafdeb47
2 files changed
+95
-29
No files matched your search
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "Core/Boot/ElfReader.h"
|
#include "Core/Boot/ElfReader.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -70,62 +71,106 @@ static void byteswapSection(Elf32_Shdr& sec)
|
|||||||
|
|
||||||
ElfReader::ElfReader(std::vector<u8> buffer) : BootExecutableReader(std::move(buffer))
|
ElfReader::ElfReader(std::vector<u8> buffer) : BootExecutableReader(std::move(buffer))
|
||||||
{
|
{
|
||||||
Initialize(m_bytes.data());
|
m_is_valid = Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
ElfReader::ElfReader(File::IOFile file) : BootExecutableReader(std::move(file))
|
ElfReader::ElfReader(File::IOFile file) : BootExecutableReader(std::move(file))
|
||||||
{
|
{
|
||||||
Initialize(m_bytes.data());
|
m_is_valid = Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
ElfReader::ElfReader(const std::string& filename) : BootExecutableReader(filename)
|
ElfReader::ElfReader(const std::string& filename) : BootExecutableReader(filename)
|
||||||
{
|
{
|
||||||
Initialize(m_bytes.data());
|
m_is_valid = Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
ElfReader::~ElfReader() = default;
|
ElfReader::~ElfReader() = default;
|
||||||
|
|
||||||
void ElfReader::Initialize(u8* ptr)
|
bool ElfReader::Initialize()
|
||||||
{
|
{
|
||||||
base = (char*)ptr;
|
if (m_bytes.size() < sizeof(Elf32_Ehdr))
|
||||||
base32 = (u32*)ptr;
|
return false;
|
||||||
header = (Elf32_Ehdr*)ptr;
|
|
||||||
|
base = reinterpret_cast<char*>(m_bytes.data());
|
||||||
|
base32 = reinterpret_cast<u32*>(m_bytes.data());
|
||||||
|
header = reinterpret_cast<Elf32_Ehdr*>(m_bytes.data());
|
||||||
|
if (header->e_ident[EI_MAG0] != ELFMAG0 || header->e_ident[EI_MAG1] != ELFMAG1 ||
|
||||||
|
header->e_ident[EI_MAG2] != ELFMAG2 || header->e_ident[EI_MAG3] != ELFMAG3 ||
|
||||||
|
header->e_ident[EI_CLASS] != ELFCLASS32 || header->e_ident[EI_DATA] != ELFDATA2MSB)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
byteswapHeader(*header);
|
byteswapHeader(*header);
|
||||||
|
|
||||||
segments = (Elf32_Phdr*)(base + header->e_phoff);
|
const auto is_range_valid = [this](size_t offset, size_t size) {
|
||||||
sections = (Elf32_Shdr*)(base + header->e_shoff);
|
return offset <= m_bytes.size() && size <= m_bytes.size() - offset;
|
||||||
|
};
|
||||||
|
if (header->e_ehsize != sizeof(Elf32_Ehdr) ||
|
||||||
|
(header->e_phnum != 0 && header->e_phentsize != sizeof(Elf32_Phdr)) ||
|
||||||
|
(header->e_shnum != 0 && header->e_shentsize != sizeof(Elf32_Shdr)) ||
|
||||||
|
!is_range_valid(header->e_phoff, sizeof(Elf32_Phdr) * header->e_phnum) ||
|
||||||
|
!is_range_valid(header->e_shoff, sizeof(Elf32_Shdr) * header->e_shnum) ||
|
||||||
|
(header->e_shstrndx != SHN_UNDEF && header->e_shstrndx >= header->e_shnum))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
segments = reinterpret_cast<Elf32_Phdr*>(base + header->e_phoff);
|
||||||
|
sections = reinterpret_cast<Elf32_Shdr*>(base + header->e_shoff);
|
||||||
|
|
||||||
for (int i = 0; i < GetNumSegments(); i++)
|
for (int i = 0; i < GetNumSegments(); i++)
|
||||||
{
|
{
|
||||||
byteswapSegment(segments[i]);
|
byteswapSegment(segments[i]);
|
||||||
|
if (!is_range_valid(segments[i].p_offset, segments[i].p_filesz) ||
|
||||||
|
segments[i].p_filesz > segments[i].p_memsz)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < GetNumSections(); i++)
|
for (int i = 0; i < GetNumSections(); i++)
|
||||||
{
|
{
|
||||||
byteswapSection(sections[i]);
|
byteswapSection(sections[i]);
|
||||||
|
if (sections[i].sh_type != SHT_NOBITS &&
|
||||||
|
!is_range_valid(sections[i].sh_offset, sections[i].sh_size))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
entryPoint = header->e_entry;
|
entryPoint = header->e_entry;
|
||||||
|
|
||||||
bRelocate = (header->e_type != ET_EXEC);
|
bRelocate = (header->e_type != ET_EXEC);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* ElfReader::GetSectionName(int section) const
|
const char* ElfReader::GetSectionName(int section) const
|
||||||
{
|
{
|
||||||
if (sections[section].sh_type == SHT_NULL)
|
if (!m_is_valid || section < 0 || section >= header->e_shnum ||
|
||||||
|
sections[section].sh_type == SHT_NULL)
|
||||||
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
int nameOffset = sections[section].sh_name;
|
const Elf32_Shdr& string_section = sections[header->e_shstrndx];
|
||||||
char* ptr = (char*)GetSectionDataPtr(header->e_shstrndx);
|
const size_t name_offset = sections[section].sh_name;
|
||||||
|
const char* const ptr = reinterpret_cast<const char*>(GetSectionDataPtr(header->e_shstrndx));
|
||||||
|
|
||||||
if (ptr)
|
if (!ptr || name_offset >= string_section.sh_size ||
|
||||||
return ptr + nameOffset;
|
!std::memchr(ptr + name_offset, '\0', string_section.sh_size - name_offset))
|
||||||
else
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr + name_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is just a simple elf loader, good enough to load elfs generated by devkitPPC
|
// This is just a simple elf loader, good enough to load elfs generated by devkitPPC
|
||||||
bool ElfReader::LoadIntoMemory(Core::System& system, bool only_in_mem1) const
|
bool ElfReader::LoadIntoMemory(Core::System& system, bool only_in_mem1) const
|
||||||
{
|
{
|
||||||
|
if (!m_is_valid)
|
||||||
|
return false;
|
||||||
|
|
||||||
INFO_LOG_FMT(BOOT, "String section: {}", header->e_shstrndx);
|
INFO_LOG_FMT(BOOT, "String section: {}", header->e_shstrndx);
|
||||||
|
|
||||||
if (bRelocate)
|
if (bRelocate)
|
||||||
@@ -183,15 +228,27 @@ SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const
|
|||||||
bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
|
bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
|
||||||
const std::string& filename) const
|
const std::string& filename) const
|
||||||
{
|
{
|
||||||
|
if (!m_is_valid)
|
||||||
|
return false;
|
||||||
|
|
||||||
bool hasSymbols = false;
|
bool hasSymbols = false;
|
||||||
SectionID sec = GetSectionByName(".symtab");
|
SectionID sec = GetSectionByName(".symtab");
|
||||||
if (sec != -1)
|
if (sec != -1)
|
||||||
{
|
{
|
||||||
int stringSection = sections[sec].sh_link;
|
const u32 string_section_index = sections[sec].sh_link;
|
||||||
const char* stringBase = (const char*)GetSectionDataPtr(stringSection);
|
if (string_section_index >= header->e_shnum)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const Elf32_Shdr& string_section = sections[string_section_index];
|
||||||
|
const char* stringBase = (const char*)GetSectionDataPtr(string_section_index);
|
||||||
|
if (!stringBase)
|
||||||
|
return false;
|
||||||
|
|
||||||
// We have a symbol table!
|
// We have a symbol table!
|
||||||
Elf32_Sym* symtab = (Elf32_Sym*)(GetSectionDataPtr(sec));
|
Elf32_Sym* symtab = (Elf32_Sym*)(GetSectionDataPtr(sec));
|
||||||
|
if (!symtab)
|
||||||
|
return false;
|
||||||
|
|
||||||
int numSymbols = sections[sec].sh_size / sizeof(Elf32_Sym);
|
int numSymbols = sections[sec].sh_size / sizeof(Elf32_Sym);
|
||||||
for (int sym = 0; sym < numSymbols; sym++)
|
for (int sym = 0; sym < numSymbols; sym++)
|
||||||
{
|
{
|
||||||
@@ -203,7 +260,13 @@ bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_
|
|||||||
int type = symtab[sym].st_info & 0xF;
|
int type = symtab[sym].st_info & 0xF;
|
||||||
int sectionIndex = Common::swap16(symtab[sym].st_shndx);
|
int sectionIndex = Common::swap16(symtab[sym].st_shndx);
|
||||||
int value = Common::swap32(symtab[sym].st_value);
|
int value = Common::swap32(symtab[sym].st_value);
|
||||||
const char* name = stringBase + Common::swap32(symtab[sym].st_name);
|
const size_t name_offset = Common::swap32(symtab[sym].st_name);
|
||||||
|
if (name_offset >= string_section.sh_size ||
|
||||||
|
!std::memchr(stringBase + name_offset, '\0', string_section.sh_size - name_offset))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const char* name = stringBase + name_offset;
|
||||||
if (bRelocate)
|
if (bRelocate)
|
||||||
value += sectionAddrs[sectionIndex];
|
value += sectionAddrs[sectionIndex];
|
||||||
|
|
||||||
@@ -229,6 +292,9 @@ bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_
|
|||||||
|
|
||||||
bool ElfReader::IsWii() const
|
bool ElfReader::IsWii() const
|
||||||
{
|
{
|
||||||
|
if (!m_is_valid)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Use the same method as the DOL loader uses: search for mfspr from HID4,
|
// Use the same method as the DOL loader uses: search for mfspr from HID4,
|
||||||
// which should only be used in Wii ELFs.
|
// which should only be used in Wii ELFs.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ public:
|
|||||||
bool LoadIntoMemory(Core::System& system, bool only_in_mem1 = false) const override;
|
bool LoadIntoMemory(Core::System& system, bool only_in_mem1 = false) const override;
|
||||||
bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
|
bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
|
||||||
const std::string& filename) const override;
|
const std::string& filename) const override;
|
||||||
// TODO: actually check for validity.
|
bool IsValid() const override { return m_is_valid; }
|
||||||
bool IsValid() const override { return true; }
|
|
||||||
bool IsWii() const override;
|
bool IsWii() const override;
|
||||||
|
|
||||||
int GetNumSegments() const { return (int)(header->e_phnum); }
|
int GetNumSegments() const { return (int)(header->e_phnum); }
|
||||||
@@ -65,16 +64,17 @@ public:
|
|||||||
bool DidRelocate() const { return bRelocate; }
|
bool DidRelocate() const { return bRelocate; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Initialize(u8* bytes);
|
bool Initialize();
|
||||||
|
|
||||||
char* base;
|
char* base = nullptr;
|
||||||
u32* base32;
|
u32* base32 = nullptr;
|
||||||
|
|
||||||
Elf32_Ehdr* header;
|
Elf32_Ehdr* header = nullptr;
|
||||||
Elf32_Phdr* segments;
|
Elf32_Phdr* segments = nullptr;
|
||||||
Elf32_Shdr* sections;
|
Elf32_Shdr* sections = nullptr;
|
||||||
|
|
||||||
u32* sectionAddrs;
|
u32* sectionAddrs = nullptr;
|
||||||
bool bRelocate;
|
bool bRelocate = false;
|
||||||
u32 entryPoint;
|
u32 entryPoint = 0;
|
||||||
|
bool m_is_valid = false;
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user