diff options
| author | Acts1631 <acts1631kjv@proton.me> | 2026-07-20 19:50:54 -0400 |
|---|---|---|
| committer | Acts1631 <acts1631kjv@proton.me> | 2026-07-20 19:50:54 -0400 |
| commit | 24aafdeb47b4723eda806fab68a0f9cd04ec9689 (patch) | |
| tree | c8ffe07670fc91e9f59ffc8be517df837a3eb985 /Source/Core | |
| parent | da1aad51880bbff94ee85f2f4ecb1cb138cd568b (diff) | |
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.
Diffstat (limited to 'Source/Core')
| -rw-r--r-- | Source/Core/Core/Boot/ElfReader.cpp | 102 | ||||
| -rw-r--r-- | Source/Core/Core/Boot/ElfReader.h | 22 |
2 files changed, 95 insertions, 29 deletions
diff --git a/Source/Core/Core/Boot/ElfReader.cpp b/Source/Core/Core/Boot/ElfReader.cpp index f667387ab3..1a6e86e93e 100644 --- a/Source/Core/Core/Boot/ElfReader.cpp +++ b/Source/Core/Core/Boot/ElfReader.cpp @@ -3,6 +3,7 @@ #include "Core/Boot/ElfReader.h" +#include <cstring> #include <string> #include <utility> @@ -70,62 +71,106 @@ static void byteswapSection(Elf32_Shdr& sec) 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)) { - Initialize(m_bytes.data()); + m_is_valid = Initialize(); } ElfReader::ElfReader(const std::string& filename) : BootExecutableReader(filename) { - Initialize(m_bytes.data()); + m_is_valid = Initialize(); } ElfReader::~ElfReader() = default; -void ElfReader::Initialize(u8* ptr) +bool ElfReader::Initialize() { - base = (char*)ptr; - base32 = (u32*)ptr; - header = (Elf32_Ehdr*)ptr; + if (m_bytes.size() < sizeof(Elf32_Ehdr)) + return false; + + 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); - segments = (Elf32_Phdr*)(base + header->e_phoff); - sections = (Elf32_Shdr*)(base + header->e_shoff); + const auto is_range_valid = [this](size_t offset, size_t size) { + 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++) { 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++) { 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; bRelocate = (header->e_type != ET_EXEC); + return true; } 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; + } - int nameOffset = sections[section].sh_name; - char* ptr = (char*)GetSectionDataPtr(header->e_shstrndx); + const Elf32_Shdr& string_section = sections[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) - return ptr + nameOffset; - else + if (!ptr || name_offset >= string_section.sh_size || + !std::memchr(ptr + name_offset, '\0', string_section.sh_size - name_offset)) + { return nullptr; + } + + return ptr + name_offset; } // 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 { + if (!m_is_valid) + return false; + INFO_LOG_FMT(BOOT, "String section: {}", header->e_shstrndx); 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, const std::string& filename) const { + if (!m_is_valid) + return false; + bool hasSymbols = false; SectionID sec = GetSectionByName(".symtab"); if (sec != -1) { - int stringSection = sections[sec].sh_link; - const char* stringBase = (const char*)GetSectionDataPtr(stringSection); + const u32 string_section_index = sections[sec].sh_link; + 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! Elf32_Sym* symtab = (Elf32_Sym*)(GetSectionDataPtr(sec)); + if (!symtab) + return false; + int numSymbols = sections[sec].sh_size / sizeof(Elf32_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 sectionIndex = Common::swap16(symtab[sym].st_shndx); 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) value += sectionAddrs[sectionIndex]; @@ -229,6 +292,9 @@ bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_ bool ElfReader::IsWii() const { + if (!m_is_valid) + return false; + // Use the same method as the DOL loader uses: search for mfspr from HID4, // which should only be used in Wii ELFs. // diff --git a/Source/Core/Core/Boot/ElfReader.h b/Source/Core/Core/Boot/ElfReader.h index 83fd878012..cb6c895496 100644 --- a/Source/Core/Core/Boot/ElfReader.h +++ b/Source/Core/Core/Boot/ElfReader.h @@ -38,8 +38,7 @@ public: bool LoadIntoMemory(Core::System& system, bool only_in_mem1 = false) const override; bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db, const std::string& filename) const override; - // TODO: actually check for validity. - bool IsValid() const override { return true; } + bool IsValid() const override { return m_is_valid; } bool IsWii() const override; int GetNumSegments() const { return (int)(header->e_phnum); } @@ -65,16 +64,17 @@ public: bool DidRelocate() const { return bRelocate; } private: - void Initialize(u8* bytes); + bool Initialize(); - char* base; - u32* base32; + char* base = nullptr; + u32* base32 = nullptr; - Elf32_Ehdr* header; - Elf32_Phdr* segments; - Elf32_Shdr* sections; + Elf32_Ehdr* header = nullptr; + Elf32_Phdr* segments = nullptr; + Elf32_Shdr* sections = nullptr; - u32* sectionAddrs; - bool bRelocate; - u32 entryPoint; + u32* sectionAddrs = nullptr; + bool bRelocate = false; + u32 entryPoint = 0; + bool m_is_valid = false; }; |
