lldb: backport export trie fixes to LLDB 22 and older

Older versions of LLDB crash on macOS 27 when starting a debugging
session on macOS 27 due to a stack overflow in `ParseExportTries`.

The fixes from LLVM 23 can’t be cherry-picked due to other changes. They
have been manually backported and squashed into a single patch.

(cherry picked from commit 53b813b166)
This commit is contained in:
Randy Eckenrode
2026-08-09 08:38:12 -04:00
committed by Ihar Hrachyshka
parent 9c847cd37c
commit 51db35b37c
4 changed files with 264 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
diff --git a/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
index 2c7005449f..e0c426afa0 100644
--- a/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
+++ b/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
@@ -137,6 +137,14 @@
using namespace lldb_private;
using namespace llvm::MachO;
+/// Upper bound on the length of a symbol name assembled from export-trie edge
+/// labels. A corrupt trie can encode an edge label whose terminator is far
+/// away in the trie data, so a single label is many megabytes long; appending
+/// it to the running name would otherwise request an unbounded allocation. No
+/// legitimate symbol name comes close to this size. Also 1 MiB is the
+/// symbol length limit in ld.
+static constexpr size_t kMaxTrieSymbolNameLength = 1 << 20; // 1 MiB
+
LLDB_PLUGIN_DEFINE(ObjectFileMachO)
static void PrintRegisterValue(RegisterContext *reg_ctx, const char *name,
@@ -2050,15 +2058,21 @@
}
};
-static bool ParseTrieEntries(DataExtractor &data, lldb::offset_t offset,
- const bool is_arm, addr_t text_seg_base_addr,
- std::vector<llvm::StringRef> &nameSlices,
- std::set<lldb::addr_t> &resolver_addresses,
- std::vector<TrieEntryWithOffset> &reexports,
- std::vector<TrieEntryWithOffset> &ext_symbols) {
+static bool ParseTrieEntriesImpl(DataExtractor &data, lldb::offset_t offset,
+ const bool is_arm, addr_t text_seg_base_addr,
+ std::string &prefix,
+ std::set<lldb::addr_t> &resolver_addresses,
+ std::vector<TrieEntryWithOffset> &reexports,
+ std::vector<TrieEntryWithOffset> &ext_symbols,
+ std::set<lldb::offset_t> &visited_nodes) {
if (!data.ValidOffset(offset))
return true;
+ // Every node in a well-formed trie is reached by exactly one path, so a node
+ // offset seen twice means the trie is corrupt.
+ if (!visited_nodes.insert(offset).second)
+ return false;
+
// Terminal node -- end of a branch, possibly add this to
// the symbol table or resolver table.
const uint64_t terminalSize = data.GetULEB128(&offset);
@@ -2098,14 +2112,9 @@
add_this_entry = true;
}
if (add_this_entry) {
- std::string name;
- if (!nameSlices.empty()) {
- for (auto name_slice : nameSlices)
- name.append(name_slice.data(), name_slice.size());
- }
- if (name.size() > 1) {
+ if (prefix.size() > 1) {
// Skip the leading '_'
- e.entry.name.SetCStringWithLength(name.c_str() + 1, name.size() - 1);
+ e.entry.name.SetString(llvm::StringRef(prefix).drop_front());
}
if (import_name) {
// Skip the leading '_'
@@ -2126,23 +2135,36 @@
const uint8_t childrenCount = data.GetU8(&children_offset);
for (uint8_t i = 0; i < childrenCount; ++i) {
const char *cstr = data.GetCStr(&children_offset);
- if (cstr)
- nameSlices.push_back(llvm::StringRef(cstr));
- else
+ if (!cstr)
return false; // Corrupt data
+ if (prefix.size() + llvm::StringRef(cstr).size() > kMaxTrieSymbolNameLength)
+ return false; // Corrupt data: implausibly long symbol name.
+ const size_t prevSize = prefix.size();
+ prefix.append(cstr);
lldb::offset_t childNodeOffset = data.GetULEB128(&children_offset);
- if (childNodeOffset) {
- if (!ParseTrieEntries(data, childNodeOffset, is_arm, text_seg_base_addr,
- nameSlices, resolver_addresses, reexports,
- ext_symbols)) {
- return false;
- }
- }
- nameSlices.pop_back();
+ // A child offset of 0 points back at the root; like any other repeated
+ // offset it is a cycle, which ParseTrieEntriesImpl rejects as corrupt.
+ if (!ParseTrieEntriesImpl(data, childNodeOffset, is_arm, text_seg_base_addr,
+ prefix, resolver_addresses, reexports,
+ ext_symbols, visited_nodes))
+ return false;
+ prefix.resize(prevSize);
}
return true;
}
+static bool ParseTrieEntries(
+ DataExtractor &data, const bool is_arm, lldb::addr_t text_seg_base_addr,
+ std::set<lldb::addr_t> &resolver_addresses,
+ std::vector<TrieEntryWithOffset> &reexports,
+ std::vector<TrieEntryWithOffset> &ext_symbols) {
+ lldb::offset_t offset = 0;
+ std::set<lldb::offset_t> visited_nodes;
+ std::string prefix;
+ return ParseTrieEntriesImpl(data, offset, is_arm, text_seg_base_addr, prefix,
+ resolver_addresses, reexports, ext_symbols,
+ visited_nodes);
+}
static SymbolType GetSymbolType(const char *&symbol_name,
bool &demangled_is_synthesized,
const SectionSP &text_section_sp,
@@ -2666,9 +2688,8 @@
lldb::addr_t text_segment_file_addr = LLDB_INVALID_ADDRESS;
if (text_segment_sp)
text_segment_file_addr = text_segment_sp->GetFileAddress();
- std::vector<llvm::StringRef> nameSlices;
- ParseTrieEntries(dyld_trie_data, 0, is_arm, text_segment_file_addr,
- nameSlices, resolver_addresses, reexport_trie_entries,
+ ParseTrieEntries(dyld_trie_data, is_arm, text_segment_file_addr,
+ resolver_addresses, reexport_trie_entries,
external_sym_trie_entries);
}

View File

@@ -0,0 +1,125 @@
diff --git a/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
index 0be7b4c6f8..f9d9a05920 100644
--- a/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
+++ b/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
@@ -137,6 +137,14 @@
static constexpr llvm::StringLiteral g_loader_path = "@loader_path";
static constexpr llvm::StringLiteral g_executable_path = "@executable_path";
+/// Upper bound on the length of a symbol name assembled from export-trie edge
+/// labels. A corrupt trie can encode an edge label whose terminator is far
+/// away in the trie data, so a single label is many megabytes long; appending
+/// it to the running name would otherwise request an unbounded allocation. No
+/// legitimate symbol name comes close to this size. Also 1 MiB is the
+/// symbol length limit in ld.
+static constexpr size_t kMaxTrieSymbolNameLength = 1 << 20; // 1 MiB
+
LLDB_PLUGIN_DEFINE(ObjectFileMachO)
static void PrintRegisterValue(RegisterContext *reg_ctx, const char *name,
@@ -1994,15 +2002,21 @@
}
};
-static bool ParseTrieEntries(DataExtractor &data, lldb::offset_t offset,
- const bool is_arm, addr_t text_seg_base_addr,
- std::vector<llvm::StringRef> &nameSlices,
- std::set<lldb::addr_t> &resolver_addresses,
- std::vector<TrieEntryWithOffset> &reexports,
- std::vector<TrieEntryWithOffset> &ext_symbols) {
+static bool ParseTrieEntriesImpl(DataExtractor &data, lldb::offset_t offset,
+ const bool is_arm, addr_t text_seg_base_addr,
+ std::string &prefix,
+ std::set<lldb::addr_t> &resolver_addresses,
+ std::vector<TrieEntryWithOffset> &reexports,
+ std::vector<TrieEntryWithOffset> &ext_symbols,
+ std::set<lldb::offset_t> &visited_nodes) {
if (!data.ValidOffset(offset))
return true;
+ // Every node in a well-formed trie is reached by exactly one path, so a node
+ // offset seen twice means the trie is corrupt.
+ if (!visited_nodes.insert(offset).second)
+ return false;
+
// Terminal node -- end of a branch, possibly add this to
// the symbol table or resolver table.
const uint64_t terminalSize = data.GetULEB128(&offset);
@@ -2042,14 +2056,9 @@
add_this_entry = true;
}
if (add_this_entry) {
- std::string name;
- if (!nameSlices.empty()) {
- for (auto name_slice : nameSlices)
- name.append(name_slice.data(), name_slice.size());
- }
- if (name.size() > 1) {
+ if (prefix.size() > 1) {
// Skip the leading '_'
- e.entry.name.SetCStringWithLength(name.c_str() + 1, name.size() - 1);
+ e.entry.name.SetString(llvm::StringRef(prefix).drop_front());
}
if (import_name) {
// Skip the leading '_'
@@ -2070,23 +2079,37 @@
const uint8_t childrenCount = data.GetU8(&children_offset);
for (uint8_t i = 0; i < childrenCount; ++i) {
const char *cstr = data.GetCStr(&children_offset);
- if (cstr)
- nameSlices.push_back(llvm::StringRef(cstr));
- else
+ if (!cstr)
return false; // Corrupt data
+ if (prefix.size() + llvm::StringRef(cstr).size() > kMaxTrieSymbolNameLength)
+ return false; // Corrupt data: implausibly long symbol name.
+ const size_t prevSize = prefix.size();
+ prefix.append(cstr);
lldb::offset_t childNodeOffset = data.GetULEB128(&children_offset);
- if (childNodeOffset) {
- if (!ParseTrieEntries(data, childNodeOffset, is_arm, text_seg_base_addr,
- nameSlices, resolver_addresses, reexports,
- ext_symbols)) {
- return false;
- }
- }
- nameSlices.pop_back();
+ // A child offset of 0 points back at the root; like any other repeated
+ // offset it is a cycle, which ParseTrieEntriesImpl rejects as corrupt.
+ if (!ParseTrieEntriesImpl(data, childNodeOffset, is_arm, text_seg_base_addr,
+ prefix, resolver_addresses, reexports,
+ ext_symbols, visited_nodes))
+ return false;
+ prefix.resize(prevSize);
}
return true;
}
+static bool ParseTrieEntries(
+ DataExtractor &data, const bool is_arm, lldb::addr_t text_seg_base_addr,
+ std::set<lldb::addr_t> &resolver_addresses,
+ std::vector<TrieEntryWithOffset> &reexports,
+ std::vector<TrieEntryWithOffset> &ext_symbols) {
+ lldb::offset_t offset = 0;
+ std::set<lldb::offset_t> visited_nodes;
+ std::string prefix;
+ return ParseTrieEntriesImpl(data, offset, is_arm, text_seg_base_addr, prefix,
+ resolver_addresses, reexports, ext_symbols,
+ visited_nodes);
+}
+
static bool
TryParseV2ObjCMetadataSymbol(const char *&symbol_name,
const char *&symbol_name_non_abi_mangled,
@@ -2659,9 +2682,8 @@
lldb::addr_t text_segment_file_addr = LLDB_INVALID_ADDRESS;
if (text_segment_sp)
text_segment_file_addr = text_segment_sp->GetFileAddress();
- std::vector<llvm::StringRef> nameSlices;
- ParseTrieEntries(dyld_trie_data, 0, is_arm, text_segment_file_addr,
- nameSlices, resolver_addresses, reexport_trie_entries,
+ ParseTrieEntries(dyld_trie_data, is_arm, text_segment_file_addr,
+ resolver_addresses, reexport_trie_entries,
external_sym_trie_entries);
}

View File

@@ -77,6 +77,10 @@ stdenv.mkDerivation (
# Fix build with gcc15
# https://github.com/llvm/llvm-project/commit/bb59f04e7e75dcbe39f1bf952304a157f0035314
./lldb-add-include-cstdint.patch
]
++ lib.optionals (lib.versionOlder (lib.versions.major release_version) "23") [
# Backports several fixes to export trie parsing. Otherwise, LLDB crashes when starting a debugging session on macOS 27.
(getVersionFile "lldb/backport-ParseTrieEntries-fixes.patch")
];
nativeBuildInputs = [

View File

@@ -20,6 +20,17 @@
path = ../18;
}
];
"lldb/backport-ParseTrieEntries-fixes.patch" = [
{
before = "22";
path = ../18;
}
{
after = "22";
before = "23";
path = ../22;
}
];
"llvm/backport-darwin-triple-parsing.patch" = [
{
after = "18";