Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions absl/debugging/symbolize_elf.inc
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,15 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol(
return SYMBOL_NOT_FOUND;
}

// sh_entsize comes straight from the object file. A crafted symbol table
// can set it to zero (making the division below undefined) or to a value
// other than the entry size we read below, which would desynchronize the
// fixed-size reads from the per-entry stride. Reject it, mirroring the
// e_shentsize check we apply to section headers.
if (symtab->sh_entsize != sizeof(ElfW(Sym))) {
return SYMBOL_NOT_FOUND;
}

// Read multiple symbols at once to save read() calls.
ElfW(Sym) *buf = reinterpret_cast<ElfW(Sym) *>(tmp_buf);
const size_t buf_entries = tmp_buf_size / sizeof(buf[0]);
Expand Down
99 changes: 99 additions & 0 deletions absl/debugging/symbolize_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,17 @@
#ifndef _WIN32
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#endif

#include <cerrno>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
Expand Down Expand Up @@ -438,6 +444,99 @@ TEST(Symbolize, ForEachSection) {

close(fd);
}

#if defined(ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE)
// Builds a minimal ELF image whose SHT_SYMTAB section header carries the given
// sh_entsize. Everything else is just enough to reach FindSymbol(): a single
// executable PT_LOAD segment (so the object is initialized) and a symbol table
// whose sh_link points at the null section header.
static std::string MakeElfWithSymtabEntSize(uint64_t sym_entsize) {
ElfW(Ehdr) ehdr;
memset(&ehdr, 0, sizeof(ehdr));
memcpy(ehdr.e_ident, ELFMAG, SELFMAG);
ehdr.e_ident[EI_CLASS] = (sizeof(void *) == 8) ? ELFCLASS64 : ELFCLASS32;
ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
ehdr.e_ident[EI_VERSION] = EV_CURRENT;
ehdr.e_type = ET_DYN;
ehdr.e_version = EV_CURRENT;
ehdr.e_phoff = sizeof(ElfW(Ehdr));
ehdr.e_phentsize = sizeof(ElfW(Phdr));
ehdr.e_phnum = 1;
ehdr.e_shoff = sizeof(ElfW(Ehdr)) + sizeof(ElfW(Phdr));
ehdr.e_shentsize = sizeof(ElfW(Shdr));
ehdr.e_shnum = 2;
ehdr.e_shstrndx = 0;

ElfW(Phdr) phdr;
memset(&phdr, 0, sizeof(phdr));
phdr.p_type = PT_LOAD;
phdr.p_flags = PF_R | PF_X;
phdr.p_filesz = 0x1000;
phdr.p_memsz = 0x1000;
phdr.p_align = 0x1000;

// Section 0 is the mandatory null header, which also serves as the string
// table referenced by sh_link below (its contents are irrelevant here).
ElfW(Shdr) null_shdr;
memset(&null_shdr, 0, sizeof(null_shdr));

ElfW(Shdr) symtab;
memset(&symtab, 0, sizeof(symtab));
symtab.sh_type = SHT_SYMTAB;
symtab.sh_link = 0;
symtab.sh_size = sizeof(ElfW(Sym));
symtab.sh_entsize = sym_entsize;
symtab.sh_offset = ehdr.e_shoff + 2 * sizeof(ElfW(Shdr));

std::string image;
image.append(reinterpret_cast<const char *>(&ehdr), sizeof(ehdr));
image.append(reinterpret_cast<const char *>(&phdr), sizeof(phdr));
image.append(reinterpret_cast<const char *>(&null_shdr), sizeof(null_shdr));
image.append(reinterpret_cast<const char *>(&symtab), sizeof(symtab));
// Pad so the symtab sh_offset lands inside the file.
image.resize(image.size() + sizeof(ElfW(Sym)), '\0');
return image;
}

// A crafted object file can set the symbol table's sh_entsize to zero, which is
// used verbatim as the divisor for the symbol count in FindSymbol(). Before the
// fix this divided by zero (SIGFPE) while symbolizing; now the malformed table
// is skipped and the address simply fails to resolve.
TEST(Symbolize, InvalidSymtabEntSizeDoesNotCrash) {
const std::string image = MakeElfWithSymtabEntSize(/*sym_entsize=*/0);

std::string tmpl = testing::TempDir();
if (tmpl.empty() || tmpl.back() != '/') tmpl.push_back('/');
tmpl += "absl_bad_symtab_XXXXXX";
std::vector<char> path(tmpl.begin(), tmpl.end());
path.push_back('\0');

int fd = mkstemp(path.data());
ASSERT_NE(fd, -1);
absl::Cleanup unlink_file = [&] { unlink(path.data()); };
ASSERT_EQ(write(fd, image.data(), image.size()),
static_cast<ssize_t>(image.size()));
close(fd);

// Map an executable page so the region shows up in /proc/self/maps, then point
// the symbolizer at the malformed file for that range via a mapping hint.
const size_t page = static_cast<size_t>(sysconf(_SC_PAGESIZE));
void *region = mmap(nullptr, page, PROT_READ | PROT_EXEC,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (region == MAP_FAILED) {
GTEST_SKIP() << "Unable to map an executable page: errno=" << errno;
}
absl::Cleanup unmap = [&] { munmap(region, page); };

ASSERT_TRUE(absl::debugging_internal::RegisterFileMappingHint(
region, static_cast<char *>(region) + page, 0, path.data()));

char buf[512];
// The point of the test is that this returns instead of crashing with SIGFPE.
EXPECT_FALSE(absl::Symbolize(static_cast<char *>(region) + 8, buf,
sizeof(buf)));
}
#endif // ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE
#endif // !ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE &&
// !ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE

Expand Down