Skip to content

Commit

Permalink
Lagom+LibELF: Add an ELF fuzzer, and tweak the code to survive a few …
Browse files Browse the repository at this point in the history
…minutes of fuzzing (SerenityOS#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.
  • Loading branch information
nico committed Aug 10, 2020
1 parent eaf7e68 commit 00f658b
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 39 deletions.
37 changes: 29 additions & 8 deletions Libraries/LibELF/Image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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();
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -184,51 +195,60 @@ 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]);
}

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);
}
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions Libraries/LibELF/Image.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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;
Expand All @@ -218,6 +217,7 @@ class Image {

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 };
Expand Down
7 changes: 4 additions & 3 deletions Libraries/LibELF/Loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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;

Expand Down
4 changes: 2 additions & 2 deletions Libraries/LibELF/Loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -68,7 +68,7 @@ class Loader : public RefCounted<Loader> {
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();
Expand Down
62 changes: 41 additions & 21 deletions Libraries/LibELF/Validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion Libraries/LibELF/Validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
11 changes: 9 additions & 2 deletions Meta/Lagom/Fuzzers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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>
Expand All @@ -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>
Expand Down
Loading

0 comments on commit 00f658b

Please sign in to comment.