From a46577592f90b04a8b897145eb1cc1e90c4678be Mon Sep 17 00:00:00 2001 From: jichao wu Date: Thu, 3 Jul 2025 15:50:05 +0800 Subject: [PATCH] fix CVE-2025-4565 --- ...tch => 0005-backport-CVE-2024-7254-2.patch | 0 0006-fix-CVE-2025-4565-1.patch | 391 ++++++++++++++++++ 0007-fix-CVE-2025-4565-2.patch | 153 +++++++ protobuf.spec | 12 +- 4 files changed, 554 insertions(+), 2 deletions(-) rename 0004-backport-CVE-2024-7254-2.patch => 0005-backport-CVE-2024-7254-2.patch (100%) create mode 100644 0006-fix-CVE-2025-4565-1.patch create mode 100644 0007-fix-CVE-2025-4565-2.patch diff --git a/0004-backport-CVE-2024-7254-2.patch b/0005-backport-CVE-2024-7254-2.patch similarity index 100% rename from 0004-backport-CVE-2024-7254-2.patch rename to 0005-backport-CVE-2024-7254-2.patch diff --git a/0006-fix-CVE-2025-4565-1.patch b/0006-fix-CVE-2025-4565-1.patch new file mode 100644 index 0000000..42ad6a8 --- /dev/null +++ b/0006-fix-CVE-2025-4565-1.patch @@ -0,0 +1,391 @@ +From 07c1eb7a44d229c8eb2b316bd357881c6d4b2e4f Mon Sep 17 00:00:00 2001 +From: zhongtao +Date: Wed, 25 Jun 2025 16:50:11 +1400 +Subject: [PATCH] Internal pure python fixes PiperOrigin-RevId: 733441339 + +--- + python/google/protobuf/internal/decoder.py | 98 ++++++++++++++----- + .../google/protobuf/internal/message_test.py | 1 + + .../protobuf/internal/python_message.py | 7 +- + 3 files changed, 81 insertions(+), 25 deletions(-) + +diff --git a/python/google/protobuf/internal/decoder.py b/python/google/protobuf/internal/decoder.py +index acb91aa..ceb5161 100755 +--- a/python/google/protobuf/internal/decoder.py ++++ b/python/google/protobuf/internal/decoder.py +@@ -172,7 +172,10 @@ def _SimpleDecoder(wire_type, decode_value): + 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)) +@@ -187,11 +190,15 @@ def _SimpleDecoder(wire_type, decode_value): + 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)) +@@ -206,9 +213,12 @@ def _SimpleDecoder(wire_type, decode_value): + 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.') +@@ -217,6 +227,7 @@ def _SimpleDecoder(wire_type, decode_value): + else: + field_dict[key] = new_value + return pos ++ + return DecodeField + + return SpecificDecoder +@@ -352,7 +363,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: +@@ -365,6 +378,7 @@ def EnumDecoder(field_number, is_repeated, is_packed, key, new_default, + 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)) +@@ -401,11 +415,14 @@ def EnumDecoder(field_number, is_repeated, is_packed, key, new_default, + # 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: +@@ -418,6 +435,7 @@ def EnumDecoder(field_number, is_repeated, is_packed, key, new_default, + 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)) +@@ -444,9 +462,11 @@ def EnumDecoder(field_number, is_repeated, is_packed, key, new_default, + 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: +@@ -459,6 +479,7 @@ def EnumDecoder(field_number, is_repeated, is_packed, key, new_default, + Returns: + int, new position in serialized data. + """ ++ del current_depth # unused + value_start_pos = pos + (enum_value, pos) = _DecodeSignedVarint32(buffer, pos) + if pos > end: +@@ -482,6 +503,7 @@ def EnumDecoder(field_number, is_repeated, is_packed, key, new_default, + field_number, wire_format.WIRETYPE_VARINT, enum_value) + # pylint: enable=protected-access + return pos ++ + return DecodeField + + +@@ -540,7 +562,10 @@ def StringDecoder(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)) +@@ -555,9 +580,12 @@ def StringDecoder(field_number, is_repeated, is_packed, key, new_default, + 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: +@@ -567,6 +595,7 @@ def StringDecoder(field_number, is_repeated, is_packed, key, new_default, + else: + field_dict[key] = _ConvertToUnicode(buffer[pos:new_pos]) + return new_pos ++ + return DecodeField + + +@@ -581,7 +610,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)) +@@ -596,9 +628,12 @@ def BytesDecoder(field_number, is_repeated, is_packed, key, new_default, + 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: +@@ -608,6 +643,7 @@ def BytesDecoder(field_number, is_repeated, is_packed, key, new_default, + else: + field_dict[key] = buffer[pos:new_pos].tobytes() + return new_pos ++ + return DecodeField + + +@@ -623,7 +659,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)) +@@ -632,7 +670,7 @@ def GroupDecoder(field_number, is_repeated, is_packed, key, new_default): + 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: +@@ -642,19 +680,22 @@ def GroupDecoder(field_number, is_repeated, is_packed, key, new_default): + 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 + + +@@ -668,7 +709,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)) +@@ -679,7 +722,10 @@ def MessageDecoder(field_number, is_repeated, is_packed, key, new_default): + 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.') +@@ -688,9 +734,11 @@ def MessageDecoder(field_number, is_repeated, is_packed, key, new_default): + 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)) +@@ -700,11 +748,12 @@ def MessageDecoder(field_number, is_repeated, is_packed, key, new_default): + 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 + + +@@ -859,7 +908,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: +@@ -942,7 +992,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() +@@ -952,14 +1002,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, wire_type, current_depth) + # 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, wire_type, current_depth=0 ++): + """Decode a unknown field. Returns the UnknownField and new position.""" + + if wire_type == wire_format.WIRETYPE_VARINT: +@@ -973,7 +1025,7 @@ 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) ++ (data, pos) = _DecodeUnknownFieldSet(buffer, pos, None, current_depth) + 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 b0f1ae7..fd7c1cb 100755 +--- a/python/google/protobuf/internal/message_test.py ++++ b/python/google/protobuf/internal/message_test.py +@@ -33,6 +33,7 @@ from google.protobuf.internal import encoder + from google.protobuf.internal import more_extensions_pb2 + from google.protobuf.internal import more_messages_pb2 + from google.protobuf.internal import packed_field_test_pb2 ++from google.protobuf.internal import self_recursive_pb2 + from google.protobuf.internal import test_proto3_optional_pb2 + from google.protobuf.internal import test_util + from google.protobuf.internal import testing_refleaks +diff --git a/python/google/protobuf/internal/python_message.py b/python/google/protobuf/internal/python_message.py +index 40c7764..d08c70d 100755 +--- a/python/google/protobuf/internal/python_message.py ++++ b/python/google/protobuf/internal/python_message.py +@@ -1136,7 +1136,7 @@ def _AddMergeFromStringMethod(message_descriptor, cls): + fields_by_tag = cls._fields_by_tag + message_set_decoders_by_tag = cls._message_set_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: +@@ -1195,10 +1195,13 @@ def _AddMergeFromStringMethod(message_descriptor, cls): + else: + _MaybeAddDecoder(cls, field_des) + field_decoder = field_des._decoders[is_packed] +- pos = field_decoder(buffer, new_pos, end, self, field_dict) ++ pos = field_decoder( ++ buffer, new_pos, end, self, field_dict, current_depth ++ ) + if field_des.containing_oneof: + self._UpdateOneofState(field_des) + return pos ++ + cls._InternalParse = InternalParse + + +-- +2.26.3 + diff --git a/0007-fix-CVE-2025-4565-2.patch b/0007-fix-CVE-2025-4565-2.patch new file mode 100644 index 0000000..43d0d73 --- /dev/null +++ b/0007-fix-CVE-2025-4565-2.patch @@ -0,0 +1,153 @@ +From a719eb8b8844f3c2cb05625dd2517c9c07d25c13 Mon Sep 17 00:00:00 2001 +From: zhongtao +Date: Wed, 25 Jun 2025 16:59:12 +1400 +Subject: [PATCH] Add recursion depth limits to pure python PiperOrigin-RevId: + 758382549 + +--- + python/google/protobuf/internal/decoder.py | 31 ++++++++++++++ + .../google/protobuf/internal/decoder_test.py | 41 +++++++++++++++++++ + 2 files changed, 72 insertions(+) + create mode 100644 python/google/protobuf/internal/decoder_test.py + +diff --git a/python/google/protobuf/internal/decoder.py b/python/google/protobuf/internal/decoder.py +index ceb5161..f327041 100755 +--- a/python/google/protobuf/internal/decoder.py ++++ b/python/google/protobuf/internal/decoder.py +@@ -670,7 +670,13 @@ def GroupDecoder(field_number, is_repeated, is_packed, key, new_default): + 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: +@@ -689,7 +695,11 @@ def GroupDecoder(field_number, is_repeated, is_packed, key, new_default): + 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: +@@ -722,6 +732,11 @@ def MessageDecoder(field_number, is_repeated, is_packed, key, new_default): + 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 +@@ -729,6 +744,7 @@ def MessageDecoder(field_number, is_repeated, is_packed, key, new_default): + # 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: +@@ -748,10 +764,14 @@ def MessageDecoder(field_number, is_repeated, is_packed, key, new_default): + 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 +@@ -1025,7 +1045,11 @@ def _DecodeUnknownField( + data = buffer[pos:pos+size].tobytes() + pos += size + elif wire_type == wire_format.WIRETYPE_START_GROUP: ++ current_depth += 1 ++ if current_depth >= _recursion_limit: ++ raise _DecodeError('Error parsing message: too many levels of nesting.') + (data, pos) = _DecodeUnknownFieldSet(buffer, pos, None, current_depth) ++ current_depth -= 1 + elif wire_type == wire_format.WIRETYPE_END_GROUP: + return (0, -1) + else: +@@ -1054,6 +1078,13 @@ def _DecodeFixed32(buffer, pos): + + new_pos = pos + 4 + return (struct.unpack(' - 25.1-9 +- Type:bugfix +- ID:NA +- SUG:NA +- DESC: fix CVE-2025-4565 + * Mon May 19 2025 dongyuzhen - 25.1-8 - Type:bugfix - CVE:NA -- Gitee