From daf0f5cf405aba6d38025347e161d6ec457fe49d Mon Sep 17 00:00:00 2001 From: zhongtao Date: Fri, 27 Jun 2025 10:45:41 +1400 Subject: [PATCH] fix CVE-2025-4565 Signed-off-by: zhongtao --- 0008-fix-CVE-2025-4565.patch | 437 +++++++++++++++++++++++++++++++++++ protobuf.spec | 15 +- 2 files changed, 448 insertions(+), 4 deletions(-) create mode 100644 0008-fix-CVE-2025-4565.patch diff --git a/0008-fix-CVE-2025-4565.patch b/0008-fix-CVE-2025-4565.patch new file mode 100644 index 0000000..7581cb3 --- /dev/null +++ b/0008-fix-CVE-2025-4565.patch @@ -0,0 +1,437 @@ +From 5a467df16ffac5a427acd3e8508804c902c0bd14 Mon Sep 17 00:00:00 2001 +From: zhongtao +Date: Fri, 27 Jun 2025 10:40:48 +1400 +Subject: [PATCH] Internal pure python fixes PiperOrigin-RevId: 733441339 + +Add recursion depth limits to pure python +PiperOrigin-RevId: 758382549 +--- + python/google/protobuf/internal/decoder.py | 125 ++++++++++++++---- + .../google/protobuf/internal/decoder_test.py | 41 ++++++ + .../protobuf/internal/python_message.py | 7 +- + 3 files changed, 147 insertions(+), 26 deletions(-) + 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 6804986..4917a5c 100644 +--- a/python/google/protobuf/internal/decoder.py ++++ b/python/google/protobuf/internal/decoder.py +@@ -213,7 +213,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)) +@@ -228,11 +231,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)) +@@ -249,7 +256,8 @@ def _SimpleDecoder(wire_type, decode_value): + 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.') +@@ -393,7 +401,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: +@@ -406,6 +416,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)) +@@ -442,11 +453,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: +@@ -459,6 +473,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)) +@@ -485,9 +500,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: +@@ -500,6 +517,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: +@@ -523,6 +541,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 + + +@@ -591,7 +610,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)) +@@ -608,7 +630,8 @@ def StringDecoder(field_number, is_repeated, is_packed, key, new_default, + 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: +@@ -632,7 +655,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)) +@@ -649,7 +675,8 @@ def BytesDecoder(field_number, is_repeated, is_packed, key, new_default, + 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: +@@ -674,7 +701,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)) +@@ -683,7 +712,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. +- pos = value.add()._InternalParse(buffer, pos, end) ++ 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: +@@ -693,19 +728,26 @@ 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) ++ 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: + raise _DecodeError('Missing group end tag.') + return new_pos ++ + return DecodeField + + +@@ -719,7 +761,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)) +@@ -730,18 +774,29 @@ 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: ++ 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 ++ ): + # 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: + # 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)) +@@ -751,11 +806,16 @@ 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: ++ 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 + + +@@ -785,7 +845,8 @@ def MessageSetItemDecoder(descriptor): + local_DecodeVarint = _DecodeVarint + local_SkipField = SkipField + +- def DecodeItem(buffer, pos, end, message, field_dict): ++ def DecodeItem(buffer, pos, end, message, field_dict, current_depth=0): ++ del current_depth # Unused. + """Decode serialized message set to its value and new position. + + Args: +@@ -872,7 +933,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: +@@ -955,7 +1017,16 @@ def _SkipGroup(buffer, pos, end): + pos = new_pos + + +-def _DecodeUnknownFieldSet(buffer, pos, end_pos=None): ++DEFAULT_RECURSION_LIMIT = 100 ++_recursion_limit = DEFAULT_RECURSION_LIMIT ++ ++ ++def SetRecursionLimit(new_limit): ++ global _recursion_limit ++ _recursion_limit = new_limit ++ ++ ++def _DecodeUnknownFieldSet(buffer, pos, end_pos=None, current_depth=0): + """Decode UnknownFieldSet. Returns the UnknownFieldSet and new position.""" + + unknown_field_set = containers.UnknownFieldSet() +@@ -965,14 +1036,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: +@@ -986,7 +1059,11 @@ 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) ++ 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: +diff --git a/python/google/protobuf/internal/decoder_test.py b/python/google/protobuf/internal/decoder_test.py +new file mode 100644 +index 0000000..b3756f1 +--- /dev/null ++++ b/python/google/protobuf/internal/decoder_test.py +@@ -0,0 +1,41 @@ ++# -*- coding: utf-8 -*- ++# Protocol Buffers - Google's data interchange format ++# Copyright 2008 Google Inc. All rights reserved. ++# ++# Use of this source code is governed by a BSD-style ++# license that can be found in the LICENSE file or at ++# https://developers.google.com/open-source/licenses/bsd ++ ++"""Test decoder.""" ++ ++import io ++import unittest ++ ++from google.protobuf import message ++from google.protobuf.internal import decoder ++from google.protobuf.internal import testing_refleaks ++from google.protobuf.internal import wire_format ++ ++ ++_INPUT_BYTES = b'\x84r\x12' ++_EXPECTED = (14596, 18) ++ ++ ++@testing_refleaks.TestCase ++class DecoderTest(unittest.TestCase): ++ ++ def test_decode_unknown_group_field_too_many_levels(self): ++ data = memoryview(b'\023' * 5000000) ++ self.assertRaisesRegex( ++ message.DecodeError, ++ 'Error parsing message', ++ decoder._DecodeUnknownField, ++ data, ++ 1, ++ wire_format.WIRETYPE_START_GROUP, ++ 1 ++ ) ++ ++ ++if __name__ == '__main__': ++ unittest.main() +diff --git a/python/google/protobuf/internal/python_message.py b/python/google/protobuf/internal/python_message.py +index d1f4dcd..bd4d2e6 100644 +--- a/python/google/protobuf/internal/python_message.py ++++ b/python/google/protobuf/internal/python_message.py +@@ -1158,7 +1158,7 @@ def _AddMergeFromStringMethod(message_descriptor, cls): + 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: +@@ -1209,10 +1209,13 @@ def _AddMergeFromStringMethod(message_descriptor, cls): + (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 + + +-- +2.26.3 + diff --git a/protobuf.spec b/protobuf.spec index c6d4336..500ec5d 100644 --- a/protobuf.spec +++ b/protobuf.spec @@ -8,7 +8,7 @@ Summary: Protocol Buffers - Google's data interchange format Name: protobuf Version: 3.14.0 -Release: 8 +Release: 9 License: BSD URL: https://github.com/protocolbuffers/protobuf Source: https://github.com/protocolbuffers/protobuf/releases/download/v%{version}%{?rcver}/%{name}-all-%{version}%{?rcver}.tar.gz @@ -18,9 +18,10 @@ Patch9000: 0001-add-secure-compile-option-in-Makefile.patch Patch9001: 0002-add-secure-compile-fs-check-in-Makefile.patch Patch9002: 0003-fix-CVE-2021-22570.patch Patch9003: 0004-Improve-performance-of-parsing-unknown-fields-in-Jav.patch -Patch9004: 0005-fix-CVE-2022-1941.patch -Patch9005: 0006-fix-CVE-2022-3171.patch -Patch9006: 0007-add-coverage-compile-option.patch +Patch9004: 0005-fix-CVE-2022-1941.patch +Patch9005: 0006-fix-CVE-2022-3171.patch +Patch9006: 0007-add-coverage-compile-option.patch +Patch9007: 0008-fix-CVE-2025-4565.patch BuildRequires: make autoconf automake emacs gcc-c++ libtool pkgconfig zlib-devel @@ -332,6 +333,12 @@ install -p -m 0644 %{SOURCE1} %{buildroot}%{_emacs_sitestartdir} %endif %changelog +* Fri June 27 2025 zhongtao - 3.14.0-9 +- Type:bugfix +- ID:NA +- SUG:NA +- DESC: fix CVE-2025-4565 + * Wed Dec 06 2023 konglidong - 3.14.0-8 - obsolets protobuf2 for fix install conflict -- Gitee