Skip to content

UPSTREAM PR #26916: Fix text format parser recursion limit bypass#145

Open
loci-dev wants to merge 3 commits intomainfrom
loci/pr-26916-fix-text-format-recursion-limit
Open

UPSTREAM PR #26916: Fix text format parser recursion limit bypass#145
loci-dev wants to merge 3 commits intomainfrom
loci/pr-26916-fix-text-format-recursion-limit

Conversation

@loci-dev
Copy link
Copy Markdown

Note

Source pull request: protocolbuffers/protobuf#26916

Summary

Two security vulnerabilities in the text format parser's recursion depth handling allow unbounded stack growth, leading to stack overflow / crash when parsing untrusted input:

  • Default recursion limit is INT_MAX. The Parser constructor initializes recursion_limit_ to std::numeric_limits<int>::max(), effectively disabling depth protection. The binary format parser (CodedInputStream) correctly defaults to 100. This patch changes the text format default to 100 to match.

  • ConsumeAnyValue bypasses the recursion limit. The call chain ConsumeField -> ConsumeAnyValue -> ConsumeMessage -> ConsumeField never decrements recursion_limit_, so even a finite limit can be bypassed via nested Any messages. This patch adds the same decrement/increment guard already used by ConsumeFieldMessage and SkipFieldMessage.

Test plan

  • Verify that deeply nested text-format messages (depth > 100) are rejected with a clear error instead of crashing.
  • Verify that deeply nested Any values in text format are also rejected.
  • Verify existing text format tests still pass (legitimate messages are well under 100 levels).

Two security issues in the text format parser's recursion handling:

1. The Parser constructor defaults recursion_limit_ to INT_MAX instead
   of 100, effectively disabling stack depth protection. The binary
   format parser (coded_stream.h) correctly defaults to 100. A deeply
   nested text-format message can cause stack overflow / crash.

2. ConsumeAnyValue calls ConsumeMessage without decrementing
   recursion_limit_, so the ConsumeField -> ConsumeAnyValue ->
   ConsumeMessage -> ConsumeField call chain bypasses the recursion
   limit entirely. Add the same decrement/increment guard used by
   ConsumeFieldMessage and SkipFieldMessage.
- Revert default recursion_limit_ back to INT_MAX since TextProto
  is not a wire format and many users have trusted inputs deeper
  than 100.
- Refactor ConsumeAnyValue recursion guard to use a single balanced
  decrement/increment: --recursion_limit_ at entry, ++recursion_limit_
  only on the success path. Error paths use DO() or bare return false,
  which fail the entire parse, so restoring the limit is unnecessary.
  This matches the pattern in ConsumeFieldMessage and SkipFieldMessage.
@loci-review
Copy link
Copy Markdown

loci-review Bot commented Apr 16, 2026

The analysis encountered an error. Please review the Processing Details for more information.

@loci-dev loci-dev force-pushed the main branch 24 times, most recently from 63b262e to 463aed9 Compare April 22, 2026 02:16
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