Skip to content

Commit 5e74b2e

Browse files
committed
llvm-dwarfdump --verify: Add support for .debug_str_offsets[.dwo]
Had a couple of issues lately causing corrupted strings due to problematic str_offsets (overflow due to >4GB .debug_str.dwo section in a dwp and the dwp tool silently overflowing the 32 bit offsets updated in the .debug_str_offsets.dwo section, and then more recently two CUs in a dwo caused the dwp tool to reapply the offset adjustment twice corrupting str_offsets.dwo as well) - so let's check that the offsets are valid. This assumes no suffix merging - if anyone implements that, then this checking should just be removed for the most part (we could still check the offsets are within the bounds of .debug_str[.dwo], but nothing more - any offset in the range would be valid, the offsets wouldn't have to land at the start of a string)
1 parent 0871f22 commit 5e74b2e

File tree

5 files changed

+187
-0
lines changed

5 files changed

+187
-0
lines changed

llvm/include/llvm/DebugInfo/DWARF/DWARFVerifier.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,17 @@ class DWARFVerifier {
338338
/// \returns true if the existing Apple-style accelerator tables verify
339339
/// successfully, false otherwise.
340340
bool handleAccelTables();
341+
342+
/// Verify the information in the .debug_str_offsets[.dwo].
343+
///
344+
/// Any errors are reported to the stream that was this object was
345+
/// constructed with.
346+
///
347+
/// \returns true if the .debug_line verifies successfully, false otherwise.
348+
bool handleDebugStrOffsets();
349+
bool verifyDebugStrOffsets(
350+
StringRef SectionName, const DWARFSection &Section, StringRef StrData,
351+
void (DWARFObject::*)(function_ref<void(const DWARFSection &)>) const);
341352
};
342353

