From 1ad2988179138ee61807fd065e9e030f285d36a4 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 10 Aug 2020 09:34:08 -0400 Subject: [PATCH] Lagom: Add an ELF fuzzer, and tweak the code to survive a few minutes of fuzzing 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. --- Libraries/LibELF/Image.cpp | 37 ++++++++++++++---- Libraries/LibELF/Image.h | 4 +- Libraries/LibELF/Loader.cpp | 7 ++-- Libraries/LibELF/Loader.h | 4 +- Libraries/LibELF/Validation.cpp | 62 ++++++++++++++++++++----------- Libraries/LibELF/Validation.h | 2 +- Meta/Lagom/Fuzzers/CMakeLists.txt | 11 +++++- Meta/Lagom/Fuzzers/FuzzELF.cpp | 35 +++++++++++++++++ 8 files changed, 123 insertions(+), 39 deletions(-) create mode 100644 Meta/Lagom/Fuzzers/FuzzELF.cpp diff --git a/Libraries/LibELF/Image.cpp b/Libraries/LibELF/Image.cpp index 9e401a1942865c..714ae98cee195f 100644 --- a/Libraries/LibELF/Image.cpp +++ b/Libraries/LibELF/Image.cpp @@ -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(m_buffer) + offset; } const Elf32_Ehdr& Image::header() const { + ASSERT(m_size >= sizeof(Elf32_Ehdr)); return *reinterpret_cast(raw_data(0)); } const Elf32_Phdr& Image::program_header_internal(unsigned index) const { + ASSERT(m_valid); ASSERT(index < header().e_phnum); return *reinterpret_cast(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(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(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); diff --git a/Libraries/LibELF/Image.h b/Libraries/LibELF/Image.h index 3f43377924206b..5f7540a3a60556 100644 --- a/Libraries/LibELF/Image.h +++ b/Libraries/LibELF/Image.h @@ -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 @@ class Image { 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 @@ class Image { const u8* m_buffer { nullptr }; size_t m_size { 0 }; + bool m_verbose_logging { true }; HashMap m_sections; bool m_valid { false }; unsigned m_symbol_table_section_index { 0 }; diff --git a/Libraries/LibELF/Loader.cpp b/Libraries/LibELF/Loader.cpp index ba3be75fd3e181..e98594510cbf58 100644 --- a/Libraries/LibELF/Loader.cpp +++ b/Libraries/LibELF/Loader.cpp @@ -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; diff --git a/Libraries/LibELF/Loader.h b/Libraries/LibELF/Loader.h index 9c0f16567d4c25..a349800515bb64 100644 --- a/Libraries/LibELF/Loader.h +++ b/Libraries/LibELF/Loader.h @@ -45,7 +45,7 @@ namespace ELF { class Loader : public RefCounted { public: - static NonnullRefPtr create(const u8* data, size_t size) { return adopt(*new Loader(data, size)); } + static NonnullRefPtr 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 @@ class Loader : public RefCounted { Optional 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(); diff --git a/Libraries/LibELF/Validation.cpp b/Libraries/LibELF/Validation.cpp index f1b9533187f4cd..8445acf2237300 100644 --- a/Libraries/LibELF/Validation.cpp +++ b/Libraries/LibELF/Validation.cpp @@ -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; } diff --git a/Libraries/LibELF/Validation.h b/Libraries/LibELF/Validation.h index 7e3af66ac03f3f..85495ce09abf26 100644 --- a/Libraries/LibELF/Validation.h +++ b/Libraries/LibELF/Validation.h @@ -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 diff --git a/Meta/Lagom/Fuzzers/CMakeLists.txt b/Meta/Lagom/Fuzzers/CMakeLists.txt index 0d4ffea22060ff..cec56eccb603cd 100644 --- a/Meta/Lagom/Fuzzers/CMakeLists.txt +++ b/Meta/Lagom/Fuzzers/CMakeLists.txt @@ -1,8 +1,16 @@ +add_executable(FuzzELF FuzzELF.cpp) +target_compile_options(FuzzELF + PRIVATE $<$:-g -O1 -fsanitize=fuzzer> + ) +target_link_libraries(FuzzELF + PUBLIC Lagom + PRIVATE $<$:-fsanitize=fuzzer> + ) + add_executable(FuzzJs FuzzJs.cpp) target_compile_options(FuzzJs PRIVATE $<$:-g -O1 -fsanitize=fuzzer> ) - target_link_libraries(FuzzJs PUBLIC Lagom PRIVATE $<$:-fsanitize=fuzzer> @@ -12,7 +20,6 @@ add_executable(FuzzMarkdown FuzzMarkdown.cpp) target_compile_options(FuzzMarkdown PRIVATE $<$:-g -O1 -fsanitize=fuzzer> ) - target_link_libraries(FuzzMarkdown PUBLIC Lagom PRIVATE $<$:-fsanitize=fuzzer> diff --git a/Meta/Lagom/Fuzzers/FuzzELF.cpp b/Meta/Lagom/Fuzzers/FuzzELF.cpp new file mode 100644 index 00000000000000..0717eea45ffd8a --- /dev/null +++ b/Meta/Lagom/Fuzzers/FuzzELF.cpp @@ -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 +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + ELF::Loader::create(data, size, /*verbose_logging=*/false); + return 0; +}