Skip to content

fix(debuginfo): Remove depth limits from function parsing - #1065

Open
klochek wants to merge 5 commits into
masterfrom
christopherklochek/remove_depth_limits
Open

fix(debuginfo): Remove depth limits from function parsing#1065
klochek wants to merge 5 commits into
masterfrom
christopherklochek/remove_depth_limits

Conversation

@klochek

@klochek klochek commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

We will now handle function parsing in both symcache convertor and dwarf DIE parsing iteratively, so there is no longer a need for bounding these by function depth.

This also required introducing an iterative fmt::Debug implementation for Function, authored by Claude.

@klochek
klochek requested a review from a team as a code owner September 2, 2026 08:37
Comment thread symbolic-symcache/src/writer.rs

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 914a4af. Configure here.

Comment thread symbolic-debuginfo/src/base/mod.rs
Comment thread symbolic-symcache/src/writer.rs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This raises a question—do we want the iterative Drop/Debug after all, now that we've taken away the parsing limits? The Debug impl at least is very low value compared to the complication it introduces, IMO.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without an iterative drop impl, symbolic can still stack-overflow on carefully-crafted dwarf files. The fmt::Debug impl is a bit more of a wash--it's only really used in tests, but it's pretty helpful to have something that will always work.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Drop makes sense, but I think the Debug is far more trouble than it's worth. I'd remove it.

Comment thread symbolic-symcache/src/writer.rs
Comment thread symbolic-debuginfo/src/pe.rs
We will now handle function parsing in both symcache convertor and dwarf DIE parsing iteratively, so there is no longer a need for bounding these by function depth.

This also required introducing an iterative fmt::Debug implementation for Function, authored by Claude.
@klochek
klochek force-pushed the christopherklochek/remove_depth_limits branch from 7670344 to 69cc4ca Compare September 2, 2026 11:27
Comment on lines +777 to 787
/// End address of the entire function body, including inlined functions.
///
/// This address points at the first instruction after the function body.
pub fn end_address(&self) -> u64 {
self.address.saturating_add(self.size)
}
}

/// A dynamically dispatched iterator over items with the given lifetime.
pub type DynIterator<'a, T> = Box<dyn Iterator<Item = T> + 'a>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The recursive fmt::Debug implementation for Function can cause a stack overflow when formatting deeply nested inlinees, which are now possible after depth limits were removed.
Severity: MEDIUM

Suggested Fix

Replace the recursive fmt::Debug implementation for Function with an iterative one. This can be done by manually iterating through the inlinees and formatting them without making a recursive call to f.debug_struct, similar to the approach taken for the Drop implementation.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: symbolic-debuginfo/src/base/mod.rs#L773-L787

Potential issue: The pull request removes the parsing depth limit for inline functions,
which allows for the creation of deeply nested `Function` structures. However, the
`fmt::Debug` implementation for `Function` remains recursive. When formatting a deeply
nested `Function` (for example, through logging or an assertion failure using `{:?}`),
the recursive calls to format the `inlinees` field will lead to a stack overflow and
crash the application. While a custom iterative `Drop` was added to prevent this issue
during object destruction, the debug formatting logic was not updated similarly, leaving
a latent vulnerability.

@klochek
klochek requested a review from loewenheim September 2, 2026 11:45
Comment on lines 1358 to 1373
next_line: Option<&'s [u8]>,
inline_origin_map: BreakpadInlineOriginMap<'s>,
lines: Lines<'s>,
max_function_parse_depth: u32,
}

impl<'s> BreakpadFunctionIterator<'s> {
fn new(
file_map: &'s BreakpadFileMap<'s>,
mut lines: Lines<'s>,
max_function_parse_depth: u32,
) -> Self {
fn new(file_map: &'s BreakpadFileMap<'s>, mut lines: Lines<'s>) -> Self {
let next_line = lines.next();
Self {
file_map,
next_line,
inline_origin_map: Default::default(),
lines,
max_function_parse_depth,
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depth limit removed but Function Debug/Clone still recurse on inlinees

Removing max_function_parse_depth allows deep Breakpad inline trees, but Function's fmt::Debug and derived Clone still recurse on inlinees (unlike Drop), so formatting or cloning a crafted deep tree can stack-overflow abort the process.

Evidence
  • BreakpadFunctionIterator no longer stores or enforces max_function_parse_depth; INLINE nest levels are passed straight into FunctionBuilderInlinee.depth.
  • FunctionBuilder::add_inlinee only rejects address < self.address; the comment still mentions a depth limit but no depth check remains.
  • symbolic-symcache/tests/breakpad.rs test_breakpad_deep_inline_functions builds and converts depth-1000 inline trees successfully after this change.
  • Function has an iterative Drop to avoid inlinee recursion, but fmt::Debug still does .field("inlinees", &self.inlinees) and #[derive(Clone)] remains recursive—contrary to the PR claim of an iterative Debug impl.

Identified by Warden · wrdn-dos-review · ZP3-TAA

@@ -357,7 +355,6 @@ impl<'data> ElfObject<'data> {
data,
is_malformed: false,
max_decompressed_section_size: opts.max_decompressed_section_size,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ELF section decompression defaults to unbounded allocation

With default ParseObjectOptions, decompress_section treats the attacker-controlled decompressed size as unlimited (unwrap_or(usize::MAX)) and does Vec::with_capacity(size), so a tiny compressed ELF section can force a multi-GB allocation/OOM; set a finite max_decompressed_section_size by default or refuse decompression when unset.

Evidence
  • ParseObjectOptions::default() leaves max_decompressed_section_size as None (object.rs).
  • That option is stored on ElfObject at the hunk line and used in decompress_section as self.max_decompressed_section_size.unwrap_or(usize::MAX) before Vec::with_capacity(size) / zstd::bulk::decompress(..., size).
  • size comes from the GNU ZLIB header or ELF CompressionHeader.ch_size in the untrusted section bytes.
  • ElfObject::parse / Dwarf::section take this path for compressed debug sections with no other cap when the option is unset.

Identified by Warden · wrdn-dos-review · YQ7-HNR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants