Control: tags 1123888 + patch
Control: tags 1123888 + pending

Dear maintainer,

I've prepared an NMU for python-marshmallow (versioned as 3.26.2-0.1)
and uploaded it to DELAYED/2. Please feel free to tell me if I should
cancel it.

cu
Adrian
diffstat for python-marshmallow-3.26.1 python-marshmallow-3.26.2

 CHANGELOG.rst                  |    8 ++++++++
 debian/changelog               |    8 ++++++++
 pyproject.toml                 |    4 ++--
 src/marshmallow/error_store.py |   35 +++++++++++++++++++++++------------
 src/marshmallow/schema.py      |    4 ++--
 tests/test_error_store.py      |   18 +++++++++++++++++-
 6 files changed, 60 insertions(+), 17 deletions(-)

diff -Nru python-marshmallow-3.26.1/CHANGELOG.rst python-marshmallow-3.26.2/CHANGELOG.rst
--- python-marshmallow-3.26.1/CHANGELOG.rst	2025-02-03 17:31:14.000000000 +0200
+++ python-marshmallow-3.26.2/CHANGELOG.rst	2025-12-22 08:51:52.000000000 +0200
@@ -1,6 +1,14 @@
 Changelog
 ---------
 
+3.26.2 (2025-12-19)
+++++++++++++++++++
+
+Bug fixes:
+
+- :cve:`CVE-2025-68480`: Merge error store messages without rebuilding collections.
+  Thanks 카푸치노 for reporting and :user:`deckar01` for the fix.
+
 3.26.1 (2025-02-03)
 *******************
 