343354
static inline bool operator<(const DWARFVerifier::DieRangeInfo &LHS,

llvm/lib/DebugInfo/DWARF/DWARFContext.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,8 @@ bool DWARFContext::verify(raw_ostream &OS, DIDumpOptions DumpOpts) {
776776
Success &= verifier.handleDebugInfo();
777777
if (DumpOpts.DumpType & DIDT_DebugLine)
778778
Success &= verifier.handleDebugLine();
779+
if (DumpOpts.DumpType & DIDT_DebugStrOffsets)
780+
Success &= verifier.handleDebugStrOffsets();
779781
Success &= verifier.handleAccelTables();
780782
return Success;
781783
}

llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,6 +1629,116 @@ bool DWARFVerifier::handleAccelTables() {
16291629
return NumErrors == 0;
16301630
}
16311631

1632+
bool DWARFVerifier::handleDebugStrOffsets() {
1633+
OS << "Verifying .debug_str_offsets...\n";
1634+
const DWARFObject &DObj = DCtx.getDWARFObj();
1635+
bool Success = true;
1636+
Success &= verifyDebugStrOffsets(
1637+
".debug_str_offsets.dwo", DObj.getStrOffsetsDWOSection(),
1638+
DObj.getStrDWOSection(), &DWARFObject::forEachInfoDWOSections);
1639+
Success &= verifyDebugStrOffsets(
1640+
".debug_str_offsets", DObj.getStrOffsetsSection(), DObj.getStrSection(),
1641+
&DWARFObject::forEachInfoSections);
1642+
return Success;
1643+
}
1644+
1645+
bool DWARFVerifier::verifyDebugStrOffsets(
1646+
StringRef SectionName, const DWARFSection &Section, StringRef StrData,
1647+
void (DWARFObject::*VisitInfoSections)(
1648+
function_ref<void(const DWARFSection &)>) const) {
1649+
const DWARFObject &DObj = DCtx.getDWARFObj();
1650+
uint16_t InfoVersion = 0;
1651+
DwarfFormat InfoFormat = DwarfFormat::DWARF32;
1652+
(DObj.*VisitInfoSections)([&](const DWARFSection &S) {
1653+
if (InfoVersion)
1654+
return;
1655+
DWARFDataExtractor DebugInfoData(DObj, S, DCtx.isLittleEndian(), 0);
1656+
uint64_t Offset = 0;
1657+
InfoFormat = DebugInfoData.getInitialLength(&Offset).second;
1658+
InfoVersion = DebugInfoData.getU16(&Offset);
1659+
});
1660+
1661+
DWARFDataExtractor DA(DObj, Section, DCtx.isLittleEndian(), 0);
1662+
1663+
DataExtractor::Cursor C(0);
1664+
uint64_t NextUnit = 0;
1665+
bool Success = true;
1666+
while (C.seek(NextUnit), C.tell() < DA.getData().size()) {
1667+
DwarfFormat Format;
1668+
uint64_t Length;
1669+
uint64_t StartOffset = C.tell();
1670+
if (InfoVersion == 4) {
1671+
Format = InfoFormat;
1672+
Length = DA.getData().size();
1673+
NextUnit = C.tell() + Length;
1674+
} else {
1675+
std::tie(Length, Format) = DA.getInitialLength(C);
1676+
if (!C)
1677+
break;
1678+
if (C.tell() + Length > DA.getData().size()) {
1679+
error() << formatv(
1680+
"{0}: contribution {1:X}: length exceeds available space "
1681+
"(contribution "
1682+
"offset ({1:X}) + length field space ({2:X}) + length ({3:X}) == "
1683+
"{4:X} > section size {5:X})\n",
1684+
SectionName, StartOffset, C.tell() - StartOffset, Length,
1685+
C.tell() + Length, DA.getData().size());
1686+
Success = false;
1687+
// Nothing more to do - no other contributions to try.
1688+
break;
1689+
}
1690+
NextUnit = C.tell() + Length;
1691+
uint8_t Version = DA.getU16(C);
1692+
if (C && Version != 5) {
1693+
error() << formatv("{0}: contribution {1:X}: invalid version {2}\n",
1694+
SectionName, StartOffset, Version);
1695+
Success = false;
1696+
// Can't parse the rest of this contribution, since we don't know the
1697+
// version, but we can pick up with the next contribution.
1698+
continue;
1699+
}
1700+
(void)DA.getU16(C); // padding
1701+
}
1702+
uint64_t OffsetByteSize = getDwarfOffsetByteSize(Format);
1703+
DA.setAddressSize(OffsetByteSize);
1704+
uint64_t Remainder = (Length - 4) % OffsetByteSize;
1705+
if (Remainder != 0) {
1706+
error() << formatv(
1707+
"{0}: contribution {1:X}: invalid length ((length ({2:X}) "
1708+
"- header (0x4)) % offset size {3:X} == {4:X} != 0)\n",
1709+
SectionName, StartOffset, Length, OffsetByteSize, Remainder);
1710+
Success = false;
1711+
}
1712+
for (uint64_t Index = 0; C && C.tell() + OffsetByteSize <= NextUnit; ++Index) {
1713+
uint64_t OffOff = C.tell();
1714+
uint64_t StrOff = DA.getAddress(C);
1715+
// check StrOff refers to the start of a string
1716+
if (StrOff == 0)
1717+
continue;
1718+
if (StrData.size() <= StrOff) {
1719+
error() << formatv(
1720+
"{0}: contribution {1:X}: index {2:X}: invalid string "
1721+
"offset *{3:X} == {4:X}, is beyond the bounds of the string section of length {5:X}\n",
1722+
SectionName, StartOffset, Index, OffOff, StrOff, StrData.size());
1723+
continue;
1724+
}
1725+
if (StrData[StrOff - 1] == '\0')
1726+
continue;
1727+
error() << formatv("{0}: contribution {1:X}: index {2:X}: invalid string "
1728+
"offset *{3:X} == {4:X}, is neither zero nor "
1729+
"immediately following a null character\n",
1730+
SectionName, StartOffset, Index, OffOff, StrOff);
1731+
Success = false;
1732+
}
1733+
}
1734+
1735+
if (Error E = C.takeError()) {
1736+
error() << SectionName << ": " << toString(std::move(E)) << '\n';
1737+
return false;
1738+
}
1739+
return Success;
1740+
}
1741+
16321742
raw_ostream &DWARFVerifier::error() const { return WithColor::error(OS); }
16331743

16341744
raw_ostream &DWARFVerifier::warn() const { return WithColor::warning(OS); }

llvm/test/tools/llvm-dwarfdump/X86/verify_dwarf5_debug_line.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,13 @@ main: # @main
4141
.Linfo_string2:
4242
.asciz "/tmp" # string offset=110
4343
.section .debug_str_offsets,"",@progbits
44+
.long .Lstr_off_end - .Lstr_off_begin # Length of String Offsets Set
45+
.Lstr_off_begin:
46+
.short 5
47+
.short 0
4448
.long .Linfo_string0
4549
.long .Linfo_string1
4650
.long .Linfo_string2
51+
.Lstr_off_end:
4752
.ident "clang version 17.0.0"
4853
.Lline_table_start0:
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# RUN: rm -rf %t && split-file %s %t
2+
# RUN: yaml2obj %t/v5.yaml -o %t/v5.o
3+
# RUN: not llvm-dwarfdump -debug-str-offsets -verify %t/v5.o | FileCheck %s
4+
# RUN: yaml2obj %t/v4.yaml -o %t/v4.o
5+
# RUN: not llvm-dwarfdump -debug-str-offsets -verify %t/v4.o | FileCheck --check-prefix=V4 %s
6+
7+
# CHECK: Verifying .debug_abbrev...
8+
# CHECK: Verifying .debug_str_offsets...
9+
# CHECK-NEXT: error: .debug_str_offsets: contribution 0x0: index 0x2: invalid string offset *0x10 == 0x1, is neither zero nor immediately following a null character
10+
# CHECK-NEXT: error: .debug_str_offsets: contribution 0x0: index 0x3: invalid string offset *0x14 == 0x42, is beyond the bounds of the string section of length 0x8
11+
# CHECK-NEXT: error: .debug_str_offsets: contribution 0x18: invalid version 42
12+
# CHECK-NEXT: error: .debug_str_offsets: contribution 0x20: invalid length ((length (0x5) - header (0x4)) % offset size 0x4 == 0x1 != 0)
13+
# CHECK-NEXT: error: .debug_str_offsets: contribution 0x29: length exceeds available space (contribution offset (0x29) + length field space (0x4) + length (0x5000000) == 0x500002D > section size 0x30)
14+
# Errors detected.
15+
16+
# V4: error: .debug_str_offsets: contribution 0x0: index 0x2: invalid string offset *0x8 == 0x2, is neither zero nor immediately following a null character
17+
18+
19+
#--- v4.yaml
20+
--- !ELF
21+
FileHeader:
22+
Class: ELFCLASS64
23+
Data: ELFDATA2LSB
24+
Type: ET_EXEC
25+
DWARF:
26+
debug_str:
27+
- 'foo'
28+
- 'bar'
29+
debug_info:
30+
- Version: 4
31+
AddrSize: 4
32+
Sections:
33+
- Name: '.debug_str_offsets'
34+
Type: SHT_PROGBITS
35+
Content: "000000000400000002000000"
36+
37+
#--- v5.yaml
38+
--- !ELF
39+
FileHeader:
40+
Class: ELFCLASS64
41+
Data: ELFDATA2LSB
42+
Type: ET_EXEC
43+
DWARF:
44+
debug_str:
45+
- 'foo'
46+
- 'bar'
47+
debug_info:
48+
- Version: 5
49+
UnitType: DW_UT_compile
50+
AddrSize: 8
51+
debug_str_offsets:
52+
- Offsets:
53+
- 0x00000000
54+
- 0x00000004
55+
- 0x00000001
56+
- 0x00000042
57+
- Version: 42
58+
- Length: 5
59+
- Length: 8

0 commit comments

Comments
 (0)