diff --git a/protobuf-3.14-CVE-2026-0994-nested-any-recursion.patch b/protobuf-3.14-CVE-2026-0994-nested-any-recursion.patch new file mode 100644 index 0000000..d459cb3 --- /dev/null +++ b/protobuf-3.14-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 17:47:01.782591911 +0000 ++++ b/python/google/protobuf/json_format.py 2026-01-26 17:58:45.049000000 +0000 +@@ -410,7 +410,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: +@@ -419,6 +423,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. +@@ -431,13 +438,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: +@@ -446,11 +455,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 + +@@ -461,9 +473,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. +@@ -475,14 +489,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. +@@ -617,8 +638,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.14-CVE-2026-0994-test.patch b/protobuf-3.14-CVE-2026-0994-test.patch new file mode 100644 index 0000000..f6efce5 --- /dev/null +++ b/protobuf-3.14-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 18:04:19.788000000 +0000 ++++ b/python/google/protobuf/internal/json_format_test.py 2026-01-26 18:04:40.403000000 +0000 +@@ -1258,6 +1258,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 e3400e4..f9ece5a 100644 --- a/protobuf.spec +++ b/protobuf.spec @@ -12,7 +12,7 @@ Summary: Protocol Buffers - Google's data interchange format Name: protobuf Version: 3.14.0 -Release: 16%{?dist} +Release: 17%{?dist} License: BSD URL: https://github.com/protocolbuffers/protobuf Source: https://github.com/protocolbuffers/protobuf/archive/v%{version}%{?rcver}/%{name}-%{version}%{?rcver}-all.tar.gz @@ -33,6 +33,11 @@ Patch2: CVE-2021-22570.patch # https://issues.redhat.com/browse/RHEL-40872 # Based on https://github.com/protocolbuffers/protobuf/pull/10542.patch Patch3: CVE-2022-1941.patch +# Fix for CVE-2026-0994: nested Any messages bypassing recursion depth limits +# https://github.com/protocolbuffers/protobuf/pull/25239 +Patch4: protobuf-3.14-CVE-2026-0994-nested-any-recursion.patch +# Test for CVE-2026-0994 fix +Patch5: protobuf-3.14-CVE-2026-0994-test.patch BuildRequires: make BuildRequires: autoconf @@ -223,6 +228,8 @@ descriptions in the Emacs editor. %patch -P 1 -p1 %patch -P 2 -p1 %patch -P 3 -p1 +%patch -P 4 -p1 -b .CVE-2026-0994 +%patch -P 5 -p1 -b .CVE-2026-0994-test mv googletest-5ec7f0c4a113e2f18ac2c6cc7df51ad6afc24081/* third_party/googletest/ find -name \*.cc -o -name \*.h | xargs chmod -x chmod 644 examples/* @@ -408,6 +415,9 @@ install -p -m 0644 %{SOURCE2} %{buildroot}%{_emacs_sitestartdir} %changelog +* Mon Jan 26 2026 Adrian Reber - 3.14.0-17 +- Fix CVE-2026-0994: nested Any messages bypassing recursion depth limits + * Tue Oct 22 2024 Adrian Reber - 3.14.0-16 - Rebuild