diff --git a/folly/debugging/symbolizer/Elf.cpp b/folly/debugging/symbolizer/Elf.cpp index d28b144ead6..8dbf06ef3ad 100644 --- a/folly/debugging/symbolizer/Elf.cpp +++ b/folly/debugging/symbolizer/Elf.cpp @@ -51,6 +51,26 @@ namespace folly { namespace symbolizer { +namespace detail { + +folly::StringPiece getNullTerminatedPathComponent( + folly::StringPiece section) noexcept { + auto const* begin = section.data(); + auto const* nul = + static_cast(memchr(begin, '\0', section.size())); + if (nul == nullptr || nul == begin) { + return {}; + } + auto const nameLen = static_cast(nul - begin); + if (memchr(begin, '/', nameLen) != nullptr || + memchr(begin, '\\', nameLen) != nullptr) { + return {}; + } + return folly::StringPiece(begin, nameLen); +} + +} // namespace detail + ElfFile::ElfFile() noexcept : fd_(-1), file_(static_cast(MAP_FAILED)), @@ -144,15 +164,21 @@ ElfFile::OpenResult ElfFile::openAndFollow( // The section starts with the filename, with any leading directory // components removed, followed by a zero byte. - auto debugFileName = getSectionBody(*debuginfo); - auto debugFileLen = strlen(debugFileName.begin()); - if (dirlen + debugFileLen >= PATH_MAX) { + auto debugFileName = + detail::getNullTerminatedPathComponent(getSectionBody(*debuginfo)); + if (debugFileName.empty()) { + return result; + } + + auto debugFileLen = debugFileName.size(); + if (dirlen + debugFileLen + 1 > PATH_MAX) { return result; } char linkname[PATH_MAX]; memcpy(linkname, name, dirlen); - memcpy(linkname + dirlen, debugFileName.begin(), debugFileLen + 1); + memcpy(linkname + dirlen, debugFileName.data(), debugFileLen); + linkname[dirlen + debugFileLen] = '\0'; reset(); result = openNoThrow(linkname, options); if (result == kSuccess) { diff --git a/folly/debugging/symbolizer/Elf.h b/folly/debugging/symbolizer/Elf.h index f2484591a7d..a5da7ec8321 100644 --- a/folly/debugging/symbolizer/Elf.h +++ b/folly/debugging/symbolizer/Elf.h @@ -43,6 +43,16 @@ namespace folly { namespace symbolizer { +namespace detail { + +// Extract and validate a filename-like C-string from section data, returning +// empty on malformed input (missing null terminator, empty name, or directory +// separator in the prefix). +folly::StringPiece getNullTerminatedPathComponent( + folly::StringPiece section) noexcept; + +} // namespace detail + #if defined(ElfW) #define FOLLY_ELF_ELFW(name) ElfW(name) #elif defined(__FreeBSD__) diff --git a/folly/debugging/symbolizer/test/ElfTest.cpp b/folly/debugging/symbolizer/test/ElfTest.cpp index ebfbb10c73e..36d2cfb6583 100644 --- a/folly/debugging/symbolizer/test/ElfTest.cpp +++ b/folly/debugging/symbolizer/test/ElfTest.cpp @@ -184,6 +184,27 @@ TEST_F(ElfTest, FailToOpenLargeFilename) { EXPECT_EQ(ElfFile::kSuccess, elfFile->openNoThrow(kDefaultElf)); } +TEST(TestDebugLinkParsing, ParsesNullTerminatedName) { + std::string raw("debug.bin\0ignored", 17); + auto const parsed = folly::symbolizer::detail::getNullTerminatedPathComponent( + folly::StringPiece(raw.data(), raw.size())); + EXPECT_EQ("debug.bin", parsed); +} + +TEST(TestDebugLinkParsing, RejectsMissingNullTerminator) { + std::string raw("debug.bin", 9); + auto const parsed = folly::symbolizer::detail::getNullTerminatedPathComponent( + folly::StringPiece(raw.data(), raw.size())); + EXPECT_TRUE(parsed.empty()); +} + +TEST(TestDebugLinkParsing, RejectsDirectoryTraversalLikeNames) { + std::string raw("../debug.bin\0", 13); + auto const parsed = folly::symbolizer::detail::getNullTerminatedPathComponent( + folly::StringPiece(raw.data(), raw.size())); + EXPECT_TRUE(parsed.empty()); +} + TEST(TestGetNoteGnuBuildId, SimpleElf) { auto const file = folly::test::find_resource("folly/debugging/symbolizer/test/simple_elf");