From b62e32440689839ed875c9574f607cc4f7245c10 Mon Sep 17 00:00:00 2001 From: Dylan Katz Date: Thu, 28 May 2020 14:23:10 -0700 Subject: [PATCH] Added fuzzer for Unwinder Adds a fuzzer for Unwinder. This will likely cover a large portion of the library, as it uses many of the library's features in the process of setting up the Unwinder. Hopefully this, combined with the calls Unwinder makes internally, will provide sufficient coverage. Rough coverage estimate (this is drastically lower than the true number due to shared libraries): 6.6% Test: Ran on device for a few hours Signed-off-by: Dylan Katz Change-Id: I813e204df595ff38dccfb73be7fff5c080aaa043 --- libunwindstack/Android.bp | 32 ++ .../tests/fuzz/UnwinderComponentCreator.cpp | 356 ++++++++++++++++++ .../tests/fuzz/UnwinderComponentCreator.h | 83 ++++ libunwindstack/tests/fuzz/UnwinderFuzz.cpp | 99 +++++ 4 files changed, 570 insertions(+) create mode 100644 libunwindstack/tests/fuzz/UnwinderComponentCreator.cpp create mode 100644 libunwindstack/tests/fuzz/UnwinderComponentCreator.h create mode 100644 libunwindstack/tests/fuzz/UnwinderFuzz.cpp diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp index 19c22c849..8cc780acd 100644 --- a/libunwindstack/Android.bp +++ b/libunwindstack/Android.bp @@ -340,6 +340,37 @@ cc_test { isolated: true, } +//------------------------------------------------------------------------- +// Fuzzers +//------------------------------------------------------------------------- +cc_defaults { + name: "libunwindstack_fuzz_defaults", + host_supported: true, + defaults: ["libunwindstack_flags"], + cflags: [ + "-Wno-exit-time-destructors", + "-g", + ], + shared_libs: [ + "libbase", + "liblog", + "liblzma", + "libunwindstack", + "libdexfile_support", + ], +} + +cc_fuzz { + name: "libunwindstack_fuzz_unwinder", + defaults: ["libunwindstack_fuzz_defaults"], + srcs: [ + "tests/MemoryFake.cpp", + "tests/ElfFake.cpp", + "tests/fuzz/UnwinderComponentCreator.cpp", + "tests/fuzz/UnwinderFuzz.cpp", + ], +} + //------------------------------------------------------------------------- // Tools //------------------------------------------------------------------------- @@ -458,3 +489,4 @@ cc_binary_host { "tests/GenGnuDebugdata.cpp", ], } + diff --git a/libunwindstack/tests/fuzz/UnwinderComponentCreator.cpp b/libunwindstack/tests/fuzz/UnwinderComponentCreator.cpp new file mode 100644 index 000000000..94f5a73cd --- /dev/null +++ b/libunwindstack/tests/fuzz/UnwinderComponentCreator.cpp @@ -0,0 +1,356 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "UnwinderComponentCreator.h" + +std::unique_ptr GetRegisters(ArchEnum arch) { + switch (arch) { + case unwindstack::ARCH_ARM: { + std::unique_ptr regs = std::make_unique(); + return regs; + } + case unwindstack::ARCH_ARM64: { + std::unique_ptr regs = std::make_unique(); + return regs; + } + case unwindstack::ARCH_X86: { + std::unique_ptr regs = std::make_unique(); + return regs; + } + case unwindstack::ARCH_X86_64: { + std::unique_ptr regs = std::make_unique(); + return regs; + } + case unwindstack::ARCH_MIPS: { + std::unique_ptr regs = std::make_unique(); + return regs; + } + case unwindstack::ARCH_MIPS64: { + std::unique_ptr regs = std::make_unique(); + return regs; + } + case unwindstack::ARCH_UNKNOWN: + default: { + std::unique_ptr regs = std::make_unique(); + return regs; + } + } +} + +ArchEnum GetArch(FuzzedDataProvider* data_provider) { + uint8_t arch = data_provider->ConsumeIntegralInRange(1, kArchCount); + return static_cast(arch); +} + +void ElfAddMapInfo(Maps* maps, uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, + const char* name, Elf* elf = nullptr) { + std::string str_name(name); + maps->Add(start, end, offset, flags, name, static_cast(-1)); + if (elf != nullptr) { + const auto& map_info = *--maps->end(); + map_info->elf.reset(elf); + } +} + +void ElfPushFakeFunctionData(FuzzedDataProvider* data_provider, ElfInterfaceFake* elf) { + uint8_t func_count = data_provider->ConsumeIntegralInRange(0, kMaxFuncCount); + for (uint8_t i = 0; i < func_count; i++) { + std::string func_name = data_provider->ConsumeRandomLengthString(kMaxFuncNameLen); + bool global = data_provider->ConsumeBool(); + if (global) { + elf->FakeSetGlobalVariable(func_name, data_provider->ConsumeIntegral()); + } else { + ElfInterfaceFake::FakePushFunctionData(FunctionData(func_name, i)); + } + } +} +void ElfPushFakeStepData(FuzzedDataProvider* data_provider) { + uint8_t step_count = data_provider->ConsumeIntegralInRange(0, kMaxStepCount); + for (uint8_t i = 0; i < step_count; i++) { + uint64_t pc = data_provider->ConsumeIntegral(); + uint64_t sp = data_provider->ConsumeIntegral(); + bool finished = i + 1 == step_count; + ElfInterfaceFake::FakePushStepData(StepData(pc, sp, finished)); + } +} + +ElfFake* PopulateElfFake(FuzzedDataProvider* data_provider) { + // This will be passed to a smart pointer in ElfAddMapInfo. + ElfFake* elf = new ElfFake(new MemoryFake); + + // This will be handled by a smart pointer within Elf. + ElfInterfaceFake* interface_fake = new ElfInterfaceFake(nullptr); + std::string build_id = data_provider->ConsumeRandomLengthString(kMaxBuildIdLen); + interface_fake->FakeSetBuildID(build_id); + std::string so_name = data_provider->ConsumeRandomLengthString(kMaxSoNameLen); + interface_fake->FakeSetSoname(so_name.c_str()); + + elf->FakeSetArch(GetArch(data_provider)); + elf->FakeSetLoadBias(data_provider->ConsumeIntegral()); + + ElfPushFakeFunctionData(data_provider, interface_fake); + ElfPushFakeStepData(data_provider); + + elf->FakeSetInterface(interface_fake); + ElfInterfaceFake::FakeClear(); + return elf; +} + +std::unique_ptr GetMaps(FuzzedDataProvider* data_provider) { + std::unique_ptr maps = std::make_unique(); + uint8_t entry_count = data_provider->ConsumeIntegralInRange(0, kMaxMapEntryCount); + for (uint8_t i = 0; i < entry_count; i++) { + uint64_t start = data_provider->ConsumeIntegral(); + uint64_t end = data_provider->ConsumeIntegralInRange(start, UINT64_MAX); + uint64_t offset = data_provider->ConsumeIntegral(); + std::string map_info_name = data_provider->ConsumeRandomLengthString(kMaxMapInfoNameLen); + uint8_t flags = PROT_READ | PROT_WRITE; + + bool exec = data_provider->ConsumeBool(); + if (exec) { + flags |= PROT_EXEC; + } + + bool shouldAddElf = data_provider->ConsumeBool(); + if (shouldAddElf) { + ElfAddMapInfo(maps.get(), start, end, offset, flags, map_info_name.c_str(), + PopulateElfFake(data_provider)); + } else { + ElfAddMapInfo(maps.get(), start, end, offset, flags, map_info_name.c_str()); + } + } + maps->Sort(); + return maps; +} + +// This code (until PutElfFilesInMemory) is pretty much directly copied from JitDebugTest.cpp +// There's a few minor modifications, most notably, all methods accept a MemoryFake pointer, and +// PutElfInMemory inserts JIT data when called. +void WriteDescriptor32(MemoryFake* memory, uint64_t addr, uint32_t entry) { + // Format of the 32 bit JITDescriptor structure: + // uint32_t version + memory->SetData32(addr, 1); + // uint32_t action_flag + memory->SetData32(addr + 4, 0); + // uint32_t relevant_entry + memory->SetData32(addr + 8, 0); + // uint32_t first_entry + memory->SetData32(addr + 12, entry); +} + +void WriteDescriptor64(MemoryFake* memory, uint64_t addr, uint64_t entry) { + // Format of the 64 bit JITDescriptor structure: + // uint32_t version + memory->SetData32(addr, 1); + // uint32_t action_flag + memory->SetData32(addr + 4, 0); + // uint64_t relevant_entry + memory->SetData64(addr + 8, 0); + // uint64_t first_entry + memory->SetData64(addr + 16, entry); +} + +void WriteEntry32Pack(MemoryFake* memory, uint64_t addr, uint32_t prev, uint32_t next, + uint32_t elf_addr, uint64_t elf_size) { + // Format of the 32 bit JITCodeEntry structure: + // uint32_t next + memory->SetData32(addr, next); + // uint32_t prev + memory->SetData32(addr + 4, prev); + // uint32_t symfile_addr + memory->SetData32(addr + 8, elf_addr); + // uint64_t symfile_size + memory->SetData64(addr + 12, elf_size); +} + +void WriteEntry32Pad(MemoryFake* memory, uint64_t addr, uint32_t prev, uint32_t next, + uint32_t elf_addr, uint64_t elf_size) { + // Format of the 32 bit JITCodeEntry structure: + // uint32_t next + memory->SetData32(addr, next); + // uint32_t prev + memory->SetData32(addr + 4, prev); + // uint32_t symfile_addr + memory->SetData32(addr + 8, elf_addr); + // uint32_t pad + memory->SetData32(addr + 12, 0); + // uint64_t symfile_size + memory->SetData64(addr + 16, elf_size); +} + +void WriteEntry64(MemoryFake* memory, uint64_t addr, uint64_t prev, uint64_t next, + uint64_t elf_addr, uint64_t elf_size) { + // Format of the 64 bit JITCodeEntry structure: + // uint64_t next + memory->SetData64(addr, next); + // uint64_t prev + memory->SetData64(addr + 8, prev); + // uint64_t symfile_addr + memory->SetData64(addr + 16, elf_addr); + // uint64_t symfile_size + memory->SetData64(addr + 24, elf_size); +} + +template +void PutElfInMemory(MemoryFake* memory, uint64_t offset, uint8_t class_type, uint8_t machine_type, + uint32_t pc, uint32_t size) { + EhdrType ehdr; + memset(&ehdr, 0, sizeof(ehdr)); + uint64_t sh_offset = sizeof(ehdr); + memcpy(ehdr.e_ident, ELFMAG, SELFMAG); + ehdr.e_ident[EI_CLASS] = class_type; + ehdr.e_machine = machine_type; + ehdr.e_shstrndx = 1; + ehdr.e_shoff = sh_offset; + ehdr.e_shentsize = sizeof(ShdrType); + ehdr.e_shnum = 3; + memory->SetMemory(offset, &ehdr, sizeof(ehdr)); + + ShdrType shdr; + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_NULL; + memory->SetMemory(offset + sh_offset, &shdr, sizeof(shdr)); + + sh_offset += sizeof(shdr); + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_STRTAB; + shdr.sh_name = 1; + shdr.sh_offset = 0x500; + shdr.sh_size = 0x100; + memory->SetMemory(offset + sh_offset, &shdr, sizeof(shdr)); + memory->SetMemory(offset + 0x500, ".debug_frame"); + + sh_offset += sizeof(shdr); + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_PROGBITS; + shdr.sh_name = 0; + shdr.sh_addr = 0x600; + shdr.sh_offset = 0x600; + shdr.sh_size = 0x200; + memory->SetMemory(offset + sh_offset, &shdr, sizeof(shdr)); + + // Now add a single cie/fde. + uint64_t dwarf_offset = offset + 0x600; + if (class_type == ELFCLASS32) { + // CIE 32 information. + memory->SetData32(dwarf_offset, 0xfc); + memory->SetData32(dwarf_offset + 0x4, 0xffffffff); + memory->SetData8(dwarf_offset + 0x8, 1); + memory->SetData8(dwarf_offset + 0x9, '\0'); + memory->SetData8(dwarf_offset + 0xa, 0x4); + memory->SetData8(dwarf_offset + 0xb, 0x4); + memory->SetData8(dwarf_offset + 0xc, 0x1); + + // FDE 32 information. + memory->SetData32(dwarf_offset + 0x100, 0xfc); + memory->SetData32(dwarf_offset + 0x104, 0); + memory->SetData32(dwarf_offset + 0x108, pc); + memory->SetData32(dwarf_offset + 0x10c, size); + } else { + // CIE 64 information. + memory->SetData32(dwarf_offset, 0xffffffff); + memory->SetData64(dwarf_offset + 4, 0xf4); + memory->SetData64(dwarf_offset + 0xc, 0xffffffffffffffffULL); + memory->SetData8(dwarf_offset + 0x14, 1); + memory->SetData8(dwarf_offset + 0x15, '\0'); + memory->SetData8(dwarf_offset + 0x16, 0x4); + memory->SetData8(dwarf_offset + 0x17, 0x4); + memory->SetData8(dwarf_offset + 0x18, 0x1); + + // FDE 64 information. + memory->SetData32(dwarf_offset + 0x100, 0xffffffff); + memory->SetData64(dwarf_offset + 0x104, 0xf4); + memory->SetData64(dwarf_offset + 0x10c, 0); + memory->SetData64(dwarf_offset + 0x114, pc); + memory->SetData64(dwarf_offset + 0x11c, size); + } +} + +void PutElfFilesInMemory(MemoryFake* memory, FuzzedDataProvider* data_provider) { + uint8_t elf_file_count = data_provider->ConsumeIntegralInRange(0, kMaxJitElfFiles); + int entry_offset = 0; + int prev_jit_addr = 0; + for (uint8_t i = 0; i < elf_file_count; i++) { + uint64_t offset = data_provider->ConsumeIntegral(); + // Technically the max valid value is ELFCLASSNUM - 1 (2), but + // we want to test values outside of that range. + uint8_t class_type = data_provider->ConsumeIntegral(); + // Same here, EM_NUM is 253, max valid machine type is 252 + uint8_t machine_type = data_provider->ConsumeIntegral(); + uint32_t pc = data_provider->ConsumeIntegral(); + uint32_t size = data_provider->ConsumeIntegral(); + bool sixty_four_bit = data_provider->ConsumeBool(); + bool write_jit = data_provider->ConsumeBool(); + if (sixty_four_bit) { + PutElfInMemory(memory, offset, class_type, machine_type, pc, size); + } else { + PutElfInMemory(memory, offset, class_type, machine_type, pc, size); + } + if (write_jit) { + bool use_pad = data_provider->ConsumeBool(); + // It is possible this will overwrite part of the ELF. + // This provides an interesting test of how malformed ELF + // data is handled. + uint64_t cur_descriptor_addr = 0x11800 + entry_offset; + uint64_t cur_jit_addr = 0x200000 + entry_offset; + uint64_t next_jit_addr = cur_jit_addr + size; + if (sixty_four_bit) { + WriteDescriptor64(memory, 0x11800, cur_jit_addr); + WriteEntry64(memory, cur_jit_addr, prev_jit_addr, next_jit_addr, pc, size); + } else { + // Loop back. Again, this may corrupt data, + // but that will allow for testing edge cases with + // malformed JIT data. + if (cur_jit_addr > UINT32_MAX) { + entry_offset = 0; + cur_jit_addr = 0x200000; + cur_descriptor_addr = 0x11800; + next_jit_addr = cur_jit_addr + size; + } + WriteDescriptor32(memory, cur_descriptor_addr, cur_jit_addr); + if (use_pad) { + WriteEntry32Pad(memory, cur_jit_addr, prev_jit_addr, next_jit_addr, pc, size); + } else { + WriteEntry32Pack(memory, cur_jit_addr, prev_jit_addr, next_jit_addr, pc, size); + } + } + entry_offset += size; + prev_jit_addr = cur_jit_addr; + } + } +} + +std::vector GetStringList(FuzzedDataProvider* data_provider, uint max_str_len, + uint max_strings) { + uint str_count = data_provider->ConsumeIntegralInRange(0, max_strings); + std::vector strings; + for (uint i = 0; i < str_count; i++) { + strings.push_back(data_provider->ConsumeRandomLengthString(max_str_len)); + } + return strings; +} + +std::unique_ptr GetDexFiles(FuzzedDataProvider* data_provider, + std::shared_ptr memory, uint max_library_length, + uint max_libraries) { + std::vector search_libs = + GetStringList(data_provider, max_library_length, max_libraries); + if (search_libs.size() <= 0) { + return std::make_unique(memory); + } + + return std::make_unique(memory, search_libs); +} diff --git a/libunwindstack/tests/fuzz/UnwinderComponentCreator.h b/libunwindstack/tests/fuzz/UnwinderComponentCreator.h new file mode 100644 index 000000000..09b3379a2 --- /dev/null +++ b/libunwindstack/tests/fuzz/UnwinderComponentCreator.h @@ -0,0 +1,83 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBUNWINDSTACK_UNWINDERCOMPONENTCREATOR_H +#define _LIBUNWINDSTACK_UNWINDERCOMPONENTCREATOR_H + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../ElfFake.h" +#include "../MemoryFake.h" + +#include "fuzzer/FuzzedDataProvider.h" + +using unwindstack::ArchEnum; +using unwindstack::DexFiles; +using unwindstack::Elf; +using unwindstack::ElfFake; +using unwindstack::ElfInterfaceFake; +using unwindstack::FunctionData; +using unwindstack::Maps; +using unwindstack::Memory; +using unwindstack::MemoryFake; +using unwindstack::Regs; +using unwindstack::StepData; + +static constexpr uint8_t kArchCount = 6; + +static constexpr uint8_t kMaxSoNameLen = 150; + +static constexpr uint8_t kMaxFuncNameLen = 50; +static constexpr uint8_t kMaxFuncCount = 100; + +static constexpr uint8_t kMaxJitElfFiles = 20; +static constexpr uint8_t kJitElfPadding = 32; + +static constexpr uint8_t kMaxStepCount = 100; +static constexpr uint8_t kMaxMapEntryCount = 50; +static constexpr uint8_t kMaxBuildIdLen = 100; +static constexpr uint8_t kMaxMapInfoNameLen = 150; + +std::unique_ptr GetRegisters(unwindstack::ArchEnum arch); +std::unique_ptr GetMaps(FuzzedDataProvider* data_provider); +std::vector GetStringList(FuzzedDataProvider* data_provider, uint max_str_len, + uint max_strings); +unwindstack::ArchEnum GetArch(FuzzedDataProvider* data_provider); + +void AddMapInfo(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, const char* name, + Elf* elf = nullptr); +void PutElfFilesInMemory(MemoryFake* memory, FuzzedDataProvider* data_provider); + +std::unique_ptr GetDexFiles(FuzzedDataProvider* data_provider, + std::shared_ptr memory, + uint max_libraries, uint max_library_length); +#endif // _LIBUNWINDSTACK_UNWINDERCOMPONENTCREATOR_H diff --git a/libunwindstack/tests/fuzz/UnwinderFuzz.cpp b/libunwindstack/tests/fuzz/UnwinderFuzz.cpp new file mode 100644 index 000000000..2f4986a19 --- /dev/null +++ b/libunwindstack/tests/fuzz/UnwinderFuzz.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "../MemoryFake.h" +#include "UnwinderComponentCreator.h" +#include "fuzzer/FuzzedDataProvider.h" + +namespace unwindstack { + +static constexpr int kMaxUnwindStringLen = 50; +static constexpr int kMaxUnwindStrings = 50; + +void PerformUnwind(FuzzedDataProvider* data_provider, Unwinder* unwinder) { + // 0 = don't set any values + // 1 = set initial_map_names_to_skip + // 2 = set map_suffixes_to_ignore + // 3 = set both + uint8_t set_values = data_provider->ConsumeIntegral() % 4; + if (set_values == 0) { + unwinder->Unwind(); + } else if (set_values == 1) { + // Only setting initial_map_names_to_skip + std::vector skip_names = + GetStringList(data_provider, kMaxUnwindStringLen, kMaxUnwindStrings); + + unwinder->Unwind(&skip_names, nullptr); + } else if (set_values == 2) { + // Only setting map_suffixes_to_ignore + std::vector ignore_suffixes = + GetStringList(data_provider, kMaxUnwindStringLen, kMaxUnwindStrings); + + unwinder->Unwind(nullptr, &ignore_suffixes); + } else if (set_values == 3) { + // Setting both values + std::vector skip_names = + GetStringList(data_provider, kMaxUnwindStringLen, kMaxUnwindStrings); + std::vector ignore_suffixes = + GetStringList(data_provider, kMaxUnwindStringLen, kMaxUnwindStrings); + + unwinder->Unwind(&skip_names, &ignore_suffixes); + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider data_provider(data, size); + + // We need to construct an unwinder. + // Generate the Maps: + std::unique_ptr maps = GetMaps(&data_provider); + + // Generate the Regs: + uint8_t arch_val = data_provider.ConsumeIntegralInRange(1, kArchCount); + ArchEnum arch = static_cast(arch_val); + std::unique_ptr regs = GetRegisters(arch); + + // Generate memory: + std::shared_ptr memory = std::make_shared(); + PutElfFilesInMemory(reinterpret_cast(memory.get()), &data_provider); + + size_t max_frames = data_provider.ConsumeIntegralInRange(0, 5000); + + std::unique_ptr jit_debug_ptr = std::make_unique(memory); + + // Create instance + Unwinder unwinder(max_frames, maps.get(), regs.get(), memory); + unwinder.SetJitDebug(jit_debug_ptr.get(), arch); + unwinder.SetResolveNames(data_provider.ConsumeBool()); + // Call unwind + PerformUnwind(&data_provider, &unwinder); + + // Run some additional logic that changes after unwind + uint64_t pc = data_provider.ConsumeIntegral(); + unwinder.BuildFrameFromPcOnly(pc); + unwinder.ConsumeFrames(); + return 0; +} +} // namespace unwindstack