[llvm] [DebugInfo] Add bounds checks to debug-info parsers (PR #211797)

Matt Davis via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 24 06:58:40 PDT 2026


https://github.com/enferex created https://github.com/llvm/llvm-project/pull/211797

Fix out-of-bounds accesses reachable from crafted debug info.  I ran a LLM security scan on an LLVM derived repo; a number of issues were discovered.  This PR is a subset of fixes to address those discovered defects.  

- DWARF DWARFUnitIndex: reject row Index > NumUnits before writing Contribs.
- GSYM LineTable: reject LineRange <= 0 to avoid a divide-by-zero (SIGFPE).
- PDB HashTable: reject Present/Deleted bit indices >= Capacity.
- PDB TpiStream: reduce raw hash value mod NumHashBuckets before indexing.
- CodeView TypeStreamMerger: validate the Ref range fits Storage before remapping.
- BTF BTFParser: guard the disengaged-optional section deref in parseRelocInfo.

Assisted by LLMs.

>From 9abb3f1285b4ef0b31fd672eb7e0dcd83fc5e072 Mon Sep 17 00:00:00 2001
From: Matt Davis <mattd at nvidia.com>
Date: Thu, 23 Jul 2026 18:59:55 +0000
Subject: [PATCH] [DebugInfo] Add bounds checks to debug-info parsers

Fix out-of-bounds accesses reachable from crafted debug info in llvm-dwarfdump,
llvm-pdbutil, lld, llvm-symbolizer, and llvm-objdump. Each guard mirrors an
existing idiom in the same or sibling code.

- DWARF DWARFUnitIndex: reject row Index > NumUnits before writing Contribs.
- GSYM LineTable: reject LineRange <= 0 to avoid a divide-by-zero (SIGFPE).
- PDB HashTable: reject Present/Deleted bit indices >= Capacity.
- PDB TpiStream: reduce raw hash value mod NumHashBuckets before indexing.
- CodeView TypeStreamMerger: validate the Ref range fits Storage before remapping.
- BTF BTFParser: guard the disengaged-optional section deref in parseRelocInfo.

Co-Authored-By: Claude Opus 4.8 <noreply at anthropic.com>
---
 llvm/include/llvm/DebugInfo/PDB/Native/HashTable.h | 10 ++++++++++
 llvm/lib/DebugInfo/BTF/BTFParser.cpp               |  5 +++++
 llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp   |  7 +++++++
 llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp        |  2 ++
 llvm/lib/DebugInfo/GSYM/LineTable.cpp              |  4 ++++
 llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp        |  2 +-
 6 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/llvm/include/llvm/DebugInfo/PDB/Native/HashTable.h b/llvm/include/llvm/DebugInfo/PDB/Native/HashTable.h
index efece7a17676e..ba02459cbe6de 100644
--- a/llvm/include/llvm/DebugInfo/PDB/Native/HashTable.h
+++ b/llvm/include/llvm/DebugInfo/PDB/Native/HashTable.h
@@ -134,9 +134,19 @@ class HashTable {
     if (Present.count() != H->Size)
       return make_error<RawError>(raw_error_code::corrupt_file,
                                   "Present bit vector does not match size!");
+    if (!Present.empty() &&
+        static_cast<uint32_t>(Present.find_last()) >= H->Capacity)
+      return make_error<RawError>(
+          raw_error_code::corrupt_file,
+          "Present bit vector contains out-of-bounds index!");
 
     if (auto EC = readSparseBitVector(Stream, Deleted))
       return EC;
+    if (!Deleted.empty() &&
+        static_cast<uint32_t>(Deleted.find_last()) >= H->Capacity)
+      return make_error<RawError>(
+          raw_error_code::corrupt_file,
+          "Deleted bit vector contains out-of-bounds index!");
     if (Present.intersects(Deleted))
       return make_error<RawError>(raw_error_code::corrupt_file,
                                   "Present bit vector intersects deleted!");
diff --git a/llvm/lib/DebugInfo/BTF/BTFParser.cpp b/llvm/lib/DebugInfo/BTF/BTFParser.cpp
index e22396c50d787..9f71dcb8af988 100644
--- a/llvm/lib/DebugInfo/BTF/BTFParser.cpp
+++ b/llvm/lib/DebugInfo/BTF/BTFParser.cpp
@@ -352,6 +352,11 @@ Error BTFParser::parseRelocInfo(ParseContext &Ctx, DataExtractor &Extractor,
     uint32_t NumInfo = Extractor.getU32(C);
     StringRef SecName = findString(SecNameOff);
     std::optional<SectionRef> Sec = Ctx.findSection(SecName);
+    if (!C)
+      return Err(".BTF.ext", C);
+    if (!Sec)
+      return Err("") << "can't find section '" << SecName
+                     << "' while parsing .BTF.ext field reloc info";
     BTFRelocVector &Relocs = SectionRelocs[Sec->getIndex()];
     for (uint32_t I = 0; C && I < NumInfo; ++I) {
       uint64_t RecStart = C.tell();
diff --git a/llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp b/llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp
index b9aaa3146c9cc..1274cc1c7b6f8 100644
--- a/llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp
+++ b/llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp
@@ -403,6 +403,13 @@ TypeStreamMerger::remapIndices(const CVType &OriginalType,
   uint8_t *DestContent = Storage.data() + sizeof(RecordPrefix);
 
   for (auto &Ref : Refs) {
+    // Ref.Count can come from a field inside the record, so it may not match
+    // the actual data length. Skip the record if the run of TypeIndex values
+    // would extend past Storage.
+    uint64_t Begin = sizeof(RecordPrefix) + static_cast<uint64_t>(Ref.Offset);
+    if (Begin + static_cast<uint64_t>(Ref.Count) * sizeof(TypeIndex) >
+        Storage.size())
+      return {};
     TypeIndex *DestTIs =
         reinterpret_cast<TypeIndex *>(DestContent + Ref.Offset);
 
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp b/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp
index 08abcf13d0618..f5d7b1e19dd11 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp
@@ -159,6 +159,8 @@ bool DWARFUnitIndex::parseImpl(DataExtractor IndexData) {
     auto Index = IndexData.getU32(&Offset);
     if (!Index)
       continue;
+    if (Index > Header.NumUnits)
+      return false;
     Rows[i].Index = this;
     Rows[i].Contributions =
         std::make_unique<Entry::SectionContribution[]>(Header.NumColumns);
diff --git a/llvm/lib/DebugInfo/GSYM/LineTable.cpp b/llvm/lib/DebugInfo/GSYM/LineTable.cpp
index 7767cdb943949..f94234a836716 100644
--- a/llvm/lib/DebugInfo/GSYM/LineTable.cpp
+++ b/llvm/lib/DebugInfo/GSYM/LineTable.cpp
@@ -63,6 +63,10 @@ static llvm::Error parse(GsymDataExtractor &Data, uint64_t BaseAddr,
         "0x%8.8" PRIx64 ": missing LineTable MaxDelta", Offset);
   int64_t MaxDelta = Data.getSLEB128(&Offset);
   int64_t LineRange = MaxDelta - MinDelta + 1;
+  if (LineRange <= 0)
+    return createStringError(std::errc::io_error,
+                             "0x%8.8" PRIx64 ": invalid LineTable LineRange",
+                             Offset);
   if (!Data.isValidOffset(Offset))
     return createStringError(std::errc::io_error,
         "0x%8.8" PRIx64 ": missing LineTable FirstLine", Offset);
diff --git a/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp b/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp
index 24d2f9e3360ea..816b4cf4c9006 100644
--- a/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp
+++ b/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp
@@ -152,7 +152,7 @@ void TpiStream::buildHashMap() {
   TypeIndex TIE{Header->TypeIndexEnd};
   while (TIB < TIE) {
     uint32_t HV = HashValues[TIB.toArrayIndex()];
-    HashMap[HV].push_back(TIB++);
+    HashMap[HV % Header->NumHashBuckets].push_back(TIB++);
   }
 }
 



More information about the llvm-commits mailing list