Control: tags 1126302 + patch
Control: tags 1126302 + pending
Control: tags 1134895 + patch
Control: tags 1134895 + pending

Dear maintainer,

I've prepared an NMU for protobuf (versioned as 3.21.12-15.1) and 
uploaded it to DELAYED/7. Please feel free to tell me if I should
cancel it.

cu
Adrian
diffstat for protobuf-3.21.12 protobuf-3.21.12

 changelog                                                               |    8 
 patches/0001-Fix-Any-recursion-depth-bypass-in-Python-json_format.patch |  169 ++++++++++
 patches/0002-Check-that-readRaw-does-not-accept-negative-length-v.patch |   48 ++
 patches/0003-php-Fix-that-recursion-limit-is-not-enforced.patch         |   65 +++
 patches/series                                                          |    3 
 5 files changed, 293 insertions(+)

diff -Nru protobuf-3.21.12/debian/changelog protobuf-3.21.12/debian/changelog
--- protobuf-3.21.12/debian/changelog	2026-01-11 20:18:49.000000000 +0200
+++ protobuf-3.21.12/debian/changelog	2026-06-21 12:05:51.000000000 +0300
@@ -1,3 +1,11 @@
+protobuf (3.21.12-15.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * CVE-2026-0994: JSON recursion depth bypass (Closes: #1126302)
+  * CVE-2026-6409: PHP Denial of Service (Closes: #1134895)
+
+ -- Adrian Bunk <[email protected]>  Sun, 21 Jun 2026 12:05:51 +0300
+
 protobuf (3.21.12-15) unstable; urgency=medium
 
   [ Adrian Bunk <[email protected]> ]
diff -Nru protobuf-3.21.12/debian/patches/0001-Fix-Any-recursion-depth-bypass-in-Python-json_format.patch protobuf-3.21.12/debian/patches/0001-Fix-Any-recursion-depth-bypass-in-Python-json_format.patch
--- protobuf-3.21.12/debian/patches/0001-Fix-Any-recursion-depth-bypass-in-Python-json_format.patch	1970-01-01 02:00:00.000000000 +0200
+++ protobuf-3.21.12/debian/patches/0001-Fix-Any-recursion-depth-bypass-in-Python-json_format.patch	2026-06-21 11:05:31.000000000 +0300
@@ -0,0 +1,169 @@
+From 425bbbbb1bba4f76cbae984b75fefbc84666d801 Mon Sep 17 00:00:00 2001
+From: zhangskz <[email protected]>
+Date: Thu, 29 Jan 2026 16:32:56 -0500
+Subject: Fix Any recursion depth bypass in Python json_format.ParseDict
+ (#25239) (#25587)
+
+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
+
+Closes #25239
+
+COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/25239 from aviralgarg05:fix-any-recursion-depth-bypass 3cbbcbea142593d3afd2ceba2db14b05660f62f4
+PiperOrigin-RevId: 862740421
+
+Co-authored-by: Aviral Garg <[email protected]>
+---
+ .../protobuf/internal/json_format_test.py     | 101 ++++++++++++++++++
+ python/google/protobuf/json_format.py         |  12 ++-
+ 2 files changed, 110 insertions(+), 3 deletions(-)
+
+diff --git a/python/google/protobuf/internal/json_format_test.py b/python/google/protobuf/internal/json_format_test.py
+index 7b7d3bba4..8eb9dfd0a 100644
+--- a/python/google/protobuf/internal/json_format_test.py
++++ b/python/google/protobuf/internal/json_format_test.py
+@@ -1281,5 +1281,106 @@ class JsonFormatTest(JsonFormatBase):
+     json_format.Parse('{"payload": {}, "child": {"child":{}}}',
+                       message, max_recursion_depth=3)
+ 
++  def testAnyRecursionDepthEnforcement(self):
++    """Test that nested Any messages respect max_recursion_depth limit."""
++    # Test that deeply nested Any messages raise ParseError instead of
++    # bypassing the recursion limit. This prevents DoS via nested Any.
++    message = any_pb2.Any()
++
++    # Create nested Any structure that should exceed depth limit
++    # With max_recursion_depth=5, we can nest 4 Any messages
++    # (depth 1 = outer Any, depth 2-4 = nested Anys, depth 5 = final value)
++    nested_any = {
++        '@type': 'type.googleapis.com/google.protobuf.Any',
++        'value': {
++            '@type': 'type.googleapis.com/google.protobuf.Any',
++            'value': {
++                '@type': 'type.googleapis.com/google.protobuf.Any',
++                'value': {
++                    '@type': 'type.googleapis.com/google.protobuf.Any',
++                    'value': {
++                        '@type': 'type.googleapis.com/google.protobuf.Any',
++                        'value': {},
++                    },
++                },
++            },
++        },
++    }
++
++    # Should raise ParseError due to exceeding max depth, not RecursionError
++    self.assertRaisesRegex(
++        json_format.ParseError,
++        'Message too deep. Max recursion depth is 5',
++        json_format.ParseDict,
++        nested_any,
++        message,
++        max_recursion_depth=5,
++    )
++
++    # Verify that Any messages within the limit can be parsed successfully
++    # With max_recursion_depth=5, we can nest up to 4 Any messages
++    shallow_any = {
++        '@type': 'type.googleapis.com/google.protobuf.Any',
++        'value': {
++            '@type': 'type.googleapis.com/google.protobuf.Any',
++            'value': {
++                '@type': 'type.googleapis.com/google.protobuf.Any',
++                'value': {
++                    '@type': 'type.googleapis.com/google.protobuf.Any',
++                    'value': {},
++                },
++            },
++        },
++    }
++    json_format.ParseDict(shallow_any, message, max_recursion_depth=5)
++
++  def testAnyRecursionDepthBoundary(self):
++    """Test recursion depth boundary behavior (exclusive upper limit)."""
++    message = any_pb2.Any()
++
++    # Create nested Any at depth exactly 4 (should succeed with max_recursion_depth=5)
++    depth_4_any = {
++        '@type': 'type.googleapis.com/google.protobuf.Any',
++        'value': {
++            '@type': 'type.googleapis.com/google.protobuf.Any',
++            'value': {
++                '@type': 'type.googleapis.com/google.protobuf.Any',
++                'value': {
++                    '@type': 'type.googleapis.com/google.protobuf.Any',
++                    'value': {},
++                },
++            },
++        },
++    }
++    # This should succeed: depth 4 < max_recursion_depth 5
++    json_format.ParseDict(depth_4_any, message, max_recursion_depth=5)
++
++    # Create nested Any at depth exactly 5 (should fail with max_recursion_depth=5)
++    depth_5_any = {
++        '@type': 'type.googleapis.com/google.protobuf.Any',
++        'value': {
++            '@type': 'type.googleapis.com/google.protobuf.Any',
++            'value': {
++                '@type': 'type.googleapis.com/google.protobuf.Any',
++                'value': {
++                    '@type': 'type.googleapis.com/google.protobuf.Any',
++                    'value': {
++                        '@type': 'type.googleapis.com/google.protobuf.Any',
++                        'value': {},
++                    },
++                },
++            },
++        },
++    }
++    # This should fail: depth 5 == max_recursion_depth 5 (exclusive limit)
++    self.assertRaisesRegex(
++        json_format.ParseError,
++        'Message too deep. Max recursion depth is 5',
++        json_format.ParseDict,
++        depth_5_any,
++        message,
++        max_recursion_depth=5,
++    )
++
+ if __name__ == '__main__':
+   unittest.main()
+diff --git a/python/google/protobuf/json_format.py b/python/google/protobuf/json_format.py
+index 5024ed89d..ff7e3c6e5 100644
+--- a/python/google/protobuf/json_format.py
++++ b/python/google/protobuf/json_format.py
+@@ -486,6 +486,10 @@ 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(
+@@ -652,9 +656,11 @@ class _Parser(object):
+       self._ConvertWrapperMessage(value['value'], sub_message,
+                                   '{0}.value'.format(path))
+     elif full_name in _WKTJSONMETHODS:
+-      methodcaller(_WKTJSONMETHODS[full_name][1], value['value'], sub_message,
+-                   '{0}.value'.format(path))(
+-                       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, path)
+-- 
+2.47.3
+
diff -Nru protobuf-3.21.12/debian/patches/0002-Check-that-readRaw-does-not-accept-negative-length-v.patch protobuf-3.21.12/debian/patches/0002-Check-that-readRaw-does-not-accept-negative-length-v.patch
--- protobuf-3.21.12/debian/patches/0002-Check-that-readRaw-does-not-accept-negative-length-v.patch	1970-01-01 02:00:00.000000000 +0200
+++ protobuf-3.21.12/debian/patches/0002-Check-that-readRaw-does-not-accept-negative-length-v.patch	2026-06-21 11:05:31.000000000 +0300
@@ -0,0 +1,48 @@
+From 8d3c56f84a1158095f28ef859757872df6c176cc Mon Sep 17 00:00:00 2001
+From: Protobuf Team Bot <[email protected]>
+Date: Tue, 13 Jan 2026 12:31:18 -0800
+Subject: Check that `readRaw` does not accept negative length value.
+
+Fixes https://github.com/protocolbuffers/protobuf/issues/24159
+
+PiperOrigin-RevId: 855837030
+---
+ php/src/Google/Protobuf/Internal/CodedInputStream.php | 3 ++-
+ php/tests/EncodeDecodeTest.php                        | 7 +++++++
+ 2 files changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/php/src/Google/Protobuf/Internal/CodedInputStream.php b/php/src/Google/Protobuf/Internal/CodedInputStream.php
+index 2ed2dfde2..0f98be7a6 100644
+--- a/php/src/Google/Protobuf/Internal/CodedInputStream.php
++++ b/php/src/Google/Protobuf/Internal/CodedInputStream.php
+@@ -295,7 +295,8 @@ class CodedInputStream
+     public function readRaw($size, &$buffer)
+     {
+         $current_buffer_size = 0;
+-        if ($this->bufferSize() < $size) {
++        // size (varint) read from the wire could be negative.
++        if ($size < 0 || $this->bufferSize() < $size) {
+             return false;
+         }
+ 
+diff --git a/php/tests/EncodeDecodeTest.php b/php/tests/EncodeDecodeTest.php
+index 33d8da179..ddd01a1ac 100644
+--- a/php/tests/EncodeDecodeTest.php
++++ b/php/tests/EncodeDecodeTest.php
+@@ -538,6 +538,13 @@ class EncodeDecodeTest extends TestBase
+         $this->assertEquals(-1, $m->getOptionalInt32());
+     }
+ 
++    public function testInvalidVarintLength() {
++        $this->expectException(Exception::class);
++
++        $m = new TestMessage();
++        $m->mergeFromString(hex2bin("0afaffffff0f"));
++    }
++
+     public function testRandomFieldOrder()
+     {
+         $m = new TestRandomFieldOrder();
+-- 
+2.47.3
+
diff -Nru protobuf-3.21.12/debian/patches/0003-php-Fix-that-recursion-limit-is-not-enforced.patch protobuf-3.21.12/debian/patches/0003-php-Fix-that-recursion-limit-is-not-enforced.patch
--- protobuf-3.21.12/debian/patches/0003-php-Fix-that-recursion-limit-is-not-enforced.patch	1970-01-01 02:00:00.000000000 +0200
+++ protobuf-3.21.12/debian/patches/0003-php-Fix-that-recursion-limit-is-not-enforced.patch	2026-06-21 11:05:31.000000000 +0300
@@ -0,0 +1,65 @@
+From 16db0faafd1c7357a7d91e53dc7deea10e9bbab9 Mon Sep 17 00:00:00 2001
+From: Protobuf Team Bot <[email protected]>
+Date: Wed, 14 Jan 2026 11:09:03 -0800
+Subject: php: Fix that recursion limit is not enforced.
+
+https://github.com/protocolbuffers/protobuf/issues/25067
+
+PiperOrigin-RevId: 856286406
+---
+ .../Protobuf/Internal/CodedInputStream.php    |  2 +-
+ php/tests/EncodeDecodeTest.php                | 25 +++++++++++++++++++
+ 2 files changed, 26 insertions(+), 1 deletion(-)
+
+diff --git a/php/src/Google/Protobuf/Internal/CodedInputStream.php b/php/src/Google/Protobuf/Internal/CodedInputStream.php
+index 0f98be7a6..52e8f5473 100644
+--- a/php/src/Google/Protobuf/Internal/CodedInputStream.php
++++ b/php/src/Google/Protobuf/Internal/CodedInputStream.php
+@@ -362,7 +362,7 @@ class CodedInputStream
+         $byte_limit, &$old_limit, &$recursion_budget)
+     {
+         $old_limit = $this->pushLimit($byte_limit);
+-        $recursion_limit = --$this->recursion_limit;
++        $recursion_budget = --$this->recursion_budget;
+     }
+ 
+     public function decrementRecursionDepthAndPopLimit($byte_limit)
+diff --git a/php/tests/EncodeDecodeTest.php b/php/tests/EncodeDecodeTest.php
+index ddd01a1ac..e4889aefc 100644
+--- a/php/tests/EncodeDecodeTest.php
++++ b/php/tests/EncodeDecodeTest.php
+@@ -545,6 +545,31 @@ class EncodeDecodeTest extends TestBase
+         $m->mergeFromString(hex2bin("0afaffffff0f"));
+     }
+ 
++    private function makeRecursiveMessage($depth) {
++        $m = new TestMessage();
++        $m->setOptionalInt32(1);
++        if ($depth == 0) {
++            return $m;
++        }
++        $m->setRecursive($this->makeRecursiveMessage($depth - 1));
++        return $m;
++    }
++
++    public function testRecursiveMessage() {
++        $payload = $this->makeRecursiveMessage(99)->serializeToString();
++
++        $m = new TestMessage();
++        $m->mergeFromString($payload);
++    }
++
++    public function testOverlyRecursiveMessage() {
++        $this->expectException(Exception::class);
++        $payload = $this->makeRecursiveMessage(101)->serializeToString();
++
++        $m = new TestMessage();
++        $m->mergeFromString($payload);
++    }
++
+     public function testRandomFieldOrder()
+     {
+         $m = new TestRandomFieldOrder();
+-- 
+2.47.3
+
diff -Nru protobuf-3.21.12/debian/patches/series protobuf-3.21.12/debian/patches/series
--- protobuf-3.21.12/debian/patches/series	2025-09-29 15:11:47.000000000 +0300
+++ protobuf-3.21.12/debian/patches/series	2026-06-21 12:05:51.000000000 +0300
@@ -29,3 +29,6 @@
 CVE-2025-4565-3.patch
 protobuf-add-loongarch64-support.patch
 0001-Change-PROTOBUF_MUSTTAIL-to-affirmatively-use-the-mu.patch
+0001-Fix-Any-recursion-depth-bypass-in-Python-json_format.patch
+0002-Check-that-readRaw-does-not-accept-negative-length-v.patch
+0003-php-Fix-that-recursion-limit-is-not-enforced.patch

Reply via email to