@@ -1760,6 +1760,107 @@ def testNestedRecursiveLimit(self):
17601760 '{"payload": {}, "child": {"child":{}}}' , message , max_recursion_depth = 3
17611761 )
17621762
1763+ def testAnyRecursionDepthEnforcement (self ):
1764+ """Test that nested Any messages respect max_recursion_depth limit."""
1765+ # Test that deeply nested Any messages raise ParseError instead of
1766+ # bypassing the recursion limit. This prevents DoS via nested Any.
1767+ message = any_pb2 .Any ()
1768+
1769+ # Create nested Any structure that should exceed depth limit
1770+ # With max_recursion_depth=5, we can nest 4 Any messages
1771+ # (depth 1 = outer Any, depth 2-4 = nested Anys, depth 5 = final value)
1772+ nested_any = {
1773+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1774+ 'value' : {
1775+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1776+ 'value' : {
1777+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1778+ 'value' : {
1779+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1780+ 'value' : {
1781+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1782+ 'value' : {},
1783+ },
1784+ },
1785+ },
1786+ },
1787+ }
1788+
1789+ # Should raise ParseError due to exceeding max depth, not RecursionError
1790+ self .assertRaisesRegex (
1791+ json_format .ParseError ,
1792+ 'Message too deep. Max recursion depth is 5' ,
1793+ json_format .ParseDict ,
1794+ nested_any ,
1795+ message ,
1796+ max_recursion_depth = 5 ,
1797+ )
1798+
1799+ # Verify that Any messages within the limit can be parsed successfully
1800+ # With max_recursion_depth=5, we can nest up to 4 Any messages
1801+ shallow_any = {
1802+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1803+ 'value' : {
1804+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1805+ 'value' : {
1806+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1807+ 'value' : {
1808+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1809+ 'value' : {},
1810+ },
1811+ },
1812+ },
1813+ }
1814+ json_format .ParseDict (shallow_any , message , max_recursion_depth = 5 )
1815+
1816+ def testAnyRecursionDepthBoundary (self ):
1817+ """Test recursion depth boundary behavior (exclusive upper limit)."""
1818+ message = any_pb2 .Any ()
1819+
1820+ # Create nested Any at depth exactly 4 (should succeed with max_recursion_depth=5)
1821+ depth_4_any = {
1822+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1823+ 'value' : {
1824+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1825+ 'value' : {
1826+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1827+ 'value' : {
1828+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1829+ 'value' : {},
1830+ },
1831+ },
1832+ },
1833+ }
1834+ # This should succeed: depth 4 < max_recursion_depth 5
1835+ json_format .ParseDict (depth_4_any , message , max_recursion_depth = 5 )
1836+
1837+ # Create nested Any at depth exactly 5 (should fail with max_recursion_depth=5)
1838+ depth_5_any = {
1839+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1840+ 'value' : {
1841+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1842+ 'value' : {
1843+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1844+ 'value' : {
1845+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1846+ 'value' : {
1847+ '@type' : 'type.googleapis.com/google.protobuf.Any' ,
1848+ 'value' : {},
1849+ },
1850+ },
1851+ },
1852+ },
1853+ }
1854+ # This should fail: depth 5 == max_recursion_depth 5 (exclusive limit)
1855+ self .assertRaisesRegex (
1856+ json_format .ParseError ,
1857+ 'Message too deep. Max recursion depth is 5' ,
1858+ json_format .ParseDict ,
1859+ depth_5_any ,
1860+ message ,
1861+ max_recursion_depth = 5 ,
1862+ )
1863+
17631864 def testJsonNameConflictSerilize (self ):
17641865 message = more_messages_pb2 .ConflictJsonName (value = 2 )
17651866 self .assertEqual (
0 commit comments