mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-12 17:37:53 +03:00
Lagom+LibELF: Add an ELF fuzzer, and tweak the code to survive a few minutes of fuzzing (#3071)
If a buffer smaller than Elf32_Ehdr was passed to Image, header() would do an out-of-bounds read. Make parse() check for that. Make most Image methods assert that the image is_valid(). For that to work, set m_valid early in Image::parse() instead of only at its end. Also reorder a few things so that the fuzzer doesn't hit (valid) assertions, which were harmless from a security PoV but which still allowed userspace to crash the kernel with an invalid ELF file. Make dbgprintf()s configurable at run time so that the fuzzer doesn't produce lots of logspam.
This commit is contained in:
parent
eaf7e68408
commit
00f658b984
Notes:
sideshowbarker
2024-07-19 04:04:02 +09:00
Author: https://github.com/nico Commit: https://github.com/SerenityOS/serenity/commit/00f658b9846 Pull-request: https://github.com/SerenityOS/serenity/pull/3071
@ -32,11 +32,12 @@
|
||||
|
||||
namespace ELF {
|
||||
|
||||
Image::Image(const u8* buffer, size_t size)
|
||||
Image::Image(const u8* buffer, size_t size, bool verbose_logging)
|
||||
: m_buffer(buffer)
|
||||
, m_size(size)
|
||||
, m_verbose_logging(verbose_logging)
|
||||
{
|
||||
m_valid = parse();
|
||||
parse();
|
||||
}
|
||||
|
||||
Image::~Image()
|
||||
@ -63,6 +64,7 @@ static const char* object_file_type_to_string(Elf32_Half type)
|
||||
|
||||
StringView Image::section_index_to_string(unsigned index) const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
if (index == SHN_UNDEF)
|
||||
return "Undefined";
|
||||
if (index >= SHN_LORESERVE)
|
||||
@ -72,6 +74,7 @@ StringView Image::section_index_to_string(unsigned index) const
|
||||
|
||||
unsigned Image::symbol_count() const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
return section(m_symbol_table_section_index).entry_count();
|
||||
}
|
||||
|
||||
@ -130,26 +133,32 @@ void Image::dump() const
|
||||
|
||||
unsigned Image::section_count() const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
return header().e_shnum;
|
||||
}
|
||||
|
||||
unsigned Image::program_header_count() const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
return header().e_phnum;
|
||||
}
|
||||
|
||||
bool Image::parse()
|
||||
{
|
||||
if (!validate_elf_header(header(), m_size)) {
|
||||
dbgputstr("Image::parse(): ELF Header not valid\n");
|
||||
return false;
|
||||
if (m_size < sizeof(Elf32_Ehdr) || !validate_elf_header(header(), m_size, m_verbose_logging)) {
|
||||
if (m_verbose_logging)
|
||||
dbgputstr("Image::parse(): ELF Header not valid\n");
|
||||
return m_valid = false;
|
||||
}
|
||||
|
||||
m_valid = true;
|
||||
|
||||
// First locate the string tables.
|
||||
for (unsigned i = 0; i < section_count(); ++i) {
|
||||
auto& sh = section_header(i);
|
||||
if (sh.sh_type == SHT_SYMTAB) {
|
||||
ASSERT(!m_symbol_table_section_index || m_symbol_table_section_index == i);
|
||||
if (m_symbol_table_section_index && m_symbol_table_section_index != i)
|
||||
return m_valid = false;
|
||||
m_symbol_table_section_index = i;
|
||||
}
|
||||
if (sh.sh_type == SHT_STRTAB && i != header().e_shstrndx) {
|
||||
@ -164,17 +173,19 @@ bool Image::parse()
|
||||
m_sections.set(section.name(), move(i));
|
||||
}
|
||||
|
||||
return true;
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
StringView Image::table_string(unsigned table_index, unsigned offset) const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
auto& sh = section_header(table_index);
|
||||
if (sh.sh_type != SHT_STRTAB)
|
||||
return nullptr;
|
||||
size_t computed_offset = sh.sh_offset + offset;
|
||||
if (computed_offset >= m_size) {
|
||||
dbgprintf("SHENANIGANS! Image::table_string() computed offset outside image.\n");
|
||||
if (m_verbose_logging)
|
||||
dbgprintf("SHENANIGANS! Image::table_string() computed offset outside image.\n");
|
||||
return {};
|
||||
}
|
||||
size_t max_length = m_size - computed_offset;
|
||||
@ -184,38 +195,45 @@ StringView Image::table_string(unsigned table_index, unsigned offset) const
|
||||
|
||||
StringView Image::section_header_table_string(unsigned offset) const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
return table_string(header().e_shstrndx, offset);
|
||||
}
|
||||
|
||||
StringView Image::table_string(unsigned offset) const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
return table_string(m_string_table_section_index, offset);
|
||||
}
|
||||
|
||||
const char* Image::raw_data(unsigned offset) const
|
||||
{
|
||||
ASSERT(offset < m_size); // Callers must check indices into raw_data()'s result are also in bounds.
|
||||
return reinterpret_cast<const char*>(m_buffer) + offset;
|
||||
}
|
||||
|
||||
const Elf32_Ehdr& Image::header() const
|
||||
{
|
||||
ASSERT(m_size >= sizeof(Elf32_Ehdr));
|
||||
return *reinterpret_cast<const Elf32_Ehdr*>(raw_data(0));
|
||||
}
|
||||
|
||||
const Elf32_Phdr& Image::program_header_internal(unsigned index) const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
ASSERT(index < header().e_phnum);
|
||||
return *reinterpret_cast<const Elf32_Phdr*>(raw_data(header().e_phoff + (index * sizeof(Elf32_Phdr))));
|
||||
}
|
||||
|
||||
const Elf32_Shdr& Image::section_header(unsigned index) const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
ASSERT(index < header().e_shnum);
|
||||
return *reinterpret_cast<const Elf32_Shdr*>(raw_data(header().e_shoff + (index * header().e_shentsize)));
|
||||
}
|
||||
|
||||
const Image::Symbol Image::symbol(unsigned index) const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
ASSERT(index < symbol_count());
|
||||
auto* raw_syms = reinterpret_cast<const Elf32_Sym*>(raw_data(section(m_symbol_table_section_index).offset()));
|
||||
return Symbol(*this, index, raw_syms[index]);
|
||||
@ -223,12 +241,14 @@ const Image::Symbol Image::symbol(unsigned index) const
|
||||
|
||||
const Image::Section Image::section(unsigned index) const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
ASSERT(index < section_count());
|
||||
return Section(*this, index);
|
||||
}
|
||||
|
||||
const Image::ProgramHeader Image::program_header(unsigned index) const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
ASSERT(index < program_header_count());
|
||||
return ProgramHeader(*this, index);
|
||||
}
|
||||
@ -258,6 +278,7 @@ const Image::RelocationSection Image::Section::relocations() const
|
||||
|
||||
const Image::Section Image::lookup_section(const String& name) const
|
||||
{
|
||||
ASSERT(m_valid);
|
||||
if (auto it = m_sections.find(name); it != m_sections.end())
|
||||
return section((*it).value);
|
||||
return section(0);
|
||||
|
@ -37,7 +37,7 @@ namespace ELF {
|
||||
|
||||
class Image {
|
||||
public:
|
||||
explicit Image(const u8*, size_t);
|
||||
explicit Image(const u8*, size_t, bool verbose_logging = true);
|
||||
~Image();
|
||||
void dump() const;
|
||||
bool is_valid() const { return m_valid; }
|
||||
@ -206,7 +206,6 @@ public:
|
||||
VirtualAddress entry() const { return VirtualAddress(header().e_entry); }
|
||||
|
||||
private:
|
||||
bool parse_header();
|
||||
const char* raw_data(unsigned offset) const;
|
||||
const Elf32_Ehdr& header() const;
|
||||
const Elf32_Shdr& section_header(unsigned) const;
|
||||
@ -218,6 +217,7 @@ private:
|
||||
|
||||
const u8* m_buffer { nullptr };
|
||||
size_t m_size { 0 };
|
||||
bool m_verbose_logging { true };
|
||||
HashMap<String, unsigned> m_sections;
|
||||
bool m_valid { false };
|
||||
unsigned m_symbol_table_section_index { 0 };
|
||||
|
@ -40,10 +40,9 @@
|
||||
|
||||
namespace ELF {
|
||||
|
||||
Loader::Loader(const u8* buffer, size_t size)
|
||||
: m_image(buffer, size)
|
||||
Loader::Loader(const u8* buffer, size_t size, bool verbose_logging)
|
||||
: m_image(buffer, size, verbose_logging)
|
||||
{
|
||||
m_symbol_count = m_image.symbol_count();
|
||||
}
|
||||
|
||||
Loader::~Loader()
|
||||
@ -58,6 +57,8 @@ bool Loader::load()
|
||||
if (!m_image.is_valid())
|
||||
return false;
|
||||
|
||||
m_symbol_count = m_image.symbol_count();
|
||||
|
||||
if (!layout())
|
||||
return false;
|
||||
|
||||
|
@ -45,7 +45,7 @@ namespace ELF {
|
||||
|
||||
class Loader : public RefCounted<Loader> {
|
||||
public:
|
||||
static NonnullRefPtr<Loader> create(const u8* data, size_t size) { return adopt(*new Loader(data, size)); }
|
||||
static NonnullRefPtr<Loader> create(const u8* data, size_t size, bool verbose_logging=true) { return adopt(*new Loader(data, size, verbose_logging)); }
|
||||
~Loader();
|
||||
|
||||
bool load();
|
||||
@ -68,7 +68,7 @@ public:
|
||||
Optional<Image::Symbol> find_symbol(u32 address, u32* offset = nullptr) const;
|
||||
|
||||
private:
|
||||
explicit Loader(const u8*, size_t);
|
||||
explicit Loader(const u8*, size_t, bool verbose_logging);
|
||||
|
||||
bool layout();
|
||||
bool perform_relocations();
|
||||
|
@ -31,99 +31,119 @@
|
||||
|
||||
namespace ELF {
|
||||
|
||||
bool validate_elf_header(const Elf32_Ehdr& elf_header, size_t file_size)
|
||||
bool validate_elf_header(const Elf32_Ehdr& elf_header, size_t file_size, bool verbose)
|
||||
{
|
||||
if (!IS_ELF(elf_header)) {
|
||||
dbgputstr("File is not an ELF file.\n");
|
||||
if (verbose)
|
||||
dbgputstr("File is not an ELF file.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ELFCLASS32 != elf_header.e_ident[EI_CLASS]) {
|
||||
dbgputstr("File is not a 32 bit ELF file.\n");
|
||||
if (verbose)
|
||||
dbgputstr("File is not a 32 bit ELF file.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ELFDATA2LSB != elf_header.e_ident[EI_DATA]) {
|
||||
dbgputstr("File is not a little endian ELF file.\n");
|
||||
if (verbose)
|
||||
dbgputstr("File is not a little endian ELF file.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EV_CURRENT != elf_header.e_ident[EI_VERSION]) {
|
||||
dbgprintf("File has unrecognized ELF version (%d), expected (%d)!\n", elf_header.e_ident[EI_VERSION], EV_CURRENT);
|
||||
if (verbose)
|
||||
dbgprintf("File has unrecognized ELF version (%d), expected (%d)!\n", elf_header.e_ident[EI_VERSION], EV_CURRENT);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ELFOSABI_SYSV != elf_header.e_ident[EI_OSABI]) {
|
||||
dbgprintf("File has unknown OS ABI (%d), expected SYSV(0)!\n", elf_header.e_ident[EI_OSABI]);
|
||||
if (verbose)
|
||||
dbgprintf("File has unknown OS ABI (%d), expected SYSV(0)!\n", elf_header.e_ident[EI_OSABI]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 != elf_header.e_ident[EI_ABIVERSION]) {
|
||||
dbgprintf("File has unknown SYSV ABI version (%d)!\n", elf_header.e_ident[EI_ABIVERSION]);
|
||||
if (verbose)
|
||||
dbgprintf("File has unknown SYSV ABI version (%d)!\n", elf_header.e_ident[EI_ABIVERSION]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EM_386 != elf_header.e_machine) {
|
||||
dbgprintf("File has unknown machine (%d), expected i386 (3)!\n", elf_header.e_machine);
|
||||
if (verbose)
|
||||
dbgprintf("File has unknown machine (%d), expected i386 (3)!\n", elf_header.e_machine);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ET_EXEC != elf_header.e_type && ET_DYN != elf_header.e_type && ET_REL != elf_header.e_type) {
|
||||
dbgprintf("File has unloadable ELF type (%d), expected REL (1), EXEC (2) or DYN (3)!\n", elf_header.e_type);
|
||||
if (verbose)
|
||||
dbgprintf("File has unloadable ELF type (%d), expected REL (1), EXEC (2) or DYN (3)!\n", elf_header.e_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EV_CURRENT != elf_header.e_version) {
|
||||
dbgprintf("File has unrecognized ELF version (%d), expected (%d)!\n", elf_header.e_version, EV_CURRENT);
|
||||
if (verbose)
|
||||
dbgprintf("File has unrecognized ELF version (%d), expected (%d)!\n", elf_header.e_version, EV_CURRENT);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sizeof(Elf32_Ehdr) != elf_header.e_ehsize) {
|
||||
dbgprintf("File has incorrect ELF header size..? (%d), expected (%zu)!\n", elf_header.e_ehsize, sizeof(Elf32_Ehdr));
|
||||
if (verbose)
|
||||
dbgprintf("File has incorrect ELF header size..? (%d), expected (%zu)!\n", elf_header.e_ehsize, sizeof(Elf32_Ehdr));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elf_header.e_phoff > file_size || elf_header.e_shoff > file_size) {
|
||||
dbgprintf("SHENANIGANS! program header offset (%d) or section header offset (%d) are past the end of the file!\n",
|
||||
elf_header.e_phoff, elf_header.e_shoff);
|
||||
if (verbose) {
|
||||
dbgprintf("SHENANIGANS! program header offset (%d) or section header offset (%d) are past the end of the file!\n",
|
||||
elf_header.e_phoff, elf_header.e_shoff);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elf_header.e_phnum != 0 && elf_header.e_phoff != elf_header.e_ehsize) {
|
||||
dbgprintf("File does not have program headers directly after the ELF header? program header offset (%d), expected (%d).\n",
|
||||
elf_header.e_phoff, elf_header.e_ehsize);
|
||||
if (verbose) {
|
||||
dbgprintf("File does not have program headers directly after the ELF header? program header offset (%d), expected (%d).\n",
|
||||
elf_header.e_phoff, elf_header.e_ehsize);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 != elf_header.e_flags) {
|
||||
dbgprintf("File has incorrect ELF header flags...? (%d), expected (%d).\n", elf_header.e_flags, 0);
|
||||
if (verbose)
|
||||
dbgprintf("File has incorrect ELF header flags...? (%d), expected (%d).\n", elf_header.e_flags, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 != elf_header.e_phnum && sizeof(Elf32_Phdr) != elf_header.e_phentsize) {
|
||||
dbgprintf("File has incorrect program header size..? (%d), expected (%zu).\n", elf_header.e_phentsize, sizeof(Elf32_Phdr));
|
||||
if (verbose)
|
||||
dbgprintf("File has incorrect program header size..? (%d), expected (%zu).\n", elf_header.e_phentsize, sizeof(Elf32_Phdr));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sizeof(Elf32_Shdr) != elf_header.e_shentsize) {
|
||||
dbgprintf("File has incorrect section header size..? (%d), expected (%zu).\n", elf_header.e_shentsize, sizeof(Elf32_Shdr));
|
||||
if (verbose)
|
||||
dbgprintf("File has incorrect section header size..? (%d), expected (%zu).\n", elf_header.e_shentsize, sizeof(Elf32_Shdr));
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t end_of_last_program_header = elf_header.e_phoff + (elf_header.e_phnum * elf_header.e_phentsize);
|
||||
if (end_of_last_program_header > file_size) {
|
||||
dbgprintf("SHENANIGANS! End of last program header (%zu) is past the end of the file!\n", end_of_last_program_header);
|
||||
if (verbose)
|
||||
dbgprintf("SHENANIGANS! End of last program header (%zu) is past the end of the file!\n", end_of_last_program_header);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t end_of_last_section_header = elf_header.e_shoff + (elf_header.e_shnum * elf_header.e_shentsize);
|
||||
if (end_of_last_section_header > file_size) {
|
||||
dbgprintf("SHENANIGANS! End of last section header (%zu) is past the end of the file!\n", end_of_last_section_header);
|
||||
if (verbose)
|
||||
dbgprintf("SHENANIGANS! End of last section header (%zu) is past the end of the file!\n", end_of_last_section_header);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elf_header.e_shstrndx >= elf_header.e_shnum) {
|
||||
dbgprintf("SHENANIGANS! Section header string table index (%d) is not a valid index given we have %d section headers!\n", elf_header.e_shstrndx, elf_header.e_shnum);
|
||||
if (verbose)
|
||||
dbgprintf("SHENANIGANS! Section header string table index (%d) is not a valid index given we have %d section headers!\n", elf_header.e_shstrndx, elf_header.e_shnum);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
|
||||
namespace ELF {
|
||||
|
||||
bool validate_elf_header(const Elf32_Ehdr& elf_header, size_t file_size);
|
||||
bool validate_elf_header(const Elf32_Ehdr& elf_header, size_t file_size, bool verbose=true);
|
||||
bool validate_program_headers(const Elf32_Ehdr& elf_header, size_t file_size, u8* buffer, size_t buffer_size, String& interpreter_path);
|
||||
|
||||
} // end namespace ELF
|
||||
|
@ -1,8 +1,16 @@
|
||||
add_executable(FuzzELF FuzzELF.cpp)
|
||||
target_compile_options(FuzzELF
|
||||
PRIVATE $<$<C_COMPILER_ID:Clang>:-g -O1 -fsanitize=fuzzer>
|
||||
)
|
||||
target_link_libraries(FuzzELF
|
||||
PUBLIC Lagom
|
||||
PRIVATE $<$<C_COMPILER_ID:Clang>:-fsanitize=fuzzer>
|
||||
)
|
||||
|
||||
add_executable(FuzzJs FuzzJs.cpp)
|
||||
target_compile_options(FuzzJs
|
||||
PRIVATE $<$<C_COMPILER_ID:Clang>:-g -O1 -fsanitize=fuzzer>
|
||||
)
|
||||
|
||||
target_link_libraries(FuzzJs
|
||||
PUBLIC Lagom
|
||||
PRIVATE $<$<C_COMPILER_ID:Clang>:-fsanitize=fuzzer>
|
||||
@ -12,7 +20,6 @@ add_executable(FuzzMarkdown FuzzMarkdown.cpp)
|
||||
target_compile_options(FuzzMarkdown
|
||||
PRIVATE $<$<C_COMPILER_ID:Clang>:-g -O1 -fsanitize=fuzzer>
|
||||
)
|
||||
|
||||
target_link_libraries(FuzzMarkdown
|
||||
PUBLIC Lagom
|
||||
PRIVATE $<$<C_COMPILER_ID:Clang>:-fsanitize=fuzzer>
|
||||
|
35
Meta/Lagom/Fuzzers/FuzzELF.cpp
Normal file
35
Meta/Lagom/Fuzzers/FuzzELF.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibELF/Loader.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
ELF::Loader::create(data, size, /*verbose_logging=*/false);
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user