diff -Nru python-marshmallow-3.26.1/debian/changelog python-marshmallow-3.26.2/debian/changelog
--- python-marshmallow-3.26.1/debian/changelog	2026-01-13 00:09:38.000000000 +0200
+++ python-marshmallow-3.26.2/debian/changelog	2026-06-23 23:27:50.000000000 +0300
@@ -1,3 +1,11 @@
+python-marshmallow (3.26.2-0.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * New upstream release.
+    - CVE-2025-68480: DoS with Schema.load(many)  (Closes: #1123888)
+
+ -- Adrian Bunk <[email protected]>  Tue, 23 Jun 2026 23:27:50 +0300
+
 python-marshmallow (3.26.1-0.4) unstable; urgency=medium
 
   * Non-maintainer upload.
diff -Nru python-marshmallow-3.26.1/pyproject.toml python-marshmallow-3.26.2/pyproject.toml
--- python-marshmallow-3.26.1/pyproject.toml	2025-02-03 17:31:14.000000000 +0200
+++ python-marshmallow-3.26.2/pyproject.toml	2025-12-22 08:51:52.000000000 +0200
@@ -1,6 +1,6 @@
 [project]
 name = "marshmallow"
-version = "3.26.1"
+version = "3.26.2"
 description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
 readme = "README.rst"
 license = { file = "LICENSE" }
@@ -121,7 +121,7 @@
 [tool.mypy]
 files = ["src", "tests", "examples"]
 ignore_missing_imports = true
-warn_unreachable = true
+warn_unreachable = false
 warn_unused_ignores = true
 warn_redundant_casts = true
 no_implicit_optional = true
diff -Nru python-marshmallow-3.26.1/src/marshmallow/error_store.py python-marshmallow-3.26.2/src/marshmallow/error_store.py
--- python-marshmallow-3.26.1/src/marshmallow/error_store.py	2025-02-03 17:31:14.000000000 +0200
+++ python-marshmallow-3.26.2/src/marshmallow/error_store.py	2025-12-22 08:51:52.000000000 +0200
@@ -18,6 +18,7 @@
         # field error  -> store/merge error messages under field name key
         # schema error -> if string or list, store/merge under _schema key
         #              -> if dict, store/merge with other top-level keys
+        messages = copy_containers(messages)
         if field_name != SCHEMA or not isinstance(messages, dict):
             messages = {field_name: messages}
         if index is not None:
@@ -25,6 +26,14 @@
         self.errors = merge_errors(self.errors, messages)
 
 
+def copy_containers(errors):
+    if isinstance(errors, list):
+        return [copy_containers(val) for val in errors]
+    if isinstance(errors, dict):
+        return {key: copy_containers(val) for key, val in errors.items()}
+    return errors
+
+
 def merge_errors(errors1, errors2):  # noqa: PLR0911
     """Deeply merge two error messages.
 
@@ -37,24 +46,26 @@
         return errors1
     if isinstance(errors1, list):
         if isinstance(errors2, list):
-            return errors1 + errors2
+            errors1.extend(errors2)
+            return errors1
         if isinstance(errors2, dict):
-            return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
-        return [*errors1, errors2]
+            errors2[SCHEMA] = merge_errors(errors1, errors2.get(SCHEMA))
+            return errors2
+        errors1.append(errors2)
+        return errors1
     if isinstance(errors1, dict):
-        if isinstance(errors2, list):
-            return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
         if isinstance(errors2, dict):
-            errors = dict(errors1)
             for key, val in errors2.items():
-                if key in errors:
-                    errors[key] = merge_errors(errors[key], val)
+                if key in errors1:
+                    errors1[key] = merge_errors(errors1[key], val)
                 else:
-                    errors[key] = val
-            return errors
-        return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
+                    errors1[key] = val
+            return errors1
+        errors1[SCHEMA] = merge_errors(errors1.get(SCHEMA), errors2)
+        return errors1
     if isinstance(errors2, list):
         return [errors1, *errors2]
     if isinstance(errors2, dict):
-        return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
+        errors2[SCHEMA] = merge_errors(errors1, errors2.get(SCHEMA))
+        return errors2
     return [errors1, errors2]
diff -Nru python-marshmallow-3.26.1/src/marshmallow/schema.py python-marshmallow-3.26.2/src/marshmallow/schema.py
--- python-marshmallow-3.26.1/src/marshmallow/schema.py	2025-02-03 17:31:14.000000000 +0200
+++ python-marshmallow-3.26.2/src/marshmallow/schema.py	2025-12-22 08:51:52.000000000 +0200
@@ -374,7 +374,7 @@
 
                 class MySchema2(Schema):
                     # Type checkers will check attributes
-                    class Meta(Schema.Opts):
+                    class Meta(Schema.Meta):
                         additional = True  # Incompatible types in assignment
 
         .. versionremoved:: 3.0.0b7 Remove ``strict``.
@@ -1139,7 +1139,7 @@
                 msg = (
                     f'Field for "{field_name}" must be declared as a '
                     "Field instance, not a class. "
-                    f'Did you mean "fields.{field_obj.__name__}()"?'  # type: ignore[attr-defined]
+                    f'Did you mean "fields.{field_obj.__name__}()"?'
                 )
                 raise TypeError(msg) from error
             raise
diff -Nru python-marshmallow-3.26.1/tests/test_error_store.py python-marshmallow-3.26.2/tests/test_error_store.py
--- python-marshmallow-3.26.1/tests/test_error_store.py	2025-02-03 17:31:14.000000000 +0200
+++ python-marshmallow-3.26.2/tests/test_error_store.py	2025-12-22 08:51:52.000000000 +0200
@@ -1,7 +1,7 @@
 from typing import NamedTuple
 
 from marshmallow import missing
-from marshmallow.error_store import merge_errors
+from marshmallow.error_store import ErrorStore, merge_errors
 
 
 def test_missing_is_falsy():
@@ -149,3 +149,19 @@
         assert merge_errors(
             {"field1": {"field2": "error1"}}, {"field1": {"field2": "error2"}}
         ) == {"field1": {"field2": ["error1", "error2"]}}
+
+    def test_list_not_changed(self):
+        store = ErrorStore()
+        message = ["foo"]
+        store.store_error(message)
+        store.store_error(message)
+        assert message == ["foo"]
+        assert store.errors == {"_schema": ["foo", "foo"]}
+
+    def test_dict_not_changed(self):
+        store = ErrorStore()
+        message = {"foo": ["bar"]}
+        store.store_error(message)
+        store.store_error(message)
+        assert message == {"foo": ["bar"]}
+        assert store.errors == {"foo": ["bar", "bar"]}

Reply via email to