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

