diff --git a/protobuf-3.19-CVE-2026-0994-nested-any-recursion.patch b/protobuf-3.19-CVE-2026-0994-nested-any-recursion.patch new file mode 100644 index 0000000..64208f8 --- /dev/null +++ b/protobuf-3.19-CVE-2026-0994-nested-any-recursion.patch @@ -0,0 +1,125 @@ +From: Fedora Package Maintainer +Subject: Fix nested Any messages bypassing recursion depth limits + +Backport fix for CVE-2026-0994: nested google.protobuf.Any messages could +bypass recursion depth limits, potentially causing denial-of-service attacks. + +This patch adds recursion depth tracking to the JSON parser and fixes the +_ConvertAnyMessage function to use ConvertMessage directly to ensure +recursion depth is properly tracked for all message types. + +Upstream PR: https://github.com/protocolbuffers/protobuf/pull/25239 + +--- a/python/google/protobuf/json_format.py 2026-01-26 16:35:51.402115353 +0000 ++++ b/python/google/protobuf/json_format.py 2026-01-26 16:41:58.361000000 +0000 +@@ -400,7 +400,11 @@ + return message_class() + + +-def Parse(text, message, ignore_unknown_fields=False, descriptor_pool=None): ++def Parse(text, ++ message, ++ ignore_unknown_fields=False, ++ descriptor_pool=None, ++ max_recursion_depth=100): + """Parses a JSON representation of a protocol message into a message. + + Args: +@@ -409,6 +413,9 @@ + ignore_unknown_fields: If True, do not raise errors for unknown fields. + descriptor_pool: A Descriptor Pool for resolving types. If None use the + default. ++ max_recursion_depth: max recursion depth of JSON message to be ++ deserialized. JSON messages over this depth will fail to be ++ deserialized. Default value is 100. + + Returns: + The same message passed as argument. +@@ -422,13 +429,15 @@ + js = json.loads(text, object_pairs_hook=_DuplicateChecker) + except ValueError as e: + raise ParseError('Failed to load JSON: {0}.'.format(str(e))) +- return ParseDict(js, message, ignore_unknown_fields, descriptor_pool) ++ return ParseDict(js, message, ignore_unknown_fields, descriptor_pool, ++ max_recursion_depth) + + + def ParseDict(js_dict, + message, + ignore_unknown_fields=False, +- descriptor_pool=None): ++ descriptor_pool=None, ++ max_recursion_depth=100): + """Parses a JSON dictionary representation into a message. + + Args: +@@ -437,11 +446,14 @@ + ignore_unknown_fields: If True, do not raise errors for unknown fields. + descriptor_pool: A Descriptor Pool for resolving types. If None use the + default. ++ max_recursion_depth: max recursion depth of JSON message to be ++ deserialized. JSON messages over this depth will fail to be ++ deserialized. Default value is 100. + + Returns: + The same message passed as argument. + """ +- parser = _Parser(ignore_unknown_fields, descriptor_pool) ++ parser = _Parser(ignore_unknown_fields, descriptor_pool, max_recursion_depth) + parser.ConvertMessage(js_dict, message) + return message + +@@ -452,9 +464,11 @@ + class _Parser(object): + """JSON format parser for protocol message.""" + +- def __init__(self, ignore_unknown_fields, descriptor_pool): ++ def __init__(self, ignore_unknown_fields, descriptor_pool, max_recursion_depth): + self.ignore_unknown_fields = ignore_unknown_fields + self.descriptor_pool = descriptor_pool ++ self.max_recursion_depth = max_recursion_depth ++ self.recursion_depth = 0 + + def ConvertMessage(self, value, message): + """Convert a JSON object into a message. +@@ -466,14 +480,21 @@ + Raises: + ParseError: In case of convert problems. + """ +- message_descriptor = message.DESCRIPTOR +- full_name = message_descriptor.full_name +- if _IsWrapperMessage(message_descriptor): +- self._ConvertWrapperMessage(value, message) +- elif full_name in _WKTJSONMETHODS: +- methodcaller(_WKTJSONMETHODS[full_name][1], value, message)(self) +- else: +- self._ConvertFieldValuePair(value, message) ++ self.recursion_depth += 1 ++ if self.recursion_depth > self.max_recursion_depth: ++ raise ParseError('Message too deep. Max recursion depth is {0}'.format( ++ self.max_recursion_depth)) ++ try: ++ message_descriptor = message.DESCRIPTOR ++ full_name = message_descriptor.full_name ++ if _IsWrapperMessage(message_descriptor): ++ self._ConvertWrapperMessage(value, message) ++ elif full_name in _WKTJSONMETHODS: ++ methodcaller(_WKTJSONMETHODS[full_name][1], value, message)(self) ++ else: ++ self._ConvertFieldValuePair(value, message) ++ finally: ++ self.recursion_depth -= 1 + + def _ConvertFieldValuePair(self, js, message): + """Convert field value pairs into regular message. +@@ -608,8 +629,8 @@ + if _IsWrapperMessage(message_descriptor): + self._ConvertWrapperMessage(value['value'], sub_message) + elif full_name in _WKTJSONMETHODS: +- methodcaller( +- _WKTJSONMETHODS[full_name][1], value['value'], sub_message)(self) ++ # Use ConvertMessage to ensure recursion depth is properly tracked ++ self.ConvertMessage(value['value'], sub_message) + else: + del value['@type'] + self._ConvertFieldValuePair(value, sub_message) diff --git a/protobuf-3.19-CVE-2026-0994-test.patch b/protobuf-3.19-CVE-2026-0994-test.patch new file mode 100644 index 0000000..1f8d75e --- /dev/null +++ b/protobuf-3.19-CVE-2026-0994-test.patch @@ -0,0 +1,32 @@ +From: Fedora Package Maintainer +Subject: Add test for CVE-2026-0994 recursion depth limits + +Add test for the max_recursion_depth parameter to verify that deeply +nested messages properly trigger ParseError when exceeding the limit. + +Upstream PR: https://github.com/protocolbuffers/protobuf/pull/25239 + +--- a/python/google/protobuf/internal/json_format_test.py 2026-01-26 16:51:53.848408566 +0000 ++++ b/python/google/protobuf/internal/json_format_test.py 2026-01-26 16:52:13.501000000 +0000 +@@ -1271,6 +1271,21 @@ + 'uint32Value': 4, 'stringValue': 'bla'}, + indent=2, sort_keys=True)) + ++ def testNestedRecursiveLimit(self): ++ message = unittest_pb2.NestedTestAllTypes() ++ self.assertRaisesRegex( ++ json_format.ParseError, ++ 'Message too deep. Max recursion depth is 3', ++ json_format.Parse, ++ '{"child": {"child": {"child" : {}}}}', ++ message, ++ max_recursion_depth=3, ++ ) ++ # The following one can pass ++ json_format.Parse( ++ '{"payload": {}, "child": {"child":{}}}', message, max_recursion_depth=3 ++ ) ++ + + if __name__ == '__main__': + unittest.main() diff --git a/protobuf.spec b/protobuf.spec index 68ea151..bc4272b 100644 --- a/protobuf.spec +++ b/protobuf.spec @@ -21,7 +21,7 @@ Name: protobuf # “patch” updates of protobuf. Version: 3.19.6 %global so_version 30 -Release: 14%{?dist} +Release: 15%{?dist} # The entire source is BSD-3-Clause, except the following files, which belong # to the build system; are unpackaged maintainer utility scripts; or are used @@ -85,6 +85,11 @@ Patch3: protobuf-3.19.4-jre17-add-opens.patch # # The PyFrameObject structure members have been removed from the public C API. Patch4: protobuf-3.19.4-python3.11.patch +# https://github.com/protocolbuffers/protobuf/pull/25239 +# Fix nested Any messages bypassing recursion depth limits (CVE-2026-0994) +Patch5: protobuf-3.19-CVE-2026-0994-nested-any-recursion.patch +# Test for CVE-2026-0994 fix +Patch6: protobuf-3.19-CVE-2026-0994-test.patch # A bundled copy of jsoncpp is included in the conformance tests, but the # result is not packaged, so we do not treat it as a formal bundled @@ -290,6 +295,8 @@ descriptions in the Emacs editor. %patch 2 -p0 %patch 3 -p1 -b .jre17 %patch 4 -p1 -b .python311 +%patch 5 -p1 -b .CVE-2026-0994 +%patch 6 -p1 -b .CVE-2026-0994-test # Copy in the needed gtest/gmock implementations. %setup -q -T -D -b 3 -n protobuf-%{version}%{?rcver} @@ -481,6 +488,9 @@ install -p -m 0644 %{SOURCE2} %{buildroot}%{_emacs_sitestartdir} %changelog +* Mon Jan 26 2026 Adrian Reber - 3.19.6-15 +- Fix CVE-2026-0994: nested Any messages bypassing recursion depth limits + * Wed Nov 12 2025 Adrian Reber - 3.19.6-14 - Disable tests during build that are flaky