Version in base suite: 3.21.12-11 Base version: protobuf_3.21.12-11 Target version: protobuf_3.21.12-11+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/protobuf/protobuf_3.21.12-11.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/protobuf/protobuf_3.21.12-11+deb13u1.dsc changelog | 18 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/CVE-2024-7254-1.patch | 202 ++ patches/CVE-2024-7254-2.patch | 143 ++ patches/CVE-2024-7254-3.patch | 684 ++++++++++ patches/CVE-2024-7254-4.patch | 21 patches/CVE-2024-7254-5.patch | 66 patches/CVE-2024-7254.patch | 143 -- patches/CVE-2025-4565-1.patch | 117 + patches/CVE-2025-4565-2.patch | 395 +++++ patches/CVE-2025-4565-3.patch | 207 +++ patches/series | 12 14 files changed, 2146 insertions(+), 144 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpnct012ur/protobuf_3.21.12-11.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpnct012ur/protobuf_3.21.12-11+deb13u1.dsc: no acceptable signature found diff -Nru protobuf-3.21.12/debian/changelog protobuf-3.21.12/debian/changelog --- protobuf-3.21.12/debian/changelog 2025-04-29 19:27:02.000000000 +0000 +++ protobuf-3.21.12/debian/changelog 2026-07-02 19:28:07.000000000 +0000 @@ -1,3 +1,21 @@ +protobuf (3.21.12-11+deb13u1) trixie; urgency=medium + + * Non-maintainer upload. + * Fix CVE-2026-0994: JSON recursion depth bypass (closes: #1126302). + * Fix CVE-2026-6409: PHP Denial of Service (closes: #1134895). + + [ Hlib Korzhynskyy ] + * Complete fix of CVE-2024-7254 (closes: #1082381): + - add recursion checks and recursion limit, + - add tests. + + [ Laszlo Boszormenyi (GCS) ] + * Fix CVE-2025-4565: data containing an arbitrary number of recursive + groups, recursive messages or a series of SGROUP tags can be corrupted + by exceeding the Python recursion limit (closes: #1108057). + + -- Adrian Bunk Thu, 02 Jul 2026 22:28:07 +0300 + protobuf (3.21.12-11) unstable; urgency=high * Fix CVE-2024-7254: when parsing unknown fields in the Protobuf Java Lite 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 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/0001-Fix-Any-recursion-depth-bypass-in-Python-json_format.patch 2026-07-02 19:07:59.000000000 +0000 @@ -0,0 +1,169 @@ +From 425bbbbb1bba4f76cbae984b75fefbc84666d801 Mon Sep 17 00:00:00 2001 +From: zhangskz +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 +--- + .../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 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/0002-Check-that-readRaw-does-not-accept-negative-length-v.patch 2026-07-02 19:07:59.000000000 +0000 @@ -0,0 +1,48 @@ +From 8d3c56f84a1158095f28ef859757872df6c176cc Mon Sep 17 00:00:00 2001 +From: Protobuf Team Bot +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 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/0003-php-Fix-that-recursion-limit-is-not-enforced.patch 2026-07-02 19:07:59.000000000 +0000 @@ -0,0 +1,65 @@ +From 16db0faafd1c7357a7d91e53dc7deea10e9bbab9 Mon Sep 17 00:00:00 2001 +From: Protobuf Team Bot +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/CVE-2024-7254-1.patch protobuf-3.21.12/debian/patches/CVE-2024-7254-1.patch --- protobuf-3.21.12/debian/patches/CVE-2024-7254-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/CVE-2024-7254-1.patch 2026-07-02 19:07:33.000000000 +0000 @@ -0,0 +1,202 @@ +Backport of: + +From: Protobuf Team Bot +Date: Fri, 16 Aug 2024 16:57:47 -0700 +Subject: [PATCH 1/5] Internal change + +PiperOrigin-RevId: 663919912 +--- + .../com/google/protobuf/CodedInputStream.java | 106 ++++-------------- + .../com/google/protobuf/map_lite_test.proto | 3 +- + .../proto/com/google/protobuf/map_test.proto | 3 +- + .../java/com/google/protobuf/LiteTest.java | 1 + + 4 files changed, 29 insertions(+), 84 deletions(-) + +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/CodedInputStream.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/CodedInputStream.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/CodedInputStream.java +@@ -246,13 +246,35 @@ public abstract class CodedInputStream { + * Reads and discards an entire message. This will read either until EOF or until an endgroup tag, + * whichever comes first. + */ +- public abstract void skipMessage() throws IOException; ++ public void skipMessage() throws IOException { ++ while (true) { ++ final int tag = readTag(); ++ if (tag == 0) { ++ return; ++ } ++ boolean fieldSkipped = skipField(tag); ++ if (!fieldSkipped) { ++ return; ++ } ++ } ++ } + + /** + * Reads an entire message and writes it to output in wire format. This will read either until EOF + * or until an endgroup tag, whichever comes first. + */ +- public abstract void skipMessage(CodedOutputStream output) throws IOException; ++ public void skipMessage(CodedOutputStream output) throws IOException { ++ while (true) { ++ final int tag = readTag(); ++ if (tag == 0) { ++ return; ++ } ++ boolean fieldSkipped = skipField(tag, output); ++ if (!fieldSkipped) { ++ return; ++ } ++ } ++ } + + + // ----------------------------------------------------------------- +@@ -725,27 +747,6 @@ public abstract class CodedInputStream { + } + } + +- @Override +- public void skipMessage() throws IOException { +- while (true) { +- final int tag = readTag(); +- if (tag == 0 || !skipField(tag)) { +- return; +- } +- } +- } +- +- @Override +- public void skipMessage(CodedOutputStream output) throws IOException { +- while (true) { +- final int tag = readTag(); +- if (tag == 0 || !skipField(tag, output)) { +- return; +- } +- } +- } +- +- + // ----------------------------------------------------------------- + + @Override +@@ -1440,27 +1441,6 @@ public abstract class CodedInputStream { + } + } + +- @Override +- public void skipMessage() throws IOException { +- while (true) { +- final int tag = readTag(); +- if (tag == 0 || !skipField(tag)) { +- return; +- } +- } +- } +- +- @Override +- public void skipMessage(CodedOutputStream output) throws IOException { +- while (true) { +- final int tag = readTag(); +- if (tag == 0 || !skipField(tag, output)) { +- return; +- } +- } +- } +- +- + // ----------------------------------------------------------------- + + @Override +@@ -2208,26 +2188,6 @@ public abstract class CodedInputStream { + } + } + +- @Override +- public void skipMessage() throws IOException { +- while (true) { +- final int tag = readTag(); +- if (tag == 0 || !skipField(tag)) { +- return; +- } +- } +- } +- +- @Override +- public void skipMessage(CodedOutputStream output) throws IOException { +- while (true) { +- final int tag = readTag(); +- if (tag == 0 || !skipField(tag, output)) { +- return; +- } +- } +- } +- + /** Collects the bytes skipped and returns the data in a ByteBuffer. */ + private class SkippedDataSink implements RefillCallback { + private int lastPos = pos; +@@ -3329,26 +3289,6 @@ public abstract class CodedInputStream { + } + } + +- @Override +- public void skipMessage() throws IOException { +- while (true) { +- final int tag = readTag(); +- if (tag == 0 || !skipField(tag)) { +- return; +- } +- } +- } +- +- @Override +- public void skipMessage(CodedOutputStream output) throws IOException { +- while (true) { +- final int tag = readTag(); +- if (tag == 0 || !skipField(tag, output)) { +- return; +- } +- } +- } +- + // ----------------------------------------------------------------- + + @Override +Index: protobuf-3.21.12/java/core/src/test/proto/com/google/protobuf/map_lite_test.proto +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/test/proto/com/google/protobuf/map_lite_test.proto ++++ protobuf-3.21.12/java/core/src/test/proto/com/google/protobuf/map_lite_test.proto +@@ -122,5 +122,6 @@ message ReservedAsMapFieldWithEnumValue + + // https://github.com/protocolbuffers/protobuf/issues/9785 + message MapContainer { +- map my_map = 1; ++ map my_map = 1; ++ map m = 3; + } +Index: protobuf-3.21.12/java/core/src/test/proto/com/google/protobuf/map_test.proto +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/test/proto/com/google/protobuf/map_test.proto ++++ protobuf-3.21.12/java/core/src/test/proto/com/google/protobuf/map_test.proto +@@ -121,5 +121,6 @@ message ReservedAsMapFieldWithEnumValue + + // https://github.com/protocolbuffers/protobuf/issues/9785 + message MapContainer { +- map my_map = 1; ++ map my_map = 1; ++ map m = 3; + } +Index: protobuf-3.21.12/java/lite/src/test/java/com/google/protobuf/LiteTest.java +=================================================================== +--- protobuf-3.21.12.orig/java/lite/src/test/java/com/google/protobuf/LiteTest.java ++++ protobuf-3.21.12/java/lite/src/test/java/com/google/protobuf/LiteTest.java +@@ -51,6 +51,7 @@ import com.google.protobuf.UnittestLite. + import com.google.protobuf.UnittestLite.TestAllTypesLiteOrBuilder; + import com.google.protobuf.UnittestLite.TestHugeFieldNumbersLite; + import com.google.protobuf.UnittestLite.TestNestedExtensionLite; ++import map_lite_test.MapTestProto.MapContainer; + import map_lite_test.MapTestProto.TestMap; + import map_lite_test.MapTestProto.TestMap.MessageValue; + import protobuf_unittest.NestedExtensionLite; diff -Nru protobuf-3.21.12/debian/patches/CVE-2024-7254-2.patch protobuf-3.21.12/debian/patches/CVE-2024-7254-2.patch --- protobuf-3.21.12/debian/patches/CVE-2024-7254-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/CVE-2024-7254-2.patch 2026-07-02 19:07:33.000000000 +0000 @@ -0,0 +1,143 @@ +From 850fcce9176e2c9070614dab53537760498c926b Mon Sep 17 00:00:00 2001 +From: Protobuf Team Bot +Date: Thu, 18 Jul 2024 07:41:01 -0700 +Subject: [PATCH 2/5] Internal change + +PiperOrigin-RevId: 653615736 +--- + .../core/src/main/java/com/google/protobuf/ArrayDecoders.java | 3 +-- + .../com/google/protobuf/InvalidProtocolBufferException.java | 2 +- + .../core/src/main/java/com/google/protobuf/MessageSchema.java | 3 +++ + .../src/main/java/com/google/protobuf/MessageSetSchema.java | 1 + + .../src/main/java/com/google/protobuf/UnknownFieldSchema.java | 3 +-- + java/lite/src/test/java/com/google/protobuf/LiteTest.java | 3 +++ + src/google/protobuf/unittest_lite.proto | 4 ++++ + 7 files changed, 14 insertions(+), 5 deletions(-) + +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java +@@ -47,8 +47,7 @@ import java.io.IOException; + @CheckReturnValue + final class ArrayDecoders { + +- private ArrayDecoders() { +- } ++ private ArrayDecoders() {} + + /** + * A helper used to return multiple values in a Java function. Java doesn't natively support +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java +@@ -155,7 +155,7 @@ public class InvalidProtocolBufferExcept + static InvalidProtocolBufferException recursionLimitExceeded() { + return new InvalidProtocolBufferException( + "Protocol message had too many levels of nesting. May be malicious. " +- + "Use CodedInputStream.setRecursionLimit() to increase the depth limit."); ++ + "Use setRecursionLimit() to increase the recursion depth limit."); + } + + static InvalidProtocolBufferException sizeLimitExceeded() { +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/MessageSchema.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/MessageSchema.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/MessageSchema.java +@@ -3946,6 +3946,7 @@ final class MessageSchema implements + unknownFields = unknownFieldSchema.getBuilderFromMessage(message); + } + // Unknown field. ++ + if (unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { + continue; + } +@@ -4321,6 +4322,7 @@ final class MessageSchema implements + if (unknownFields == null) { + unknownFields = unknownFieldSchema.getBuilderFromMessage(message); + } ++ + if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { + return; + } +@@ -4337,6 +4339,7 @@ final class MessageSchema implements + if (unknownFields == null) { + unknownFields = unknownFieldSchema.getBuilderFromMessage(message); + } ++ + if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { + return; + } +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java +@@ -301,6 +301,7 @@ final class MessageSetSchema implemen + reader, extension, extensionRegistry, extensions); + return true; + } else { ++ + return unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader); + } + } else { +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java +@@ -78,7 +78,6 @@ abstract class UnknownFieldSchema + /** Marks unknown fields as immutable. */ + abstract void makeImmutable(Object message); + +- /** Merges one field into the unknown fields. */ + final boolean mergeOneFieldFrom(B unknownFields, Reader reader) throws IOException { + int tag = reader.getTag(); + int fieldNumber = WireFormat.getTagFieldNumber(tag); +@@ -111,7 +110,7 @@ abstract class UnknownFieldSchema + } + } + +- final void mergeFrom(B unknownFields, Reader reader) throws IOException { ++ private final void mergeFrom(B unknownFields, Reader reader) throws IOException { + while (true) { + if (reader.getFieldNumber() == Reader.READ_DONE + || !mergeOneFieldFrom(unknownFields, reader)) { +Index: protobuf-3.21.12/java/lite/src/test/java/com/google/protobuf/LiteTest.java +=================================================================== +--- protobuf-3.21.12.orig/java/lite/src/test/java/com/google/protobuf/LiteTest.java ++++ protobuf-3.21.12/java/lite/src/test/java/com/google/protobuf/LiteTest.java +@@ -33,12 +33,14 @@ package com.google.protobuf; + import static com.google.common.truth.Truth.assertThat; + import static com.google.common.truth.Truth.assertWithMessage; + import static java.util.Collections.singletonList; ++import static org.junit.Assert.assertThrows; + + import com.google.protobuf.FieldPresenceTestProto.TestAllTypes; + import com.google.protobuf.UnittestImportLite.ImportEnumLite; + import com.google.protobuf.UnittestImportPublicLite.PublicImportMessageLite; + import com.google.protobuf.UnittestLite.ForeignEnumLite; + import com.google.protobuf.UnittestLite.ForeignMessageLite; ++import com.google.protobuf.UnittestLite.RecursiveGroup; + import com.google.protobuf.UnittestLite.RecursiveMessage; + import com.google.protobuf.UnittestLite.TestAllExtensionsLite; + import com.google.protobuf.UnittestLite.TestAllTypesLite; +@@ -73,6 +75,7 @@ import java.util.ArrayList; + import java.util.Arrays; + import java.util.Iterator; + import java.util.List; ++import java.util.concurrent.atomic.AtomicBoolean; + import org.junit.Before; + import org.junit.Test; + import org.junit.runner.RunWith; +Index: protobuf-3.21.12/src/google/protobuf/unittest_lite.proto +=================================================================== +--- protobuf-3.21.12.orig/src/google/protobuf/unittest_lite.proto ++++ protobuf-3.21.12/src/google/protobuf/unittest_lite.proto +@@ -518,3 +518,7 @@ message RecursiveMessage { + optional RecursiveMessage recurse = 1; + optional bytes payload = 2; + } ++ ++message RecursiveGroup { ++ RecursiveGroup recurse = 1 [features.message_encoding = DELIMITED]; ++} diff -Nru protobuf-3.21.12/debian/patches/CVE-2024-7254-3.patch protobuf-3.21.12/debian/patches/CVE-2024-7254-3.patch --- protobuf-3.21.12/debian/patches/CVE-2024-7254-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/CVE-2024-7254-3.patch 2026-07-02 19:07:33.000000000 +0000 @@ -0,0 +1,684 @@ +Backport of: + +From 4728531c162f2f9e8c2ca1add713cfee2db6be3b Mon Sep 17 00:00:00 2001 +From: Protobuf Team Bot +Date: Tue, 17 Sep 2024 12:03:36 -0700 +Subject: [PATCH 3/5] Add recursion check when parsing unknown fields in Java. + +PiperOrigin-RevId: 675657198 +--- + .../com/google/protobuf/ArrayDecoders.java | 28 +++ + .../com/google/protobuf/CodedInputStream.java | 6 + + .../com/google/protobuf/MessageSchema.java | 12 +- + .../com/google/protobuf/MessageSetSchema.java | 3 +- + .../google/protobuf/UnknownFieldSchema.java | 29 ++- + .../google/protobuf/CodedInputStreamTest.java | 158 ++++++++++++ + .../java/com/google/protobuf/LiteTest.java | 232 ++++++++++++++++++ + 7 files changed, 456 insertions(+), 12 deletions(-) + +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java +@@ -46,6 +46,10 @@ import java.io.IOException; + */ + @CheckReturnValue + final class ArrayDecoders { ++ static final int DEFAULT_RECURSION_LIMIT = 100; ++ ++ @SuppressWarnings("NonFinalStaticField") ++ private static volatile int recursionLimit = DEFAULT_RECURSION_LIMIT; + + private ArrayDecoders() {} + +@@ -60,6 +64,7 @@ final class ArrayDecoders { + public long long1; + public Object object1; + public final ExtensionRegistryLite extensionRegistry; ++ public int recursionDepth; + + Registers() { + this.extensionRegistry = ExtensionRegistryLite.getEmptyRegistry(); +@@ -267,7 +272,10 @@ final class ArrayDecoders { + if (length < 0 || length > limit - position) { + throw InvalidProtocolBufferException.truncatedMessage(); + } ++ registers.recursionDepth++; ++ checkRecursionLimit(registers.recursionDepth); + schema.mergeFrom(msg, data, position, position + length, registers); ++ registers.recursionDepth--; + registers.object1 = msg; + return position + length; + } +@@ -285,9 +293,12 @@ final class ArrayDecoders { + // A group field must has a MessageSchema (the only other subclass of Schema is MessageSetSchema + // and it can't be used in group fields). + final MessageSchema messageSchema = (MessageSchema) schema; ++ registers.recursionDepth++; ++ checkRecursionLimit(registers.recursionDepth); + // It's OK to directly use parseProto2Message since proto3 doesn't have group. + final int endPosition = + messageSchema.parseProto2Message(msg, data, position, limit, endGroup, registers); ++ registers.recursionDepth--; + registers.object1 = msg; + return endPosition; + } +@@ -1048,6 +1059,8 @@ final class ArrayDecoders { + final UnknownFieldSetLite child = UnknownFieldSetLite.newInstance(); + final int endGroup = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP; + int lastTag = 0; ++ registers.recursionDepth++; ++ checkRecursionLimit(registers.recursionDepth); + while (position < limit) { + position = decodeVarint32(data, position, registers); + lastTag = registers.int1; +@@ -1056,6 +1069,7 @@ final class ArrayDecoders { + } + position = decodeUnknownField(lastTag, data, position, limit, child, registers); + } ++ registers.recursionDepth--; + if (position > limit || lastTag != endGroup) { + throw InvalidProtocolBufferException.parseFailure(); + } +@@ -1102,4 +1116,18 @@ final class ArrayDecoders { + throw InvalidProtocolBufferException.invalidTag(); + } + } ++ ++ /** ++ * Set the maximum recursion limit that ArrayDecoders will allow. An exception will be thrown if ++ * the depth of the message exceeds this limit. ++ */ ++ public static void setRecursionLimit(int limit) { ++ recursionLimit = limit; ++ } ++ ++ private static void checkRecursionLimit(int depth) throws InvalidProtocolBufferException { ++ if (depth >= recursionLimit) { ++ throw InvalidProtocolBufferException.recursionLimitExceeded(); ++ } ++ } + } +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/CodedInputStream.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/CodedInputStream.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/CodedInputStream.java +@@ -252,7 +252,10 @@ public abstract class CodedInputStream { + if (tag == 0) { + return; + } ++ checkRecursionLimit(); ++ ++recursionDepth; + boolean fieldSkipped = skipField(tag); ++ --recursionDepth; + if (!fieldSkipped) { + return; + } +@@ -269,7 +272,10 @@ public abstract class CodedInputStream { + if (tag == 0) { + return; + } ++ checkRecursionLimit(); ++ ++recursionDepth; + boolean fieldSkipped = skipField(tag, output); ++ --recursionDepth; + if (!fieldSkipped) { + return; + } +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/MessageSchema.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/MessageSchema.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/MessageSchema.java +@@ -3946,8 +3946,8 @@ final class MessageSchema implements + unknownFields = unknownFieldSchema.getBuilderFromMessage(message); + } + // Unknown field. +- +- if (unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { ++ if (unknownFieldSchema.mergeOneFieldFrom( ++ unknownFields, reader, /* currentDepth= */ 0)) { + continue; + } + } +@@ -4322,8 +4322,8 @@ final class MessageSchema implements + if (unknownFields == null) { + unknownFields = unknownFieldSchema.getBuilderFromMessage(message); + } +- +- if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { ++ if (!unknownFieldSchema.mergeOneFieldFrom( ++ unknownFields, reader, /* currentDepth= */ 0)) { + return; + } + break; +@@ -4339,8 +4339,8 @@ final class MessageSchema implements + if (unknownFields == null) { + unknownFields = unknownFieldSchema.getBuilderFromMessage(message); + } +- +- if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { ++ if (!unknownFieldSchema.mergeOneFieldFrom( ++ unknownFields, reader, /* currentDepth= */ 0)) { + return; + } + } +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java +@@ -301,8 +301,7 @@ final class MessageSetSchema implemen + reader, extension, extensionRegistry, extensions); + return true; + } else { +- +- return unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader); ++ return unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader, /* currentDepth= */ 0); + } + } else { + return reader.skipField(); +Index: protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java ++++ protobuf-3.21.12/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java +@@ -36,6 +36,11 @@ import java.io.IOException; + @CheckReturnValue + abstract class UnknownFieldSchema { + ++ static final int DEFAULT_RECURSION_LIMIT = 100; ++ ++ @SuppressWarnings("NonFinalStaticField") ++ private static volatile int recursionLimit = DEFAULT_RECURSION_LIMIT; ++ + /** Whether unknown fields should be dropped. */ + abstract boolean shouldDiscardUnknownFields(Reader reader); + +@@ -78,7 +83,9 @@ abstract class UnknownFieldSchema + /** Marks unknown fields as immutable. */ + abstract void makeImmutable(Object message); + +- final boolean mergeOneFieldFrom(B unknownFields, Reader reader) throws IOException { ++ /** Merges one field into the unknown fields. */ ++ final boolean mergeOneFieldFrom(B unknownFields, Reader reader, int currentDepth) ++ throws IOException { + int tag = reader.getTag(); + int fieldNumber = WireFormat.getTagFieldNumber(tag); + switch (WireFormat.getTagWireType(tag)) { +@@ -97,7 +104,12 @@ abstract class UnknownFieldSchema + case WireFormat.WIRETYPE_START_GROUP: + final B subFields = newBuilder(); + int endGroupTag = WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); +- mergeFrom(subFields, reader); ++ currentDepth++; ++ if (currentDepth >= recursionLimit) { ++ throw InvalidProtocolBufferException.recursionLimitExceeded(); ++ } ++ mergeFrom(subFields, reader, currentDepth); ++ currentDepth--; + if (endGroupTag != reader.getTag()) { + throw InvalidProtocolBufferException.invalidEndTag(); + } +@@ -110,10 +122,11 @@ abstract class UnknownFieldSchema + } + } + +- private final void mergeFrom(B unknownFields, Reader reader) throws IOException { ++ private final void mergeFrom(B unknownFields, Reader reader, int currentDepth) ++ throws IOException { + while (true) { + if (reader.getFieldNumber() == Reader.READ_DONE +- || !mergeOneFieldFrom(unknownFields, reader)) { ++ || !mergeOneFieldFrom(unknownFields, reader, currentDepth)) { + break; + } + } +@@ -130,4 +143,12 @@ abstract class UnknownFieldSchema + abstract int getSerializedSizeAsMessageSet(T message); + + abstract int getSerializedSize(T unknowns); ++ ++ /** ++ * Set the maximum recursion limit that ArrayDecoders will allow. An exception will be thrown if ++ * the depth of the message exceeds this limit. ++ */ ++ public void setRecursionLimit(int limit) { ++ recursionLimit = limit; ++ } + } +Index: protobuf-3.21.12/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java +=================================================================== +--- protobuf-3.21.12.orig/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java ++++ protobuf-3.21.12/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java +@@ -33,6 +33,9 @@ package com.google.protobuf; + import static com.google.common.truth.Truth.assertThat; + import static com.google.common.truth.Truth.assertWithMessage; + import static org.junit.Assert.assertArrayEquals; ++ ++import com.google.common.primitives.Bytes; ++import map_test.MapTestProto.MapContainer; + import protobuf_unittest.UnittestProto.BoolMessage; + import protobuf_unittest.UnittestProto.Int32Message; + import protobuf_unittest.UnittestProto.Int64Message; +@@ -57,6 +60,13 @@ public class CodedInputStreamTest { + + private static final int DEFAULT_BLOCK_SIZE = 4096; + ++ private static final int GROUP_TAP = WireFormat.makeTag(3, WireFormat.WIRETYPE_START_GROUP); ++ ++ private static final byte[] NESTING_SGROUP = generateSGroupTags(); ++ ++ private static final byte[] NESTING_SGROUP_WITH_INITIAL_BYTES = generateSGroupTagsForMapField(); ++ ++ + private enum InputType { + ARRAY { + @Override +@@ -139,6 +149,17 @@ public class CodedInputStreamTest { + return bytes; + } + ++ private static byte[] generateSGroupTags() { ++ byte[] bytes = new byte[100000]; ++ Arrays.fill(bytes, (byte) GROUP_TAP); ++ return bytes; ++ } ++ ++ private static byte[] generateSGroupTagsForMapField() { ++ byte[] initialBytes = {18, 1, 75, 26, (byte) 198, (byte) 154, 12}; ++ return Bytes.concat(initialBytes, NESTING_SGROUP); ++ } ++ + /** + * An InputStream which limits the number of bytes it reads at a time. We use this to make sure + * that CodedInputStream doesn't screw up when reading in small blocks. +@@ -684,6 +705,143 @@ public class CodedInputStreamTest { + } + } + ++ @Test ++ public void testMaliciousRecursion_unknownFields() throws Exception { ++ Throwable thrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> TestRecursiveMessage.parseFrom(NESTING_SGROUP)); ++ ++ assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testMaliciousRecursion_skippingUnknownField() throws Exception { ++ Throwable thrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> ++ DiscardUnknownFieldsParser.wrap(TestRecursiveMessage.parser()) ++ .parseFrom(NESTING_SGROUP)); ++ ++ assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testMaliciousSGroupTagsWithMapField_fromInputStream() throws Exception { ++ Throwable parseFromThrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> ++ MapContainer.parseFrom( ++ new ByteArrayInputStream(NESTING_SGROUP_WITH_INITIAL_BYTES))); ++ Throwable mergeFromThrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> ++ MapContainer.newBuilder() ++ .mergeFrom(new ByteArrayInputStream(NESTING_SGROUP_WITH_INITIAL_BYTES))); ++ ++ assertThat(parseFromThrown) ++ .hasMessageThat() ++ .contains("Protocol message had too many levels of nesting"); ++ assertThat(mergeFromThrown) ++ .hasMessageThat() ++ .contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testMaliciousSGroupTags_inputStream_skipMessage() throws Exception { ++ ByteArrayInputStream inputSteam = new ByteArrayInputStream(NESTING_SGROUP); ++ CodedInputStream input = CodedInputStream.newInstance(inputSteam); ++ CodedOutputStream output = CodedOutputStream.newInstance(new byte[NESTING_SGROUP.length]); ++ ++ Throwable thrown = assertThrows(InvalidProtocolBufferException.class, input::skipMessage); ++ Throwable thrown2 = ++ assertThrows(InvalidProtocolBufferException.class, () -> input.skipMessage(output)); ++ ++ assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); ++ assertThat(thrown2) ++ .hasMessageThat() ++ .contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testMaliciousSGroupTagsWithMapField_fromByteArray() throws Exception { ++ Throwable parseFromThrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> MapContainer.parseFrom(NESTING_SGROUP_WITH_INITIAL_BYTES)); ++ Throwable mergeFromThrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> MapContainer.newBuilder().mergeFrom(NESTING_SGROUP_WITH_INITIAL_BYTES)); ++ ++ assertThat(parseFromThrown) ++ .hasMessageThat() ++ .contains("the input ended unexpectedly in the middle of a field"); ++ assertThat(mergeFromThrown) ++ .hasMessageThat() ++ .contains("the input ended unexpectedly in the middle of a field"); ++ } ++ ++ @Test ++ public void testMaliciousSGroupTags_arrayDecoder_skipMessage() throws Exception { ++ CodedInputStream input = CodedInputStream.newInstance(NESTING_SGROUP); ++ CodedOutputStream output = CodedOutputStream.newInstance(new byte[NESTING_SGROUP.length]); ++ ++ Throwable thrown = assertThrows(InvalidProtocolBufferException.class, input::skipMessage); ++ Throwable thrown2 = ++ assertThrows(InvalidProtocolBufferException.class, () -> input.skipMessage(output)); ++ ++ assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); ++ assertThat(thrown2) ++ .hasMessageThat() ++ .contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testMaliciousSGroupTagsWithMapField_fromByteBuffer() throws Exception { ++ Throwable thrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> MapContainer.parseFrom(ByteBuffer.wrap(NESTING_SGROUP_WITH_INITIAL_BYTES))); ++ ++ assertThat(thrown) ++ .hasMessageThat() ++ .contains("the input ended unexpectedly in the middle of a field"); ++ } ++ ++ @Test ++ public void testMaliciousSGroupTags_byteBuffer_skipMessage() throws Exception { ++ CodedInputStream input = InputType.NIO_DIRECT.newDecoder(NESTING_SGROUP); ++ CodedOutputStream output = CodedOutputStream.newInstance(new byte[NESTING_SGROUP.length]); ++ ++ Throwable thrown = assertThrows(InvalidProtocolBufferException.class, input::skipMessage); ++ Throwable thrown2 = ++ assertThrows(InvalidProtocolBufferException.class, () -> input.skipMessage(output)); ++ ++ assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); ++ assertThat(thrown2) ++ .hasMessageThat() ++ .contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testMaliciousSGroupTags_iterableByteBuffer() throws Exception { ++ CodedInputStream input = InputType.ITER_DIRECT.newDecoder(NESTING_SGROUP); ++ CodedOutputStream output = CodedOutputStream.newInstance(new byte[NESTING_SGROUP.length]); ++ ++ Throwable thrown = assertThrows(InvalidProtocolBufferException.class, input::skipMessage); ++ Throwable thrown2 = ++ assertThrows(InvalidProtocolBufferException.class, () -> input.skipMessage(output)); ++ ++ assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); ++ assertThat(thrown2) ++ .hasMessageThat() ++ .contains("Protocol message had too many levels of nesting"); ++ } ++ + private void checkSizeLimitExceeded(InvalidProtocolBufferException e) { + assertThat(e) + .hasMessageThat() +Index: protobuf-3.21.12/java/lite/src/test/java/com/google/protobuf/LiteTest.java +=================================================================== +--- protobuf-3.21.12.orig/java/lite/src/test/java/com/google/protobuf/LiteTest.java ++++ protobuf-3.21.12/java/lite/src/test/java/com/google/protobuf/LiteTest.java +@@ -2461,6 +2461,211 @@ public class LiteTest { + } + + @Test ++ public void testParseFromInputStream_concurrent_nestingUnknownGroups() throws Exception { ++ int numThreads = 200; ++ ArrayList threads = new ArrayList<>(); ++ ++ ByteString byteString = generateNestingGroups(99); ++ AtomicBoolean thrown = new AtomicBoolean(false); ++ ++ for (int i = 0; i < numThreads; i++) { ++ Thread thread = ++ new Thread( ++ () -> { ++ try { ++ TestAllTypesLite unused = TestAllTypesLite.parseFrom(byteString); ++ } catch (IOException e) { ++ if (e.getMessage().contains("Protocol message had too many levels of nesting")) { ++ thrown.set(true); ++ } ++ } ++ }); ++ thread.start(); ++ threads.add(thread); ++ } ++ ++ for (Thread thread : threads) { ++ thread.join(); ++ } ++ ++ assertThat(thrown.get()).isFalse(); ++ } ++ ++ @Test ++ public void testParseFromInputStream_nestingUnknownGroups() throws IOException { ++ ByteString byteString = generateNestingGroups(99); ++ ++ Throwable thrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, () -> TestAllTypesLite.parseFrom(byteString)); ++ assertThat(thrown) ++ .hasMessageThat() ++ .doesNotContain("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testParseFromInputStream_nestingUnknownGroups_exception() throws IOException { ++ ByteString byteString = generateNestingGroups(100); ++ ++ Throwable thrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, () -> TestAllTypesLite.parseFrom(byteString)); ++ assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testParseFromInputStream_setRecursionLimit_exception() throws IOException { ++ ByteString byteString = generateNestingGroups(199); ++ UnknownFieldSchema schema = SchemaUtil.unknownFieldSetLiteSchema(); ++ schema.setRecursionLimit(200); ++ ++ Throwable thrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, () -> TestAllTypesLite.parseFrom(byteString)); ++ assertThat(thrown) ++ .hasMessageThat() ++ .doesNotContain("Protocol message had too many levels of nesting"); ++ schema.setRecursionLimit(UnknownFieldSchema.DEFAULT_RECURSION_LIMIT); ++ } ++ ++ @Test ++ public void testParseFromBytes_concurrent_nestingUnknownGroups() throws Exception { ++ int numThreads = 200; ++ ArrayList threads = new ArrayList<>(); ++ ++ ByteString byteString = generateNestingGroups(99); ++ AtomicBoolean thrown = new AtomicBoolean(false); ++ ++ for (int i = 0; i < numThreads; i++) { ++ Thread thread = ++ new Thread( ++ () -> { ++ try { ++ // Should pass in byte[] instead of ByteString to go into ArrayDecoders. ++ TestAllTypesLite unused = TestAllTypesLite.parseFrom(byteString.toByteArray()); ++ } catch (InvalidProtocolBufferException e) { ++ if (e.getMessage().contains("Protocol message had too many levels of nesting")) { ++ thrown.set(true); ++ } ++ } ++ }); ++ thread.start(); ++ threads.add(thread); ++ } ++ ++ for (Thread thread : threads) { ++ thread.join(); ++ } ++ ++ assertThat(thrown.get()).isFalse(); ++ } ++ ++ @Test ++ public void testParseFromBytes_nestingUnknownGroups() throws IOException { ++ ByteString byteString = generateNestingGroups(99); ++ ++ Throwable thrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> TestAllTypesLite.parseFrom(byteString.toByteArray())); ++ assertThat(thrown) ++ .hasMessageThat() ++ .doesNotContain("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testParseFromBytes_nestingUnknownGroups_exception() throws IOException { ++ ByteString byteString = generateNestingGroups(100); ++ ++ Throwable thrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> TestAllTypesLite.parseFrom(byteString.toByteArray())); ++ assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testParseFromBytes_setRecursionLimit_exception() throws IOException { ++ ByteString byteString = generateNestingGroups(199); ++ ArrayDecoders.setRecursionLimit(200); ++ ++ Throwable thrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> TestAllTypesLite.parseFrom(byteString.toByteArray())); ++ assertThat(thrown) ++ .hasMessageThat() ++ .doesNotContain("Protocol message had too many levels of nesting"); ++ ArrayDecoders.setRecursionLimit(ArrayDecoders.DEFAULT_RECURSION_LIMIT); ++ } ++ ++ @Test ++ public void testParseFromBytes_recursiveMessages() throws Exception { ++ byte[] data99 = makeRecursiveMessage(99).toByteArray(); ++ byte[] data100 = makeRecursiveMessage(100).toByteArray(); ++ ++ RecursiveMessage unused = RecursiveMessage.parseFrom(data99); ++ Throwable thrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, () -> RecursiveMessage.parseFrom(data100)); ++ assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testParseFromBytes_recursiveKnownGroups() throws Exception { ++ byte[] data99 = makeRecursiveGroup(99).toByteArray(); ++ byte[] data100 = makeRecursiveGroup(100).toByteArray(); ++ ++ RecursiveGroup unused = RecursiveGroup.parseFrom(data99); ++ Throwable thrown = ++ assertThrows(InvalidProtocolBufferException.class, () -> RecursiveGroup.parseFrom(data100)); ++ assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ @SuppressWarnings("ProtoParseFromByteString") ++ public void testMaliciousSGroupTagsWithMapField_fromByteArray() throws Exception { ++ ByteString byteString = generateNestingGroups(102); ++ ++ Throwable parseFromThrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> MapContainer.parseFrom(byteString.toByteArray())); ++ Throwable mergeFromThrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> MapContainer.newBuilder().mergeFrom(byteString.toByteArray())); ++ ++ assertThat(parseFromThrown) ++ .hasMessageThat() ++ .contains("Protocol message had too many levels of nesting"); ++ assertThat(mergeFromThrown) ++ .hasMessageThat() ++ .contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test ++ public void testMaliciousSGroupTagsWithMapField_fromInputStream() throws Exception { ++ byte[] bytes = generateNestingGroups(101).toByteArray(); ++ ++ Throwable parseFromThrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> MapContainer.parseFrom(new ByteArrayInputStream(bytes))); ++ Throwable mergeFromThrown = ++ assertThrows( ++ InvalidProtocolBufferException.class, ++ () -> MapContainer.newBuilder().mergeFrom(new ByteArrayInputStream(bytes))); ++ ++ assertThat(parseFromThrown) ++ .hasMessageThat() ++ .contains("Protocol message had too many levels of nesting"); ++ assertThat(mergeFromThrown) ++ .hasMessageThat() ++ .contains("Protocol message had too many levels of nesting"); ++ } ++ ++ @Test + public void testParseFromByteBuffer_extensions() throws Exception { + TestAllExtensionsLite message = + TestAllExtensionsLite.newBuilder() +@@ -2816,4 +3021,31 @@ public class LiteTest { + } + return false; + } ++ ++ private static ByteString generateNestingGroups(int num) throws IOException { ++ int groupTap = WireFormat.makeTag(3, WireFormat.WIRETYPE_START_GROUP); ++ ByteString.Output byteStringOutput = ByteString.newOutput(); ++ CodedOutputStream codedOutput = CodedOutputStream.newInstance(byteStringOutput); ++ for (int i = 0; i < num; i++) { ++ codedOutput.writeInt32NoTag(groupTap); ++ } ++ codedOutput.flush(); ++ return byteStringOutput.toByteString(); ++ } ++ ++ private static RecursiveMessage makeRecursiveMessage(int num) { ++ if (num == 0) { ++ return RecursiveMessage.getDefaultInstance(); ++ } else { ++ return RecursiveMessage.newBuilder().setRecurse(makeRecursiveMessage(num - 1)).build(); ++ } ++ } ++ ++ private static RecursiveGroup makeRecursiveGroup(int num) { ++ if (num == 0) { ++ return RecursiveGroup.getDefaultInstance(); ++ } else { ++ return RecursiveGroup.newBuilder().setRecurse(makeRecursiveGroup(num - 1)).build(); ++ } ++ } + } diff -Nru protobuf-3.21.12/debian/patches/CVE-2024-7254-4.patch protobuf-3.21.12/debian/patches/CVE-2024-7254-4.patch --- protobuf-3.21.12/debian/patches/CVE-2024-7254-4.patch 1970-01-01 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/CVE-2024-7254-4.patch 2026-07-02 19:07:33.000000000 +0000 @@ -0,0 +1,21 @@ +From f000b7e18fd6921ca02ea4b87608e8cadcb7b64f Mon Sep 17 00:00:00 2001 +From: Sandy Zhang +Date: Wed, 18 Sep 2024 14:57:16 -0400 +Subject: [PATCH 4/5] Fix merge conflict by adding optional label to proto2 + unittest_lite.proto + +--- + src/google/protobuf/unittest_lite.proto | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: protobuf-3.21.12/src/google/protobuf/unittest_lite.proto +=================================================================== +--- protobuf-3.21.12.orig/src/google/protobuf/unittest_lite.proto ++++ protobuf-3.21.12/src/google/protobuf/unittest_lite.proto +@@ -520,5 +520,5 @@ message RecursiveMessage { + } + + message RecursiveGroup { +- RecursiveGroup recurse = 1 [features.message_encoding = DELIMITED]; ++ optional RecursiveGroup recurse = 1 [features.message_encoding = DELIMITED]; + } diff -Nru protobuf-3.21.12/debian/patches/CVE-2024-7254-5.patch protobuf-3.21.12/debian/patches/CVE-2024-7254-5.patch --- protobuf-3.21.12/debian/patches/CVE-2024-7254-5.patch 1970-01-01 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/CVE-2024-7254-5.patch 2026-07-02 19:07:33.000000000 +0000 @@ -0,0 +1,66 @@ +From b5a7cf7cf4b7e39f6b02205e45afe2104a7faf81 Mon Sep 17 00:00:00 2001 +From: Sandy Zhang +Date: Wed, 18 Sep 2024 15:25:25 -0400 +Subject: [PATCH 5/5] Remove RecursiveGroup test case which doesn't exist in + 25.x pre-Editions + +--- + .../java/com/google/protobuf/LiteTest.java | 20 ------------------- + src/google/protobuf/unittest_lite.proto | 4 ---- + 2 files changed, 24 deletions(-) + +Index: protobuf-3.21.12/java/lite/src/test/java/com/google/protobuf/LiteTest.java +=================================================================== +--- protobuf-3.21.12.orig/java/lite/src/test/java/com/google/protobuf/LiteTest.java ++++ protobuf-3.21.12/java/lite/src/test/java/com/google/protobuf/LiteTest.java +@@ -40,7 +40,6 @@ import com.google.protobuf.UnittestImpor + import com.google.protobuf.UnittestImportPublicLite.PublicImportMessageLite; + import com.google.protobuf.UnittestLite.ForeignEnumLite; + import com.google.protobuf.UnittestLite.ForeignMessageLite; +-import com.google.protobuf.UnittestLite.RecursiveGroup; + import com.google.protobuf.UnittestLite.RecursiveMessage; + import com.google.protobuf.UnittestLite.TestAllExtensionsLite; + import com.google.protobuf.UnittestLite.TestAllTypesLite; +@@ -2612,17 +2611,6 @@ public class LiteTest { + } + + @Test +- public void testParseFromBytes_recursiveKnownGroups() throws Exception { +- byte[] data99 = makeRecursiveGroup(99).toByteArray(); +- byte[] data100 = makeRecursiveGroup(100).toByteArray(); +- +- RecursiveGroup unused = RecursiveGroup.parseFrom(data99); +- Throwable thrown = +- assertThrows(InvalidProtocolBufferException.class, () -> RecursiveGroup.parseFrom(data100)); +- assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); +- } +- +- @Test + @SuppressWarnings("ProtoParseFromByteString") + public void testMaliciousSGroupTagsWithMapField_fromByteArray() throws Exception { + ByteString byteString = generateNestingGroups(102); +@@ -3040,12 +3028,4 @@ public class LiteTest { + return RecursiveMessage.newBuilder().setRecurse(makeRecursiveMessage(num - 1)).build(); + } + } +- +- private static RecursiveGroup makeRecursiveGroup(int num) { +- if (num == 0) { +- return RecursiveGroup.getDefaultInstance(); +- } else { +- return RecursiveGroup.newBuilder().setRecurse(makeRecursiveGroup(num - 1)).build(); +- } +- } + } +Index: protobuf-3.21.12/src/google/protobuf/unittest_lite.proto +=================================================================== +--- protobuf-3.21.12.orig/src/google/protobuf/unittest_lite.proto ++++ protobuf-3.21.12/src/google/protobuf/unittest_lite.proto +@@ -518,7 +518,3 @@ message RecursiveMessage { + optional RecursiveMessage recurse = 1; + optional bytes payload = 2; + } +- +-message RecursiveGroup { +- optional RecursiveGroup recurse = 1 [features.message_encoding = DELIMITED]; +-} diff -Nru protobuf-3.21.12/debian/patches/CVE-2024-7254.patch protobuf-3.21.12/debian/patches/CVE-2024-7254.patch --- protobuf-3.21.12/debian/patches/CVE-2024-7254.patch 2025-04-29 19:27:02.000000000 +0000 +++ protobuf-3.21.12/debian/patches/CVE-2024-7254.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,143 +0,0 @@ -From cc8b3483a5584b3301e3d43d17eb59704857ffaa Mon Sep 17 00:00:00 2001 -From: Protobuf Team Bot -Date: Thu, 18 Jul 2024 07:41:01 -0700 -Subject: [PATCH] Internal change - -PiperOrigin-RevId: 653615736 ---- - .../core/src/main/java/com/google/protobuf/ArrayDecoders.java | 3 +-- - .../com/google/protobuf/InvalidProtocolBufferException.java | 2 +- - .../core/src/main/java/com/google/protobuf/MessageSchema.java | 3 +++ - .../src/main/java/com/google/protobuf/MessageSetSchema.java | 1 + - .../src/main/java/com/google/protobuf/UnknownFieldSchema.java | 3 +-- - java/lite/src/test/java/com/google/protobuf/LiteTest.java | 3 +++ - src/google/protobuf/unittest_lite.proto | 4 ++++ - 7 files changed, 14 insertions(+), 5 deletions(-) - -diff --git a/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java b/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java -index f3241de5095c0..9bf14396263a3 100644 ---- a/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java -+++ b/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java -@@ -47,8 +47,7 @@ - @CheckReturnValue - final class ArrayDecoders { - -- private ArrayDecoders() { -- } -+ private ArrayDecoders() {} - - /** - * A helper used to return multiple values in a Java function. Java doesn't natively support -diff --git a/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java b/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java -index 5d10e48884c1a..dbcb9e899dbee 100644 ---- a/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java -+++ b/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java -@@ -155,7 +155,7 @@ public InvalidWireTypeException(String description) { - static InvalidProtocolBufferException recursionLimitExceeded() { - return new InvalidProtocolBufferException( - "Protocol message had too many levels of nesting. May be malicious. " -- + "Use CodedInputStream.setRecursionLimit() to increase the depth limit."); -+ + "Use setRecursionLimit() to increase the recursion depth limit."); - } - - static InvalidProtocolBufferException sizeLimitExceeded() { -diff --git a/java/core/src/main/java/com/google/protobuf/MessageSchema.java b/java/core/src/main/java/com/google/protobuf/MessageSchema.java -index de3890f7023aa..f8f79fcdf8b4c 100644 ---- a/java/core/src/main/java/com/google/protobuf/MessageSchema.java -+++ b/java/core/src/main/java/com/google/protobuf/MessageSchema.java -@@ -3946,6 +3946,7 @@ private > void mergeFromHelper( - unknownFields = unknownFieldSchema.getBuilderFromMessage(message); - } - // Unknown field. -+ - if (unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { - continue; - } -@@ -4321,6 +4322,7 @@ private > void mergeFromHelper( - if (unknownFields == null) { - unknownFields = unknownFieldSchema.getBuilderFromMessage(message); - } -+ - if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { - return; - } -@@ -4337,6 +4339,7 @@ private > void mergeFromHelper( - if (unknownFields == null) { - unknownFields = unknownFieldSchema.getBuilderFromMessage(message); - } -+ - if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { - return; - } -diff --git a/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java b/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java -index eec3acd35ca79..a17037e8efd4e 100644 ---- a/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java -+++ b/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java -@@ -301,6 +301,7 @@ boolean parseMessageSetItemOrUnknownField( - reader, extension, extensionRegistry, extensions); - return true; - } else { -+ - return unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader); - } - } else { -diff --git a/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java -index c4ec645bf7fc8..a43bc2a9472d3 100644 ---- a/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java -+++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java -@@ -78,7 +78,6 @@ abstract class UnknownFieldSchema { - /** Marks unknown fields as immutable. */ - abstract void makeImmutable(Object message); - -- /** Merges one field into the unknown fields. */ - final boolean mergeOneFieldFrom(B unknownFields, Reader reader) throws IOException { - int tag = reader.getTag(); - int fieldNumber = WireFormat.getTagFieldNumber(tag); -@@ -111,7 +110,7 @@ final boolean mergeOneFieldFrom(B unknownFields, Reader reader) throws IOExcepti - } - } - -- final void mergeFrom(B unknownFields, Reader reader) throws IOException { -+ private final void mergeFrom(B unknownFields, Reader reader) throws IOException { - while (true) { - if (reader.getFieldNumber() == Reader.READ_DONE - || !mergeOneFieldFrom(unknownFields, reader)) { -diff --git a/java/lite/src/test/java/com/google/protobuf/LiteTest.java b/java/lite/src/test/java/com/google/protobuf/LiteTest.java -index 0c7b8b535b18d..411bc63f087fd 100644 ---- a/java/lite/src/test/java/com/google/protobuf/LiteTest.java -+++ b/java/lite/src/test/java/com/google/protobuf/LiteTest.java -@@ -33,12 +33,14 @@ - import static com.google.common.truth.Truth.assertThat; - import static com.google.common.truth.Truth.assertWithMessage; - import static java.util.Collections.singletonList; -+import static org.junit.Assert.assertThrows; - - import com.google.protobuf.FieldPresenceTestProto.TestAllTypes; - import com.google.protobuf.UnittestImportLite.ImportEnumLite; - import com.google.protobuf.UnittestImportPublicLite.PublicImportMessageLite; - import com.google.protobuf.UnittestLite.ForeignEnumLite; - import com.google.protobuf.UnittestLite.ForeignMessageLite; -+import com.google.protobuf.UnittestLite.RecursiveGroup; - import com.google.protobuf.UnittestLite.RecursiveMessage; - import com.google.protobuf.UnittestLite.TestAllExtensionsLite; - import com.google.protobuf.UnittestLite.TestAllTypesLite; -@@ -72,6 +74,7 @@ - import java.util.Arrays; - import java.util.Iterator; - import java.util.List; -+import java.util.concurrent.atomic.AtomicBoolean; - import org.junit.Before; - import org.junit.Test; - import org.junit.runner.RunWith; -diff --git a/src/google/protobuf/unittest_lite.proto b/src/google/protobuf/unittest_lite.proto -index b3fcfa431c00a..4bc78c4de6a80 100644 ---- a/src/google/protobuf/unittest_lite.proto -+++ b/src/google/protobuf/unittest_lite.proto -@@ -518,3 +518,7 @@ message RecursiveMessage { - optional RecursiveMessage recurse = 1; - optional bytes payload = 2; - } -+ -+message RecursiveGroup { -+ optional RecursiveGroup recurse = 1; -+} diff -Nru protobuf-3.21.12/debian/patches/CVE-2025-4565-1.patch protobuf-3.21.12/debian/patches/CVE-2025-4565-1.patch --- protobuf-3.21.12/debian/patches/CVE-2025-4565-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/CVE-2025-4565-1.patch 2026-07-02 19:07:33.000000000 +0000 @@ -0,0 +1,117 @@ +From f69ea1cc48191534c3450a1053f3741250274eca Mon Sep 17 00:00:00 2001 +From: Mike Kruskal +Date: Mon, 9 Dec 2024 20:04:43 -0800 +Subject: [PATCH] Make Pure Python reject unmatched end-group tag in unknown + fields + +This brings it into conformance with our spec and other languages. + +PiperOrigin-RevId: 704518974 + +Origin: upstream, https://github.com/protocolbuffers/protobuf/commit/f69ea1cc48191534c3450a1053f3741250274eca.patch +Bug: https://www.cve.org/CVERecord?id=CVE-2025-4565 +Bug-Debian: https://bugs.debian.org/1108057 +--- + conformance/failure_list_python.txt | 3 - + python/google/protobuf/internal/decoder.py | 14 ++++- + .../google/protobuf/internal/decoder_test.py | 56 ++++++++++++++++++- + .../google/protobuf/internal/message_test.py | 31 ++++++---- + .../protobuf/internal/python_message.py | 3 +- + python/google/protobuf/unknown_fields.py | 3 +- + 6 files changed, 90 insertions(+), 20 deletions(-) + +diff --git a/python/google/protobuf/internal/decoder.py b/python/google/protobuf/internal/decoder.py +index dcde1d942..e26770e1d 100755 +--- a/python/google/protobuf/internal/decoder.py ++++ b/python/google/protobuf/internal/decoder.py +@@ -976,14 +976,16 @@ def _DecodeUnknownFieldSet(buffer, pos, end_pos=None): + field_number, wire_type = wire_format.UnpackTag(tag) + if wire_type == wire_format.WIRETYPE_END_GROUP: + break +- (data, pos) = _DecodeUnknownField(buffer, pos, wire_type) ++ (data, pos) = _DecodeUnknownField( ++ buffer, pos, end_pos, field_number, wire_type ++ ) + # pylint: disable=protected-access + unknown_field_set._add(field_number, wire_type, data) + + return (unknown_field_set, pos) + + +-def _DecodeUnknownField(buffer, pos, wire_type): ++def _DecodeUnknownField(buffer, pos, end_pos, field_number, wire_type): + """Decode a unknown field. Returns the UnknownField and new position.""" + + if wire_type == wire_format.WIRETYPE_VARINT: +@@ -997,7 +999,13 @@ def _DecodeUnknownField(buffer, pos, wire_type): + data = buffer[pos:pos+size].tobytes() + pos += size + elif wire_type == wire_format.WIRETYPE_START_GROUP: +- (data, pos) = _DecodeUnknownFieldSet(buffer, pos) ++ end_tag_bytes = encoder.TagBytes( ++ field_number, wire_format.WIRETYPE_END_GROUP ++ ) ++ data, pos = _DecodeUnknownFieldSet(buffer, pos, end_pos) ++ # Check end tag. ++ if buffer[pos - len(end_tag_bytes) : pos] != end_tag_bytes: ++ raise _DecodeError('Missing group end tag.') + elif wire_type == wire_format.WIRETYPE_END_GROUP: + return (0, -1) + else: +diff --git a/python/google/protobuf/internal/message_test.py b/python/google/protobuf/internal/message_test.py +index f1541f14b..7212639db 100755 +--- a/python/google/protobuf/internal/message_test.py ++++ b/python/google/protobuf/internal/message_test.py +@@ -122,6 +122,8 @@ class MessageTest(unittest.TestCase): + msg = message_module.TestAllTypes() + self.assertRaises(TypeError, msg.FromString, 0) + self.assertRaises(Exception, msg.FromString, '0') ++ ++ # Unexpected end group tag. + # TODO(jieluo): Fix cpp extension to raise error instead of warning. + # b/27494216 + end_tag = encoder.TagBytes(1, 4) +@@ -133,6 +133,15 @@ class MessageTest(unittest.TestCase): + # Only pure-Python has an error message this specific. + self.assertEqual('Unexpected end-group tag.', str(context.exception)) + ++ # Unmatched start group tag. ++ start_tag = encoder.TagBytes(2, 3) ++ with self.assertRaises(message.DecodeError): ++ msg.FromString(start_tag) ++ ++ # Mismatched end group tag. ++ with self.assertRaises(message.DecodeError): ++ msg.FromString(start_tag + end_tag) ++ + # Field number 0 is illegal. + self.assertRaises(message.DecodeError, msg.FromString, b'\3\4') + +diff --git a/python/google/protobuf/internal/python_message.py b/python/google/protobuf/internal/python_message.py +index 1f9016880..22d02c4d6 100755 +--- a/python/google/protobuf/internal/python_message.py ++++ b/python/google/protobuf/internal/python_message.py +@@ -1179,7 +1179,8 @@ def _AddMergeFromStringMethod(message_de + # TODO(jieluo): remove old_pos. + old_pos = new_pos + (data, new_pos) = decoder._DecodeUnknownField( +- buffer, new_pos, wire_type) # pylint: disable=protected-access ++ buffer, new_pos, end, field_number, wire_type ++ ) # pylint: disable=protected-access + if new_pos == -1: + return pos + # pylint: disable=protected-access +diff --git a/python/google/protobuf/unknown_fields.py b/python/google/protobuf/unknown_fields.py +index 9b1e54932..1a4537e3f 100644 +--- a/python/google/protobuf/unknown_fields.py ++++ b/python/google/protobuf/unknown_fields.py +@@ -101,7 +101,8 @@ else: + if field_number == 0: + raise RuntimeError('Field number 0 is illegal.') + (data, _) = decoder._DecodeUnknownField( +- memoryview(buffer), 0, wire_type) ++ memoryview(buffer), 0, len(buffer), field_number, wire_type ++ ) + InternalAdd(field_number, wire_type, data) + + def __getitem__(self, index): diff -Nru protobuf-3.21.12/debian/patches/CVE-2025-4565-2.patch protobuf-3.21.12/debian/patches/CVE-2025-4565-2.patch --- protobuf-3.21.12/debian/patches/CVE-2025-4565-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/CVE-2025-4565-2.patch 2026-07-02 19:07:33.000000000 +0000 @@ -0,0 +1,395 @@ +From a6fa5799ff0153e4f0f6f1d8263d6b492ac129e0 Mon Sep 17 00:00:00 2001 +From: Protobuf Team Bot +Date: Tue, 4 Mar 2025 13:16:32 -0800 +Subject: [PATCH] Internal pure python fixes + +PiperOrigin-RevId: 733441339 + +Origin: upstream, https://github.com/protocolbuffers/protobuf/commit/a6fa5799ff0153e4f0f6f1d8263d6b492ac129e0.patch +Bug: https://www.cve.org/CVERecord?id=CVE-2025-4565 +Bug-Debian: https://bugs.debian.org/1108057 +--- + python/google/protobuf/internal/decoder.py | 98 ++++++++++++++----- + .../google/protobuf/internal/message_test.py | 1 + + .../protobuf/internal/python_message.py | 7 +- + .../protobuf/internal/self_recursive.proto | 9 +- + 4 files changed, 86 insertions(+), 29 deletions(-) + +diff --git a/python/google/protobuf/internal/decoder.py b/python/google/protobuf/internal/decoder.py +index 91a4c7541e3dc..e2a3de17089ab 100755 +--- a/python/google/protobuf/internal/decoder.py ++++ b/python/google/protobuf/internal/decoder.py +@@ -195,7 +195,10 @@ def SpecificDecoder(field_number, is_repeated, is_packed, key, new_default, + clear_if_default=False): + if is_packed: + local_DecodeVarint = _DecodeVarint +- def DecodePackedField(buffer, pos, end, message, field_dict): ++ def DecodePackedField( ++ buffer, pos, end, message, field_dict, current_depth=0 ++ ): ++ del current_depth # unused + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) +@@ -210,11 +213,15 @@ def DecodePackedField(buffer, pos, end, message, field_dict): + del value[-1] # Discard corrupt value. + raise _DecodeError('Packed element was truncated.') + return pos ++ + return DecodePackedField + elif is_repeated: + tag_bytes = encoder.TagBytes(field_number, wire_type) + tag_len = len(tag_bytes) +- def DecodeRepeatedField(buffer, pos, end, message, field_dict): ++ def DecodeRepeatedField( ++ buffer, pos, end, message, field_dict, current_depth=0 ++ ): ++ del current_depth # unused + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) +@@ -229,9 +236,12 @@ def DecodeRepeatedField(buffer, pos, end, message, field_dict): + if new_pos > end: + raise _DecodeError('Truncated message.') + return new_pos ++ + return DecodeRepeatedField + else: +- def DecodeField(buffer, pos, end, message, field_dict): ++ ++ def DecodeField(buffer, pos, end, message, field_dict, current_depth=0): ++ del current_depth # unused + (new_value, pos) = decode_value(buffer, pos) + if pos > end: + raise _DecodeError('Truncated message.') +@@ -240,6 +250,7 @@ def DecodeField(buffer, pos, end, message, field_dict): + else: + field_dict[key] = new_value + return pos ++ + return DecodeField + + return SpecificDecoder +@@ -375,7 +386,9 @@ def EnumDecoder(field_number, is_repeated, is_packed, key, new_default, + enum_type = key.enum_type + if is_packed: + local_DecodeVarint = _DecodeVarint +- def DecodePackedField(buffer, pos, end, message, field_dict): ++ def DecodePackedField( ++ buffer, pos, end, message, field_dict, current_depth=0 ++ ): + """Decode serialized packed enum to its value and a new position. + + Args: +@@ -388,6 +401,7 @@ def DecodePackedField(buffer, pos, end, message, field_dict): + Returns: + int, new position in serialized data. + """ ++ del current_depth # unused + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) +@@ -424,11 +438,14 @@ def DecodePackedField(buffer, pos, end, message, field_dict): + # pylint: enable=protected-access + raise _DecodeError('Packed element was truncated.') + return pos ++ + return DecodePackedField + elif is_repeated: + tag_bytes = encoder.TagBytes(field_number, wire_format.WIRETYPE_VARINT) + tag_len = len(tag_bytes) +- def DecodeRepeatedField(buffer, pos, end, message, field_dict): ++ def DecodeRepeatedField( ++ buffer, pos, end, message, field_dict, current_depth=0 ++ ): + """Decode serialized repeated enum to its value and a new position. + + Args: +@@ -441,6 +458,7 @@ def DecodeRepeatedField(buffer, pos, end, message, field_dict): + Returns: + int, new position in serialized data. + """ ++ del current_depth # unused + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) +@@ -467,9 +485,11 @@ def DecodeRepeatedField(buffer, pos, end, message, field_dict): + if new_pos > end: + raise _DecodeError('Truncated message.') + return new_pos ++ + return DecodeRepeatedField + else: +- def DecodeField(buffer, pos, end, message, field_dict): ++ ++ def DecodeField(buffer, pos, end, message, field_dict, current_depth=0): + """Decode serialized repeated enum to its value and a new position. + + Args: +@@ -482,6 +502,7 @@ def DecodeField(buffer, pos, end, message, field_dict): + Returns: + int, new position in serialized data. + """ ++ del current_depth # unused + value_start_pos = pos + (enum_value, pos) = _DecodeSignedVarint32(buffer, pos) + if pos > end: +@@ -505,6 +505,7 @@ def EnumDecoder(field_number, is_repeate + field_number, wire_format.WIRETYPE_VARINT, enum_value) + # pylint: enable=protected-access + return pos ++ + return DecodeField + + +@@ -563,7 +585,10 @@ def _ConvertToUnicode(memview): + tag_bytes = encoder.TagBytes(field_number, + wire_format.WIRETYPE_LENGTH_DELIMITED) + tag_len = len(tag_bytes) +- def DecodeRepeatedField(buffer, pos, end, message, field_dict): ++ def DecodeRepeatedField( ++ buffer, pos, end, message, field_dict, current_depth=0 ++ ): ++ del current_depth # unused + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) +@@ -578,9 +603,12 @@ def DecodeRepeatedField(buffer, pos, end, message, field_dict): + if buffer[new_pos:pos] != tag_bytes or new_pos == end: + # Prediction failed. Return. + return new_pos ++ + return DecodeRepeatedField + else: +- def DecodeField(buffer, pos, end, message, field_dict): ++ ++ def DecodeField(buffer, pos, end, message, field_dict, current_depth=0): ++ del current_depth # unused + (size, pos) = local_DecodeVarint(buffer, pos) + new_pos = pos + size + if new_pos > end: +@@ -590,6 +618,7 @@ def DecodeField(buffer, pos, end, message, field_dict): + else: + field_dict[key] = _ConvertToUnicode(buffer[pos:new_pos]) + return new_pos ++ + return DecodeField + + +@@ -604,7 +633,10 @@ def BytesDecoder(field_number, is_repeated, is_packed, key, new_default, + tag_bytes = encoder.TagBytes(field_number, + wire_format.WIRETYPE_LENGTH_DELIMITED) + tag_len = len(tag_bytes) +- def DecodeRepeatedField(buffer, pos, end, message, field_dict): ++ def DecodeRepeatedField( ++ buffer, pos, end, message, field_dict, current_depth=0 ++ ): ++ del current_depth # unused + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) +@@ -619,9 +651,12 @@ def DecodeRepeatedField(buffer, pos, end, message, field_dict): + if buffer[new_pos:pos] != tag_bytes or new_pos == end: + # Prediction failed. Return. + return new_pos ++ + return DecodeRepeatedField + else: +- def DecodeField(buffer, pos, end, message, field_dict): ++ ++ def DecodeField(buffer, pos, end, message, field_dict, current_depth=0): ++ del current_depth # unused + (size, pos) = local_DecodeVarint(buffer, pos) + new_pos = pos + size + if new_pos > end: +@@ -631,6 +666,7 @@ def DecodeField(buffer, pos, end, message, field_dict): + else: + field_dict[key] = buffer[pos:new_pos].tobytes() + return new_pos ++ + return DecodeField + + +@@ -646,7 +682,9 @@ def GroupDecoder(field_number, is_repeated, is_packed, key, new_default): + tag_bytes = encoder.TagBytes(field_number, + wire_format.WIRETYPE_START_GROUP) + tag_len = len(tag_bytes) +- def DecodeRepeatedField(buffer, pos, end, message, field_dict): ++ def DecodeRepeatedField( ++ buffer, pos, end, message, field_dict, current_depth=0 ++ ): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) +@@ -655,7 +693,7 @@ def DecodeRepeatedField(buffer, pos, end, message, field_dict): + if value is None: + value = field_dict.setdefault(key, new_default(message)) + # Read sub-message. +- pos = value.add()._InternalParse(buffer, pos, end) ++ pos = value.add()._InternalParse(buffer, pos, end, current_depth) + # Read end tag. + new_pos = pos+end_tag_len + if buffer[pos:new_pos] != end_tag_bytes or new_pos > end: +@@ -665,19 +703,22 @@ def DecodeRepeatedField(buffer, pos, end, message, field_dict): + if buffer[new_pos:pos] != tag_bytes or new_pos == end: + # Prediction failed. Return. + return new_pos ++ + return DecodeRepeatedField + else: +- def DecodeField(buffer, pos, end, message, field_dict): ++ ++ def DecodeField(buffer, pos, end, message, field_dict, current_depth=0): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + # Read sub-message. +- pos = value._InternalParse(buffer, pos, end) ++ pos = value._InternalParse(buffer, pos, end, current_depth) + # Read end tag. + new_pos = pos+end_tag_len + if buffer[pos:new_pos] != end_tag_bytes or new_pos > end: + raise _DecodeError('Missing group end tag.') + return new_pos ++ + return DecodeField + + +@@ -691,7 +732,9 @@ def MessageDecoder(field_number, is_repeated, is_packed, key, new_default): + tag_bytes = encoder.TagBytes(field_number, + wire_format.WIRETYPE_LENGTH_DELIMITED) + tag_len = len(tag_bytes) +- def DecodeRepeatedField(buffer, pos, end, message, field_dict): ++ def DecodeRepeatedField( ++ buffer, pos, end, message, field_dict, current_depth=0 ++ ): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) +@@ -702,7 +745,10 @@ def DecodeRepeatedField(buffer, pos, end, message, field_dict): + if new_pos > end: + raise _DecodeError('Truncated message.') + # Read sub-message. +- if value.add()._InternalParse(buffer, pos, new_pos) != new_pos: ++ if ( ++ value.add()._InternalParse(buffer, pos, new_pos, current_depth) ++ != new_pos ++ ): + # The only reason _InternalParse would return early is if it + # encountered an end-group tag. + raise _DecodeError('Unexpected end-group tag.') +@@ -711,9 +757,11 @@ def DecodeRepeatedField(buffer, pos, end, message, field_dict): + if buffer[new_pos:pos] != tag_bytes or new_pos == end: + # Prediction failed. Return. + return new_pos ++ + return DecodeRepeatedField + else: +- def DecodeField(buffer, pos, end, message, field_dict): ++ ++ def DecodeField(buffer, pos, end, message, field_dict, current_depth=0): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) +@@ -723,11 +771,12 @@ def DecodeField(buffer, pos, end, message, field_dict): + if new_pos > end: + raise _DecodeError('Truncated message.') + # Read sub-message. +- if value._InternalParse(buffer, pos, new_pos) != new_pos: ++ if value._InternalParse(buffer, pos, new_pos, current_depth) != new_pos: + # The only reason _InternalParse would return early is if it encountered + # an end-group tag. + raise _DecodeError('Unexpected end-group tag.') + return new_pos ++ + return DecodeField + + +@@ -883,7 +932,8 @@ def MapDecoder(field_descriptor, new_default, is_message_map): + # Can't read _concrete_class yet; might not be initialized. + message_type = field_descriptor.message_type + +- def DecodeMap(buffer, pos, end, message, field_dict): ++ def DecodeMap(buffer, pos, end, message, field_dict, current_depth=0): ++ del current_depth # Unused. + submsg = message_type._concrete_class() + value = field_dict.get(key) + if value is None: +@@ -966,7 +966,7 @@ def _SkipGroup(buffer, pos, end): + pos = new_pos + + +-def _DecodeUnknownFieldSet(buffer, pos, end_pos=None): ++def _DecodeUnknownFieldSet(buffer, pos, end_pos=None, current_depth=0): + """Decode UnknownFieldSet. Returns the UnknownFieldSet and new position.""" + + unknown_field_set = containers.UnknownFieldSet() +@@ -977,7 +1027,7 @@ def _DecodeUnknownFieldSet(buffer, pos, end_pos=None): + if wire_type == wire_format.WIRETYPE_END_GROUP: + break + (data, pos) = _DecodeUnknownField( +- buffer, pos, end_pos, field_number, wire_type ++ buffer, pos, end_pos, field_number, wire_type, current_depth + ) + # pylint: disable=protected-access + unknown_field_set._add(field_number, wire_type, data) +@@ -985,7 +1035,9 @@ def _DecodeUnknownFieldSet(buffer, pos, end_pos=None): + return (unknown_field_set, pos) + + +-def _DecodeUnknownField(buffer, pos, end_pos, field_number, wire_type): ++def _DecodeUnknownField( ++ buffer, pos, end_pos, field_number, wire_type, current_depth=0 ++): + """Decode a unknown field. Returns the UnknownField and new position.""" + + if wire_type == wire_format.WIRETYPE_VARINT: +@@ -1002,7 +1054,7 @@ def _DecodeUnknownField(buffer, pos, end_pos, field_number, wire_type): + end_tag_bytes = encoder.TagBytes( + field_number, wire_format.WIRETYPE_END_GROUP + ) +- data, pos = _DecodeUnknownFieldSet(buffer, pos, end_pos) ++ data, pos = _DecodeUnknownFieldSet(buffer, pos, end_pos, current_depth) + # Check end tag. + if buffer[pos - len(end_tag_bytes) : pos] != end_tag_bytes: + raise _DecodeError('Missing group end tag.') +diff --git a/python/google/protobuf/internal/message_test.py b/python/google/protobuf/internal/message_test.py +index f8382085b9a00..d9fdf874a7052 100755 +--- a/python/google/protobuf/internal/message_test.py ++++ b/python/google/protobuf/internal/message_test.py +@@ -60,6 +60,7 @@ from google.protobuf import unittest_pb2 + from google.protobuf import unittest_proto3_arena_pb2 + from google.protobuf import descriptor + from google.protobuf.internal import api_implementation ++from google.protobuf.internal import decoder + from google.protobuf.internal import encoder + from google.protobuf.internal import more_extensions_pb2 + from google.protobuf.internal import packed_field_test_pb2 +diff --git a/python/google/protobuf/internal/python_message.py b/python/google/protobuf/internal/python_message.py +index 23ac28e36d2e0..8cc517c07878d 100755 +--- a/python/google/protobuf/internal/python_message.py ++++ b/python/google/protobuf/internal/python_message.py +@@ -1141,7 +1141,7 @@ def _AddMergeFromStringMethod(message_de + local_SkipField = decoder.SkipField + decoders_by_tag = cls._decoders_by_tag + +- def InternalParse(self, buffer, pos, end): ++ def InternalParse(self, buffer, pos, end, current_depth=0): + """Create a message from serialized bytes. + + Args: +@@ -1193,10 +1193,13 @@ def _AddMergeFromStringMethod(message_de + (tag_bytes, buffer[old_pos:new_pos].tobytes())) + pos = new_pos + else: +- pos = field_decoder(buffer, new_pos, end, self, field_dict) ++ pos = field_decoder( ++ buffer, new_pos, end, self, field_dict, current_depth ++ ) + if field_desc: + self._UpdateOneofState(field_desc) + return pos ++ + cls._InternalParse = InternalParse + + diff -Nru protobuf-3.21.12/debian/patches/CVE-2025-4565-3.patch protobuf-3.21.12/debian/patches/CVE-2025-4565-3.patch --- protobuf-3.21.12/debian/patches/CVE-2025-4565-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ protobuf-3.21.12/debian/patches/CVE-2025-4565-3.patch 2026-07-02 19:07:33.000000000 +0000 @@ -0,0 +1,207 @@ +From 17838beda2943d08b8a9d4df5b68f5f04f26d901 Mon Sep 17 00:00:00 2001 +From: Protobuf Team Bot +Date: Tue, 13 May 2025 14:42:18 -0700 +Subject: [PATCH] Add recursion depth limits to pure python + +PiperOrigin-RevId: 758382549 + +Origin: upstream, https://github.com/protocolbuffers/protobuf/commit/17838beda2943d08b8a9d4df5b68f5f04f26d901.patch +Bug: https://www.cve.org/CVERecord?id=CVE-2025-4565 +Bug-Debian: https://bugs.debian.org/1108057 +--- + python/google/protobuf/internal/decoder.py | 31 ++++++++ + .../google/protobuf/internal/message_test.py | 76 ++++++++++++++----- + .../protobuf/internal/self_recursive.proto | 1 + + 4 files changed, 102 insertions(+), 19 deletions(-) + +diff --git a/python/google/protobuf/internal/decoder.py b/python/google/protobuf/internal/decoder.py +index e2a3de17089ab..dbf848bf31083 100755 +--- a/python/google/protobuf/internal/decoder.py ++++ b/python/google/protobuf/internal/decoder.py +@@ -693,7 +693,13 @@ def DecodeRepeatedField( + if value is None: + value = field_dict.setdefault(key, new_default(message)) + # Read sub-message. ++ current_depth += 1 ++ if current_depth > _recursion_limit: ++ raise _DecodeError( ++ 'Error parsing message: too many levels of nesting.' ++ ) + pos = value.add()._InternalParse(buffer, pos, end, current_depth) ++ current_depth -= 1 + # Read end tag. + new_pos = pos+end_tag_len + if buffer[pos:new_pos] != end_tag_bytes or new_pos > end: +@@ -712,7 +718,11 @@ def DecodeField(buffer, pos, end, message, field_dict, current_depth=0): + if value is None: + value = field_dict.setdefault(key, new_default(message)) + # Read sub-message. ++ current_depth += 1 ++ if current_depth > _recursion_limit: ++ raise _DecodeError('Error parsing message: too many levels of nesting.') + pos = value._InternalParse(buffer, pos, end, current_depth) ++ current_depth -= 1 + # Read end tag. + new_pos = pos+end_tag_len + if buffer[pos:new_pos] != end_tag_bytes or new_pos > end: +@@ -745,6 +755,11 @@ def DecodeRepeatedField( + if new_pos > end: + raise _DecodeError('Truncated message.') + # Read sub-message. ++ current_depth += 1 ++ if current_depth > _recursion_limit: ++ raise _DecodeError( ++ 'Error parsing message: too many levels of nesting.' ++ ) + if ( + value.add()._InternalParse(buffer, pos, new_pos, current_depth) + != new_pos +@@ -752,6 +767,7 @@ def DecodeRepeatedField( + # The only reason _InternalParse would return early is if it + # encountered an end-group tag. + raise _DecodeError('Unexpected end-group tag.') ++ current_depth -= 1 + # Predict that the next tag is another copy of the same repeated field. + pos = new_pos + tag_len + if buffer[new_pos:pos] != tag_bytes or new_pos == end: +@@ -771,10 +787,14 @@ def DecodeField(buffer, pos, end, message, field_dict, current_depth=0): + if new_pos > end: + raise _DecodeError('Truncated message.') + # Read sub-message. ++ current_depth += 1 ++ if current_depth > _recursion_limit: ++ raise _DecodeError('Error parsing message: too many levels of nesting.') + if value._InternalParse(buffer, pos, new_pos, current_depth) != new_pos: + # The only reason _InternalParse would return early is if it encountered + # an end-group tag. + raise _DecodeError('Unexpected end-group tag.') ++ current_depth -= 1 + return new_pos + + return DecodeField +@@ -1054,7 +1054,11 @@ def _DecodeUnknownField( + end_tag_bytes = encoder.TagBytes( + field_number, wire_format.WIRETYPE_END_GROUP + ) ++ current_depth += 1 ++ if current_depth >= _recursion_limit: ++ raise _DecodeError('Error parsing message: too many levels of nesting.') + data, pos = _DecodeUnknownFieldSet(buffer, pos, end_pos, current_depth) ++ current_depth -= 1 + # Check end tag. + if buffer[pos - len(end_tag_bytes) : pos] != end_tag_bytes: + raise _DecodeError('Missing group end tag.') +@@ -1086,6 +1086,13 @@ def _DecodeFixed32(buffer, pos): + + new_pos = pos + 4 + return (struct.unpack('