diff --git a/SPECS/pytorch/CVE-2026-0994.patch b/SPECS/pytorch/CVE-2026-0994.patch new file mode 100644 index 00000000000..d8c87e117d5 --- /dev/null +++ b/SPECS/pytorch/CVE-2026-0994.patch @@ -0,0 +1,230 @@ +From 55985cbd6a38c7266bb557926e545034362964e2 Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Fri, 13 Feb 2026 14:01:33 +0000 +Subject: [PATCH] Fix Any recursion depth bypass in json_format.ParseDict: + enforce max_recursion_depth across nested Any and well-known types; add tests + for depth enforcement and boundary; route WKT via ConvertMessage + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/protocolbuffers/protobuf/commit/c4eda3e58680528147a4cc7e2b3c9044f795c9c9.patch +--- + .../protobuf/internal/json_format_test.py | 102 ++++++++++++++++++ + .../python/google/protobuf/json_format.py | 47 +++++--- + 2 files changed, 134 insertions(+), 15 deletions(-) + +diff --git a/third_party/protobuf/python/google/protobuf/internal/json_format_test.py b/third_party/protobuf/python/google/protobuf/internal/json_format_test.py +index 68aa21c4..2d0f5cd5 100755 +--- a/third_party/protobuf/python/google/protobuf/internal/json_format_test.py ++++ b/third_party/protobuf/python/google/protobuf/internal/json_format_test.py +@@ -774,6 +774,108 @@ class JsonFormatTest(JsonFormatBase): + '}\n')) + self.CheckParseBack(message, parsed_message) + ++ 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.assertRaisesRegexp( ++ 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.assertRaisesRegexp( ++ json_format.ParseError, ++ 'Message too deep. Max recursion depth is 5', ++ json_format.ParseDict, ++ depth_5_any, ++ message, ++ max_recursion_depth=5, ++ ) ++ ++ + def testParseNull(self): + message = json_format_proto3_pb2.TestMessage() + parsed_message = json_format_proto3_pb2.TestMessage() +diff --git a/third_party/protobuf/python/google/protobuf/json_format.py b/third_party/protobuf/python/google/protobuf/json_format.py +index 4d76d021..4457d6c3 100644 +--- a/third_party/protobuf/python/google/protobuf/json_format.py ++++ b/third_party/protobuf/python/google/protobuf/json_format.py +@@ -408,7 +408,7 @@ def _CreateMessageFromTypeUrl(type_url, descriptor_pool): + 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: +@@ -429,13 +429,14 @@ def Parse(text, message, ignore_unknown_fields=False, descriptor_pool=None): + 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=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: +@@ -448,7 +449,7 @@ def ParseDict(js_dict, + Returns: + The same message passed as argument. + """ +- parser = _Parser(ignore_unknown_fields, descriptor_pool) ++ parser = _Parser(ignore_unknown_fields, descriptor_pool, max_recursion_depth=max_recursion_depth) + parser.ConvertMessage(js_dict, message) + return message + +@@ -459,9 +460,12 @@ _INT_OR_FLOAT = six.integer_types + (float,) + 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=100): + self.ignore_unknown_fields = ignore_unknown_fields + self.descriptor_pool = descriptor_pool ++ # Track recursion depth across nested message parsing ++ self.recursion_depth = 0 ++ self.max_recursion_depth = max_recursion_depth + + def ConvertMessage(self, value, message): + """Convert a JSON object into a message. +@@ -473,14 +477,26 @@ class _Parser(object): + 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) ++ # 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 %d' % 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: ++ # Decrement recursion depth when exiting this message context ++ self.recursion_depth -= 1 + + def _ConvertFieldValuePair(self, js, message): + """Convert field value pairs into regular message. +@@ -612,8 +628,9 @@ class _Parser(object): + 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) ++ # For well-known types (including nested Any), use ConvertMessage ++ # to ensure recursion depth is properly tracked ++ self.ConvertMessage(value['value'], sub_message) + else: + del value['@type'] + self._ConvertFieldValuePair(value, sub_message) +-- +2.45.4 + diff --git a/SPECS/pytorch/pytorch.spec b/SPECS/pytorch/pytorch.spec index 4c7a0c597c5..f499469a333 100644 --- a/SPECS/pytorch/pytorch.spec +++ b/SPECS/pytorch/pytorch.spec @@ -2,7 +2,7 @@ Summary: Tensors and Dynamic neural networks in Python with strong GPU acceleration. Name: pytorch Version: 2.0.0 -Release: 13%{?dist} +Release: 14%{?dist} License: BSD-3-Clause Vendor: Microsoft Corporation Distribution: Mariner @@ -24,6 +24,7 @@ Patch9: CVE-2025-55552.patch Patch10: CVE-2025-55560.patch Patch11: CVE-2025-3001.patch Patch12: CVE-2026-24747.patch +Patch13: CVE-2026-0994.patch BuildRequires: cmake BuildRequires: gcc @@ -96,6 +97,9 @@ cp -arf docs %{buildroot}/%{_pkgdocdir} %{_docdir}/* %changelog +* Fri Feb 13 2026 Azure Linux Security Servicing Account - 2.0.0-14 +- Patch for CVE-2026-0994 + * Thu Jan 29 2026 Azure Linux Security Servicing Account - 2.0.0-13 - Patch for CVE-2026-24747