Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions SPECS/pytorch/CVE-2026-0994.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
From 55985cbd6a38c7266bb557926e545034362964e2 Mon Sep 17 00:00:00 2001
From: AllSpark <allspark@microsoft.com>
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 <azurelinux-security@microsoft.com>
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

6 changes: 5 additions & 1 deletion SPECS/pytorch/pytorch.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -96,6 +97,9 @@ cp -arf docs %{buildroot}/%{_pkgdocdir}
%{_docdir}/*

%changelog
* Fri Feb 13 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 2.0.0-14
- Patch for CVE-2026-0994

* Thu Jan 29 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 2.0.0-13
- Patch for CVE-2026-24747

Expand Down
Loading