diff --git a/absl/debugging/symbolize_elf.inc b/absl/debugging/symbolize_elf.inc index f0f02a1f762..814b65021d1 100644 --- a/absl/debugging/symbolize_elf.inc +++ b/absl/debugging/symbolize_elf.inc @@ -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(tmp_buf); const size_t buf_entries = tmp_buf_size / sizeof(buf[0]); diff --git a/absl/debugging/symbolize_test.cc b/absl/debugging/symbolize_test.cc index 2862394418c..f8a96d40d5f 100644 --- a/absl/debugging/symbolize_test.cc +++ b/absl/debugging/symbolize_test.cc @@ -25,11 +25,17 @@ #ifndef _WIN32 #include #include +#include #endif +#include +#include +#include #include #include #include +#include +#include #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -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(&ehdr), sizeof(ehdr)); + image.append(reinterpret_cast(&phdr), sizeof(phdr)); + image.append(reinterpret_cast(&null_shdr), sizeof(null_shdr)); + image.append(reinterpret_cast(&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 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(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(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(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(region) + 8, buf, + sizeof(buf))); +} +#endif // ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE #endif // !ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE && // !ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE