[Lldb-commits] [lldb] [lldb] Bound Mach-O symtab allocation to the file size (PR #222955)

Yao Qi via lldb-commits lldb-commits at lists.llvm.org
Fri Sep 11 07:28:10 PDT 2026


https://github.com/qiyao created https://github.com/llvm/llvm-project/pull/222955

Opening a Mach-O file whose LC_SYMTAB claims far more symbols than the
file could possibly hold makes lldb ask for hundreds of gigabytes.  A
128-byte file with LC_SYMTAB.nsyms = 0x80000000 (the new unit test's
input, built with yaml2obj) reproduces it through `lldb-target-fuzzer`,
the harness that found this bug:

```
==45515== ERROR: libFuzzer: out-of-memory (malloc(171798691840))
    #8  std::__1::__split_buffer<lldb_private::Symbol,...>::__split_buffer(...)
    #9  std::__1::vector<lldb_private::Symbol,...>::resize(unsigned long)
    #10 lldb_private::Symtab::Resize(unsigned long)
    #11 ObjectFileMachO::ParseSymtab(lldb_private::Symtab&)
```

`ParseSymtab()` sizes the destination `Symtab`'s `vector<Symbol>` directly
from `nsyms` and `nindirectsyms`, two counts read from the file's
`LC_SYMTAB` and `LC_DYSYMTAB` commands.  `nlist_data`, the view already
built earlier in the function from the bytes the file actually has at
`symoff`, is far smaller than what those counts claim; the guard gating
this code only checks that it is non-empty, not how many nlist records
it can back.

Bound both counts by what `nlist_data` and `indirect_symbol_index_data`
(already clipped to the available bytes) can hold, instead of trusting
the file's counts outright.

Add a unit test whose LC_SYMTAB claims about two billion symbols in a
128-byte file; completing quickly instead of reserving that much space
is the regression check.

Assited-by: claude


>From ddea943d33812fb74da33cfeca2f502f33ea1f14 Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Tue, 8 Sep 2026 14:33:29 +0100
Subject: [PATCH] [lldb] Bound Mach-O symtab allocation to the file size

Opening a Mach-O file whose LC_SYMTAB claims far more symbols than the
file could possibly hold makes lldb ask for hundreds of gigabytes.  A
128-byte file with LC_SYMTAB.nsyms = 0x80000000 (the new unit test's
input, built with yaml2obj) reproduces it through `lldb-target-fuzzer`,
the harness that found this bug:

```
==45515== ERROR: libFuzzer: out-of-memory (malloc(171798691840))
    #8  std::__1::__split_buffer<lldb_private::Symbol,...>::__split_buffer(...)
    #9  std::__1::vector<lldb_private::Symbol,...>::resize(unsigned long)
    #10 lldb_private::Symtab::Resize(unsigned long)
    #11 ObjectFileMachO::ParseSymtab(lldb_private::Symtab&)
```

`ParseSymtab()` sizes the destination `Symtab`'s `vector<Symbol>` directly
from `nsyms` and `nindirectsyms`, two counts read from the file's
`LC_SYMTAB` and `LC_DYSYMTAB` commands.  `nlist_data`, the view already
built earlier in the function from the bytes the file actually has at
`symoff`, is far smaller than what those counts claim; the guard gating
this code only checks that it is non-empty, not how many nlist records
it can back.

Bound both counts by what `nlist_data` and `indirect_symbol_index_data`
(already clipped to the available bytes) can hold, instead of trusting
the file's counts outright.

Add a unit test whose LC_SYMTAB claims about two billion symbols in a
128-byte file; completing quickly instead of reserving that much space
is the regression check.
---
 .../ObjectFile/Mach-O/ObjectFileMachO.cpp     | 13 ++++--
 .../ObjectFile/MachO/TestObjectFileMachO.cpp  | 45 +++++++++++++++++++
 2 files changed, 55 insertions(+), 3 deletions(-)

diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
index b9202f5d9ffb3..ab73da8471937 100644
--- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
+++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
@@ -9,6 +9,8 @@
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringRef.h"
 
+#include <algorithm>
+
 #include "Plugins/Process/Utility/RegisterContextDarwin_arm.h"
 #include "Plugins/Process/Utility/RegisterContextDarwin_arm64.h"
 #include "Plugins/Process/Utility/RegisterContextDarwin_riscv32.h"
@@ -3468,11 +3470,16 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) {
 
   if (nlist_data.GetByteSize() > 0) {
 
+    const uint64_t max_nsyms = nlist_data.GetByteSize() / nlist_byte_size;
+    const uint64_t max_nindirectsyms =
+        indirect_symbol_index_data.GetByteSize() / sizeof(uint32_t);
+
     // If the sym array was not created while parsing the DSC unmapped
     // symbols, create it now.
     if (sym == nullptr) {
-      sym =
-          symtab.Resize(symtab_load_command.nsyms + m_dysymtab.nindirectsyms);
+      sym = symtab.Resize(
+          std::min<uint64_t>(symtab_load_command.nsyms, max_nsyms) +
+          std::min<uint64_t>(m_dysymtab.nindirectsyms, max_nindirectsyms));
       num_syms = symtab.GetNumSymbols();
     }
 
@@ -4179,7 +4186,7 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) {
     // First parse all the nlists but don't process them yet. See the next
     // comment for an explanation why.
     std::vector<struct nlist_64> nlists;
-    nlists.reserve(symtab_load_command.nsyms);
+    nlists.reserve(std::min<uint64_t>(symtab_load_command.nsyms, max_nsyms));
     for (; nlist_idx < symtab_load_command.nsyms; ++nlist_idx) {
       if (auto nlist =
               ParseNList(nlist_data, nlist_data_offset, nlist_byte_size))
diff --git a/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp b/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp
index 004742bf2befa..a9a24b855c52e 100644
--- a/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp
+++ b/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp
@@ -191,3 +191,48 @@ TEST_F(ObjectFileMachOTest, ParseSymtabSharedCacheMissingLinkedit) {
   Symtab symtab(OF);
   OF->ParseSymtab(symtab);
 }
+
+// An LC_SYMTAB whose nsyms claims far more symbols than the file could
+// possibly hold.
+TEST_F(ObjectFileMachOTest, ParseSymtabHugeSymbolCountIsBounded) {
+  const char *yamldata = R"(
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACF
+  cputype:         0x01000007
+  cpusubtype:      0x00000003
+  filetype:        0x00000001
+  ncmds:           2
+  sizeofcmds:      96
+  flags:           0x00000000
+  reserved:        0x00000000
+LoadCommands:
+  - cmd:             LC_SEGMENT_64
+    cmdsize:         72
+    segname:         __TEXT
+    vmaddr:          0
+    vmsize:          4096
+    fileoff:         0
+    filesize:        0
+    maxprot:         7
+    initprot:        5
+    nsects:          0
+    flags:           0
+  - cmd:             LC_SYMTAB
+    cmdsize:         24
+    symoff:          0
+    nsyms:           0x80000000
+    stroff:          0
+    strsize:         16
+...
+)";
+
+  llvm::Expected<TestFile> file = TestFile::fromYaml(yamldata);
+  ASSERT_THAT_EXPECTED(file, llvm::Succeeded());
+  lldb::ModuleSP module = std::make_shared<Module>(file->moduleSpec());
+  ObjectFile *OF = module->GetObjectFile();
+  ASSERT_TRUE(llvm::isa<ObjectFileMachO>(OF));
+
+  Symtab symtab(OF);
+  OF->ParseSymtab(symtab);
+}



More information about the lldb-commits mailing list