blob: c94a5205bd949f29c5829e04fbac6b00cda8e23c [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/lib/elflib/elflib.h"
#include <limits.h>
#include <unistd.h>
#if defined(__APPLE__)
#include <mach-o/dyld.h>
#endif
#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <string_view>
#include <gtest/gtest.h>
namespace elflib {
namespace {
constexpr uint64_t kAddrPoison = 0xdeadb33ff00db4b3;
constexpr uint64_t kSymbolPoison = 0xb0bab0ba;
constexpr uint64_t kNoteGnuBuildId = 3;
constexpr uint64_t kMeaninglessNoteType = 42;
// The test files will be copied over to this specific location at build time.
constexpr std::string_view kStrippedExampleFile = "stripped_example.elf";
constexpr std::string_view kUnstrippedExampleFileBase = "unstripped_example";
constexpr std::string_view kUnstrippedExampleFileStrippedBase = "unstripped_example_stripped";
inline std::string GetTestBinaryPath(std::string_view bin) {
#if defined(__Fuchsia__)
return std::string("/pkg/data/") + std::string(bin);
#else
// host tests will be invoked from the build directory.
return std::string("../../src/lib/elflib/test_data/") + std::string(bin);
#endif
}
class TestData {
public:
TestData(bool with_symbols) {
PushData(Elf64_Ehdr{
.e_ident = {0, 0, 0, 0, ELFCLASS64, ELFDATA2LSB, EV_CURRENT},
.e_version = EV_CURRENT,
.e_shoff = sizeof(Elf64_Ehdr),
.e_ehsize = sizeof(Elf64_Ehdr),
.e_phentsize = sizeof(Elf64_Phdr),
.e_phnum = 2,
.e_shentsize = sizeof(Elf64_Shdr),
.e_shnum = 6,
.e_shstrndx = 0,
});
*DataAt<char>(0) = ElfMagic[0];
*DataAt<char>(1) = ElfMagic[1];
*DataAt<char>(2) = ElfMagic[2];
*DataAt<char>(3) = ElfMagic[3];
size_t shstrtab_hdr = PushData(Elf64_Shdr{
.sh_name = 1,
.sh_type = SHT_STRTAB,
.sh_addr = kAddrPoison,
.sh_size = 34,
});
size_t stuff_hdr = PushData(Elf64_Shdr{
.sh_name = 11,
.sh_type = SHT_LOUSER,
.sh_addr = kAddrPoison,
.sh_size = 15,
});
size_t strtab_hdr = PushData(Elf64_Shdr{
.sh_name = 18,
.sh_type = SHT_STRTAB,
.sh_addr = kAddrPoison,
.sh_size = 16,
});
size_t symtab_hdr = PushData(Elf64_Shdr{
.sh_name = 26,
.sh_type = SHT_SYMTAB,
.sh_addr = kAddrPoison,
.sh_size = sizeof(Elf64_Sym),
});
PushData(Elf64_Shdr{
.sh_name = 34,
.sh_type = SHT_NULL,
.sh_addr = kAddrPoison,
.sh_size = 0,
});
PushData(Elf64_Shdr{
.sh_name = 40,
.sh_type = SHT_NOBITS,
.sh_addr = kAddrPoison,
.sh_size = 0,
});
size_t phnote_hdr = PushData(Elf64_Phdr{
.p_type = PT_NOTE,
.p_vaddr = kAddrPoison,
});
DataAt<Elf64_Ehdr>(0)->e_phoff = phnote_hdr;
if (with_symbols) {
DataAt<Elf64_Shdr>(shstrtab_hdr)->sh_offset =
PushData("\0.shstrtab\0.stuff\0.strtab\0.symtab\0.null\0.nobits\0", 48);
}
DataAt<Elf64_Shdr>(stuff_hdr)->sh_offset = PushData("This is a test.", 15);
if (with_symbols) {
DataAt<Elf64_Shdr>(strtab_hdr)->sh_offset = PushData("\0zx_frob_handle\0", 16);
}
DataAt<Elf64_Shdr>(symtab_hdr)->sh_offset = PushData(Elf64_Sym{
.st_name = 1,
.st_shndx = SHN_COMMON,
.st_value = kSymbolPoison,
.st_size = 0,
});
size_t buildid_nhdr =
PushData(Elf64_Nhdr{.n_namesz = 4, .n_descsz = 32, .n_type = kNoteGnuBuildId});
DataAt<Elf64_Phdr>(phnote_hdr)->p_offset = buildid_nhdr;
PushData("GNU\0", 4);
uint8_t desc_data[32] = {
0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7,
};
PushData(desc_data, 32);
PushData(Elf64_Nhdr{
.n_namesz = 6,
.n_descsz = 3,
.n_type = kMeaninglessNoteType,
});
PushData("seven\0\0\0", 8);
PushData("foo\0", 4);
DataAt<Elf64_Phdr>(phnote_hdr)->p_filesz = Pos() - buildid_nhdr;
DataAt<Elf64_Phdr>(phnote_hdr)->p_memsz = Pos() - buildid_nhdr;
}
template <typename T>
T* DataAt(size_t offset) {
return reinterpret_cast<T*>(content_.data() + offset);
}
template <typename T>
size_t PushData(T data) {
return PushData(reinterpret_cast<uint8_t*>(&data), sizeof(data));
}
size_t PushData(const char* bytes, size_t size) {
return PushData(reinterpret_cast<const uint8_t*>(bytes), size);
}
size_t PushData(const uint8_t* bytes, size_t size) {
size_t offset = Pos();
std::copy(bytes, bytes + size, std::back_inserter(content_));
return offset;
}
size_t Pos() { return content_.size(); }
size_t Size() { return content_.size(); }
const uint8_t* Data() { return content_.data(); }
std::function<bool(uint64_t, std::vector<uint8_t>*)> GetFetcher() {
return [this](uint64_t offset, std::vector<uint8_t>* out) {
if (offset + out->size() > content_.size()) {
return false;
}
std::copy(content_.begin() + offset, content_.begin() + offset + out->size(), out->begin());
return true;
};
}
private:
std::vector<uint8_t> content_;
};
TEST(ElfLib, Create) {
TestData t(/*with_symbols=*/true);
std::unique_ptr<ElfLib> got;
EXPECT_NE(ElfLib::Create(t.Data(), t.Size()).get(), nullptr);
}
TEST(ElfLib, GetSection) {
TestData t(/*with_symbols=*/true);
std::unique_ptr<ElfLib> elf = ElfLib::Create(t.Data(), t.Size());
ASSERT_NE(elf.get(), nullptr);
auto data = elf->GetSectionData(".stuff");
const uint8_t* expected_content = reinterpret_cast<const uint8_t*>("This is a test.");
ASSERT_NE(data.ptr, nullptr);
auto expect = std::vector<uint8_t>(expected_content, expected_content + 15);
auto got = std::vector<uint8_t>(data.ptr, data.ptr + data.size);
EXPECT_EQ(expect, got);
}
TEST(ElfLib, GetSymbolValue) {
TestData t(/*with_symbols=*/true);
std::unique_ptr<ElfLib> elf = ElfLib::Create(t.Data(), t.Size());
ASSERT_NE(elf.get(), nullptr);
// TODO(sadmac): GetSymbol should return properly aligned data.
auto data = elf->GetSymbol("zx_frob_handle");
ASSERT_TRUE(data);
Elf64_Sym aligned_data;
memcpy(&aligned_data, reinterpret_cast<const std::byte*>(data), sizeof(aligned_data));
EXPECT_EQ(kSymbolPoison, aligned_data.st_value);
}
TEST(ElfLib, GetSymbolValueFromDebug) {
TestData t1(/*with_symbols=*/false);
TestData t2(/*with_symbols=*/true);
std::unique_ptr<ElfLib> elf = ElfLib::Create(t1.Data(), t1.Size());
std::unique_ptr<ElfLib> debug = ElfLib::Create(t2.Data(), t2.Size());
elf->SetDebugData(std::move(debug));
ASSERT_NE(elf.get(), nullptr);
auto data = elf->GetSymbol("zx_frob_handle");
ASSERT_TRUE(data);
Elf64_Sym aligned_data;
memcpy(&aligned_data, reinterpret_cast<const std::byte*>(data), sizeof(aligned_data));
EXPECT_EQ(kSymbolPoison, aligned_data.st_value);
}
TEST(ElfLib, GetAllSymbols) {
TestData t(/*with_symbols=*/true);
std::unique_ptr<ElfLib> elf = ElfLib::Create(t.Data(), t.Size());
ASSERT_NE(elf.get(), nullptr);
auto syms = elf->GetAllSymbols();
ASSERT_TRUE(syms);
EXPECT_EQ(1U, syms->size());
Elf64_Sym sym = (*syms)["zx_frob_handle"];
EXPECT_EQ(1U, sym.st_name);
EXPECT_EQ(0U, sym.st_size);
EXPECT_EQ(SHN_COMMON, sym.st_shndx);
EXPECT_EQ(kSymbolPoison, sym.st_value);
}
TEST(ElfLib, GetNote) {
TestData t(/*with_symbols=*/true);
std::unique_ptr<ElfLib> elf = ElfLib::Create(t.Data(), t.Size());
ASSERT_NE(elf.get(), nullptr);
auto got = elf->GetNote("GNU", kNoteGnuBuildId);
EXPECT_TRUE(got);
auto data = *got;
EXPECT_EQ(32U, data.size());
for (size_t i = 0; i < 32; i++) {
EXPECT_EQ(i % 8, data[i]);
}
EXPECT_EQ("0001020304050607000102030405060700010203040506070001020304050607",
elf->GetGNUBuildID());
}
TEST(ElfLib, MissingSections) {
TestData t(/*with_symbols=*/true);
std::unique_ptr<ElfLib> elf = ElfLib::Create(t.Data(), t.Size());
ASSERT_NE(elf.get(), nullptr);
auto got = elf->GetSectionData(".null");
EXPECT_EQ(got.ptr, nullptr);
got = elf->GetSectionData(".nobits");
EXPECT_EQ(got.ptr, nullptr);
}
TEST(ElfLib, GetIrregularNote) {
TestData t(/*with_symbols=*/true);
std::unique_ptr<ElfLib> elf = ElfLib::Create(t.Data(), t.Size());
ASSERT_NE(elf.get(), nullptr);
auto got = elf->GetNote("seven", kMeaninglessNoteType);
EXPECT_TRUE(got);
auto data = *got;
EXPECT_EQ(3U, data.size());
EXPECT_EQ("foo", std::string(data.data(), data.data() + 3));
}
TEST(ElfLib, GetSymbolsFromStripped) {
std::unique_ptr<ElfLib> elf = ElfLib::Create(GetTestBinaryPath(kStrippedExampleFile));
ASSERT_NE(elf.get(), nullptr);
auto missing_syms = elf->GetAllSymbols();
EXPECT_FALSE(missing_syms);
auto syms = elf->GetAllDynamicSymbols();
ASSERT_TRUE(syms);
EXPECT_EQ(8U, syms->size());
std::map<std::string, Elf64_Sym>::iterator it;
it = syms->find("");
EXPECT_NE(it, syms->end());
it = syms->find("__bss_start");
EXPECT_NE(it, syms->end());
it = syms->find("__libc_start_main");
EXPECT_NE(it, syms->end());
it = syms->find("__scudo_default_options");
EXPECT_NE(it, syms->end());
it = syms->find("_edata");
EXPECT_NE(it, syms->end());
it = syms->find("_end");
EXPECT_NE(it, syms->end());
it = syms->find("printf");
EXPECT_NE(it, syms->end());
it = syms->find("strlen");
EXPECT_NE(it, syms->end());
}
void GetPLTOffsetsTest(std::string_view elf_base, std::string_view rel_suffix,
std::string_view debug_base = {}) {
ASSERT_FALSE(elf_base.empty());
struct Case {
std::string_view machine_suffix;
uint64_t printf_plt, strlen_plt;
};
constexpr Case kCases[] = {
{".x64", 0x1080, 0x1070},
{".arm64", 0x10b0, 0x10a0},
};
for (auto [machine_suffix, printf_plt, strlen_plt] : kCases) {
const std::string suffix = std::string(machine_suffix) + std::string(rel_suffix) + ".elf";
std::unique_ptr<ElfLib> elf = ElfLib::Create(GetTestBinaryPath(std::string(elf_base) + suffix));
ASSERT_NE(elf.get(), nullptr) << suffix;
if (!debug_base.empty()) {
std::unique_ptr<ElfLib> debug =
ElfLib::Create(GetTestBinaryPath(std::string(debug_base) + suffix));
ASSERT_NE(debug.get(), nullptr) << suffix;
ASSERT_TRUE(elf->SetDebugData(std::move(debug)));
}
auto plt = elf->GetPLTOffsets();
EXPECT_GE(plt.size(), 2u) << suffix;
EXPECT_EQ(printf_plt, plt["printf"]) << suffix;
EXPECT_EQ(strlen_plt, plt["strlen"]) << suffix;
}
}
TEST(ElfLib, GetPLTFromUnstrippedRela) { GetPLTOffsetsTest(kUnstrippedExampleFileBase, ".rela"); }
TEST(ElfLib, GetPLTFromStrippedDebugRela) {
GetPLTOffsetsTest(kUnstrippedExampleFileStrippedBase, ".rela", kUnstrippedExampleFileBase);
}
TEST(ElfLib, GetPLTFromUnstrippedRel) { GetPLTOffsetsTest(kUnstrippedExampleFileBase, ".rel"); }
TEST(ElfLib, GetPLTFromStrippedDebugRel) {
GetPLTOffsetsTest(kUnstrippedExampleFileStrippedBase, ".rel", kUnstrippedExampleFileBase);
}
TEST(ElfLib, DetectUnstripped) {
std::unique_ptr<ElfLib> elf =
ElfLib::Create(GetTestBinaryPath(std::string(kUnstrippedExampleFileBase) + ".x64.rela.elf"));
ASSERT_NE(elf.get(), nullptr);
EXPECT_TRUE(elf->ProbeHasDebugInfo());
EXPECT_TRUE(elf->ProbeHasProgramBits());
}
TEST(ElfLib, DetectStripped) {
std::unique_ptr<ElfLib> elf = ElfLib::Create(
GetTestBinaryPath(std::string(kUnstrippedExampleFileStrippedBase) + ".x64.rela.elf"));
ASSERT_NE(elf.get(), nullptr);
EXPECT_FALSE(elf->ProbeHasDebugInfo());
EXPECT_TRUE(elf->ProbeHasProgramBits());
}
TEST(ElfLib, SectionOverflow) {
// This test case was found by clusterfuzz. This is the reproducer it found. The function of the
// test is to have a section with a size and offset that, when added together, result in an
// overflow. This can break bounds checking and hopefully trick us into an out of bounds read.
const uint8_t kData[] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0xe2, 0x01, 0xff, 0x05, 0xff, 0xff, 0x5b, 0xff, 0x00,
0x9a, 0x00, 0x00, 0x00, 0x45, 0x5b, 0x01, 0x00, 0x00, 0x00, 0xf6, 0x05, 0x9f, 0x9f,
0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00,
0x00, 0x00, 0x40, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff,
};
std::unique_ptr<ElfLib> elf = ElfLib::Create(kData, 84);
ASSERT_NE(elf.get(), nullptr);
EXPECT_FALSE(elf->ProbeHasDebugInfo());
EXPECT_FALSE(elf->ProbeHasProgramBits());
EXPECT_EQ(nullptr, elf->GetSectionData("bogus").ptr);
EXPECT_EQ(0u, elf->GetSegmentHeaders().size());
EXPECT_FALSE(elf->GetAllSymbols());
EXPECT_FALSE(elf->GetAllDynamicSymbols());
EXPECT_EQ(0u, elf->GetPLTOffsets().size());
auto warnings = elf->GetAndClearWarnings();
ASSERT_EQ(1u, warnings.size());
EXPECT_EQ("Architecture doesn't support GetPLTOffsets.", warnings[0]);
}
// Checks that we can load a library which has a big plt (535 entries) on AArch64.
TEST(ElfLib, AArch64Plt) {
std::unique_ptr<ElfLib> elf =
ElfLib::Create(GetTestBinaryPath(std::string("6d4d8ac190ecc7.debug")));
ASSERT_NE(elf.get(), nullptr);
auto plt = elf->GetPLTOffsets();
auto warnings = elf->GetAndClearWarnings();
for (const auto& warning : warnings) {
std::cout << warning << '\n';
}
ASSERT_EQ(plt.size(), 535U);
ASSERT_NE(plt.find("_zx_channel_create"), plt.end());
EXPECT_EQ(642864U, plt["_zx_channel_create"]);
ASSERT_NE(plt.find("_zx_channel_read"), plt.end());
EXPECT_EQ(651120U, plt["_zx_channel_read"]);
ASSERT_NE(plt.find("_zx_channel_write"), plt.end());
EXPECT_EQ(642848U, plt["_zx_channel_write"]);
ASSERT_TRUE(warnings.empty());
}
TEST(ElfLib, GetSharedObjectDependencies) {
std::unique_ptr<ElfLib> elf =
ElfLib::Create(GetTestBinaryPath(std::string(kStrippedExampleFile)));
ASSERT_NE(elf.get(), nullptr);
auto objects = elf->GetSharedObjectDependencies();
ASSERT_TRUE(objects);
auto& object_vec = objects.value();
ASSERT_EQ(object_vec.size(), 3u);
EXPECT_EQ(object_vec[0], "libclang_rt.scudo.so");
EXPECT_EQ(object_vec[1], "libfdio.so");
EXPECT_EQ(object_vec[2], "libc.so");
}
} // namespace
} // namespace elflib