From dcb76d0c2175d9e9be6688c83d2b753a9a7c864d Mon Sep 17 00:00:00 2001
From: aviralgarg05 <gargaviral99@gmail.com>
Date: Fri, 9 Jan 2026 20:59:10 +0530
Subject: [PATCH 3/3] Fix Any recursion depth bypass in Python
 json_format.ParseDict

This fixes a security vulnerability where nested google.protobuf.Any messages
could bypass the max_recursion_depth limit, potentially leading to denial of
service via stack overflow.

The root cause was that _ConvertAnyMessage() was calling itself recursively
via methodcaller() for nested well-known types, bypassing the recursion depth
tracking in ConvertMessage().

The fix routes well-known type parsing through ConvertMessage() to ensure
proper recursion depth accounting for all message types including nested Any.

Fixes #25070
---
 python/google/protobuf/json_format.py | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/python/google/protobuf/json_format.py b/python/google/protobuf/json_format.py
index 6c3b1cb8c..3dda09d61 100644
--- a/python/google/protobuf/json_format.py
+++ b/python/google/protobuf/json_format.py
@@ -456,6 +456,17 @@ class _Parser(object):
     Raises:
       ParseError: In case of convert problems.
     """
+    # Increment recursion depth at message entry. The max_recursion_depth limit
+    # is exclusive: a depth value equal to max_recursion_depth will trigger an
+    # error. For example, with max_recursion_depth=5, nesting up to depth 4 is
+    # allowed, but attempting depth 5 raises ParseError.
+    self.recursion_depth += 1
+    if self.recursion_depth > self.max_recursion_depth:
+      raise ParseError(
+          'Message too deep. Max recursion depth is {0}'.format(
+              self.max_recursion_depth
+          )
+      )
     message_descriptor = message.DESCRIPTOR
     full_name = message_descriptor.full_name
     if _IsWrapperMessage(message_descriptor):
@@ -595,8 +606,11 @@ class _Parser(object):
     if _IsWrapperMessage(message_descriptor):
       self._ConvertWrapperMessage(value['value'], sub_message)
     elif full_name in _WKTJSONMETHODS:
-      methodcaller(
-          _WKTJSONMETHODS[full_name][1], value['value'], sub_message)(self)
+      # For well-known types (including nested Any), use ConvertMessage
+      # to ensure recursion depth is properly tracked
+      self.ConvertMessage(
+          value['value'], sub_message, '{0}.value'.format(path)
+      )
     else:
       del value['@type']
       self._ConvertFieldValuePair(value, sub_message)
-- 
2.53.0

