diff --git a/.gitignore b/.gitignore index cdf0f19389ec0b4b813ff0f8b13b6ed1eeb5e114..6f16c22f0f69c6d4e7ab5607960ee35c81d32a77 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /5.0.8.tar.gz + +django-5.2.7.tar.gz diff --git a/python-django-5.2.7-CVE-2026-1285.patch b/python-django-5.2.7-CVE-2026-1285.patch new file mode 100644 index 0000000000000000000000000000000000000000..79195fcba7eb1a1f1690e909f2ef1307e7e21d0c --- /dev/null +++ b/python-django-5.2.7-CVE-2026-1285.patch @@ -0,0 +1,70 @@ +From 9f2ada875bbee62ac46032e38ddb22755d67ae5a Mon Sep 17 00:00:00 2001 +From: Natalia <124304+nessita@users.noreply.github.com> +Date: Wed, 21 Jan 2026 09:53:10 -0300 +Subject: [PATCH] [5.2.x] Fixed CVE-2026-1285 -- Mitigated potential DoS in + django.utils.text.Truncator for HTML input. + +The `TruncateHTMLParser` used `deque.remove()` to remove tags from the +stack when processing end tags. With crafted input containing many +unmatched end tags, this caused repeated full scans of the tag stack, +leading to quadratic time complexity. + +The fix uses LIFO semantics, only removing a tag from the stack when it +matches the most recently opened tag. This avoids linear scans for +unmatched end tags and reduces complexity to linear time. + +Refs #30686 and 6ee37ada3241ed263d8d1c2901b030d964cbd161. + +Thanks Seokchan Yoon for the report, and Jake Howard and Jacob Walls for +reviews. + +Backport of a33540b3e20b5d759aa8b2e4b9ca0e8edd285344 from main. + +Adapted-by: PkgAgent (modified to adapt to opencloudos-stream) + +--- + django/utils/text.py | 9 +++++---- + tests/utils_tests/test_text.py | 10 ++++++++++ + 2 files changed, 15 insertions(+), 4 deletions(-) + +diff --git a/django/utils/text.py b/django/utils/text.py +index 26edde9..21efb00 100644 +--- a/django/utils/text.py ++++ b/django/utils/text.py +@@ -126,10 +126,11 @@ class TruncateHTMLParser(HTMLParser): + def handle_endtag(self, tag): + if tag not in self.void_elements: + self.output += f"" +- try: +- self.tags.remove(tag) +- except ValueError: +- pass ++ # Remove from the stack only if the tag matches the most recently ++ # opened tag (LIFO). This avoids O(n) linear scans for unmatched ++ # end tags if `deque.remove()` would be called. ++ if self.tags and self.tags[0] == tag: ++ self.tags.popleft() + + def handle_data(self, data): + data, output = self.process(data) +diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py +index 63c7889..11c0187 100644 +--- a/tests/utils_tests/test_text.py ++++ b/tests/utils_tests/test_text.py +@@ -202,6 +202,16 @@ class TestUtilsText(SimpleTestCase): + truncator = text.Truncator("

I <3 python, what about you?

") + self.assertEqual("

I <3 python, wh…

", truncator.chars(16, html=True)) + ++ def test_truncate_chars_html_with_misnested_tags(self): ++ # LIFO removal keeps all tags when a middle tag is closed out of order. ++ # With , the doesn't match , so all tags remain ++ # in the stack and are properly closed at truncation. ++ truncator = text.Truncator("XXXX") ++ self.assertEqual( ++ truncator.chars(2, html=True, truncate=""), ++ "XX", ++ ) ++ + def test_truncate_words(self): + truncator = text.Truncator("The quick brown fox jumped over the lazy dog.") + self.assertEqual( diff --git a/python-django-5.2.7-CVE-2026-1287.patch b/python-django-5.2.7-CVE-2026-1287.patch new file mode 100644 index 0000000000000000000000000000000000000000..8a1a329ddadb84aec5195eb188c3434a08b04479 --- /dev/null +++ b/python-django-5.2.7-CVE-2026-1287.patch @@ -0,0 +1,302 @@ +From 3e68ccdc11c127758745ddf0b4954990b14892bc Mon Sep 17 00:00:00 2001 +From: Jake Howard +Date: Wed, 21 Jan 2026 11:14:48 +0000 +Subject: [PATCH] [5.2.x] Fixed CVE-2026-1287 -- Protected against SQL + injection in column aliases via control characters. + +Control characters in FilteredRelation column aliases could be used for +SQL injection attacks. This affected QuerySet.annotate(), aggregate(), +extra(), values(), values_list(), and alias() when using dictionary +expansion with **kwargs. + +Thanks Solomon Kebede for the report, and Simon Charette, Jacob Walls, +and Natalia Bidart for reviews. + +Backport of e891a84c7ef9962bfcc3b4685690219542f86a22 from main. + +Adapted-by: PkgAgent (modified to adapt to opencloudos-stream) + +--- + django/db/models/sql/query.py | 10 ++-- + tests/aggregation/tests.py | 18 +++++-- + tests/annotations/tests.py | 74 +++++++++++++++++++---------- + tests/expressions/test_queryset_values.py | 36 +++++++++----- + tests/queries/tests.py | 18 +++++-- + 5 files changed, 102 insertions(+), 54 deletions(-) + +diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py +index 3a1cd73..baeac3a 100644 +--- a/django/db/models/sql/query.py ++++ b/django/db/models/sql/query.py +@@ -48,9 +48,11 @@ from django.utils.tree import Node + + __all__ = ["Query", "RawQuery"] + +-# Quotation marks ('"`[]), whitespace characters, semicolons, hashes, or inline +-# SQL comments are forbidden in column aliases. +-FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|#|--|/\*|\*/") ++# Quotation marks ('"`[]), whitespace characters, control characters, ++# semicolons, hashes, or inline SQL comments are forbidden in column aliases. ++FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile( ++ r"['`\"\]\[;\s\x00-\x1F\x7F-\x9F]|#|--|/\*|\*/" ++) + + # Inspired from + # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS +@@ -1209,7 +1211,7 @@ class Query(BaseExpression): + if FORBIDDEN_ALIAS_PATTERN.search(alias): + raise ValueError( + "Column aliases cannot contain whitespace characters, hashes, " +- "quotation marks, semicolons, or SQL comments." ++ "control characters, quotation marks, semicolons, or SQL comments." + ) + + def add_annotation(self, annotation, alias, select=True): +diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py +index 2e41f19..f621c53 100644 +--- a/tests/aggregation/tests.py ++++ b/tests/aggregation/tests.py +@@ -2,6 +2,7 @@ import datetime + import math + import re + from decimal import Decimal ++from itertools import chain + + from django.core.exceptions import FieldError + from django.db import connection +@@ -2134,13 +2135,18 @@ class AggregateTestCase(TestCase): + self.assertEqual(len(qs), 6) + + def test_alias_sql_injection(self): +- crafted_alias = """injected_name" from "aggregation_author"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, hashes, quotation " +- "marks, semicolons, or SQL comments." +- ) +- with self.assertRaisesMessage(ValueError, msg): +- Author.objects.aggregate(**{crafted_alias: Avg("age")}) ++ "Column aliases cannot contain whitespace characters, hashes, " ++ "control characters, quotation marks, semicolons, or SQL comments." ++ ) ++ for crafted_alias in [ ++ """injected_name" from "aggregation_author"; --""", ++ # Control characters. ++ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))), ++ ]: ++ with self.subTest(crafted_alias): ++ with self.assertRaisesMessage(ValueError, msg): ++ Author.objects.aggregate(**{crafted_alias: Avg("age")}) + + def test_exists_extra_where_with_aggregate(self): + qs = Book.objects.annotate( +diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py +index 78e5408..0430c68 100644 +--- a/tests/annotations/tests.py ++++ b/tests/annotations/tests.py +@@ -1,5 +1,6 @@ + import datetime + from decimal import Decimal ++from itertools import chain + from unittest import skipUnless + + from django.core.exceptions import FieldDoesNotExist, FieldError +@@ -1157,22 +1158,32 @@ class NonAggregateAnnotationTestCase(TestCase): + ) + + def test_alias_sql_injection(self): +- crafted_alias = """injected_name" from "annotations_book"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, hashes, quotation " +- "marks, semicolons, or SQL comments." +- ) +- with self.assertRaisesMessage(ValueError, msg): +- Book.objects.annotate(**{crafted_alias: Value(1)}) ++ "Column aliases cannot contain whitespace characters, hashes, " ++ "control characters, quotation marks, semicolons, or SQL comments." ++ ) ++ for crafted_alias in [ ++ """injected_name" from "annotations_book"; --""", ++ # Control characters. ++ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))), ++ ]: ++ with self.subTest(crafted_alias): ++ with self.assertRaisesMessage(ValueError, msg): ++ Book.objects.annotate(**{crafted_alias: Value(1)}) + + def test_alias_filtered_relation_sql_injection(self): +- crafted_alias = """injected_name" from "annotations_book"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, hashes, quotation " +- "marks, semicolons, or SQL comments." +- ) +- with self.assertRaisesMessage(ValueError, msg): +- Book.objects.annotate(**{crafted_alias: FilteredRelation("author")}) ++ "Column aliases cannot contain whitespace characters, hashes, " ++ "control characters, quotation marks, semicolons, or SQL comments." ++ ) ++ for crafted_alias in [ ++ """injected_name" from "annotations_book"; --""", ++ # Control characters. ++ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))), ++ ]: ++ with self.subTest(crafted_alias): ++ with self.assertRaisesMessage(ValueError, msg): ++ Book.objects.annotate(**{crafted_alias: FilteredRelation("author")}) + + def test_alias_forbidden_chars(self): + tests = [ +@@ -1190,10 +1201,11 @@ class NonAggregateAnnotationTestCase(TestCase): + "alias[", + "alias]", + "ali#as", ++ "ali\0as", + ] + msg = ( +- "Column aliases cannot contain whitespace characters, hashes, quotation " +- "marks, semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, " ++ "control characters, quotation marks, semicolons, or SQL comments." + ) + for crafted_alias in tests: + with self.subTest(crafted_alias): +@@ -1491,22 +1503,32 @@ class AliasTests(TestCase): + self.assertEqual(qs.get(pk=self.b1.pk), (self.b1.pk,)) + + def test_alias_sql_injection(self): +- crafted_alias = """injected_name" from "annotations_book"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, hashes, quotation " +- "marks, semicolons, or SQL comments." +- ) +- with self.assertRaisesMessage(ValueError, msg): +- Book.objects.alias(**{crafted_alias: Value(1)}) ++ "Column aliases cannot contain whitespace characters, hashes, " ++ "control characters, quotation marks, semicolons, or SQL comments." ++ ) ++ for crafted_alias in [ ++ """injected_name" from "annotations_book"; --""", ++ # Control characters. ++ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))), ++ ]: ++ with self.subTest(crafted_alias): ++ with self.assertRaisesMessage(ValueError, msg): ++ Book.objects.alias(**{crafted_alias: Value(1)}) + + def test_alias_filtered_relation_sql_injection(self): +- crafted_alias = """injected_name" from "annotations_book"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, hashes, quotation " +- "marks, semicolons, or SQL comments." +- ) +- with self.assertRaisesMessage(ValueError, msg): +- Book.objects.alias(**{crafted_alias: FilteredRelation("authors")}) ++ "Column aliases cannot contain whitespace characters, hashes, " ++ "control characters, quotation marks, semicolons, or SQL comments." ++ ) ++ for crafted_alias in [ ++ """injected_name" from "annotations_book"; --""", ++ # Control characters. ++ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))), ++ ]: ++ with self.subTest(crafted_alias): ++ with self.assertRaisesMessage(ValueError, msg): ++ Book.objects.alias(**{crafted_alias: FilteredRelation("authors")}) + + def test_alias_filtered_relation_sql_injection_dollar_sign(self): + qs = Book.objects.alias( +diff --git a/tests/expressions/test_queryset_values.py b/tests/expressions/test_queryset_values.py +index 080ee06..afd8a51 100644 +--- a/tests/expressions/test_queryset_values.py ++++ b/tests/expressions/test_queryset_values.py +@@ -1,3 +1,5 @@ ++from itertools import chain ++ + from django.db.models import F, Sum + from django.test import TestCase, skipUnlessDBFeature + +@@ -35,26 +37,36 @@ class ValuesExpressionsTests(TestCase): + ) + + def test_values_expression_alias_sql_injection(self): +- crafted_alias = """injected_name" from "expressions_company"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, hashes, quotation " +- "marks, semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, " ++ "control characters, quotation marks, semicolons, or SQL comments." + ) +- with self.assertRaisesMessage(ValueError, msg): +- Company.objects.values(**{crafted_alias: F("ceo__salary")}) ++ for crafted_alias in [ ++ """injected_name" from "expressions_company"; --""", ++ # Control characters. ++ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))), ++ ]: ++ with self.subTest(crafted_alias): ++ with self.assertRaisesMessage(ValueError, msg): ++ Company.objects.values(**{crafted_alias: F("ceo__salary")}) + + @skipUnlessDBFeature("supports_json_field") + def test_values_expression_alias_sql_injection_json_field(self): +- crafted_alias = """injected_name" from "expressions_company"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, hashes, quotation " +- "marks, semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, " ++ "control characters, quotation marks, semicolons, or SQL comments." + ) +- with self.assertRaisesMessage(ValueError, msg): +- JSONFieldModel.objects.values(f"data__{crafted_alias}") ++ for crafted_alias in [ ++ """injected_name" from "expressions_company"; --""", ++ # Control characters. ++ *(chr(c) for c in chain(range(32), range(0x7F, 0xA0))), ++ ]: ++ with self.subTest(crafted_alias): ++ with self.assertRaisesMessage(ValueError, msg): ++ JSONFieldModel.objects.values(f"data__{crafted_alias}") + +- with self.assertRaisesMessage(ValueError, msg): +- JSONFieldModel.objects.values_list(f"data__{crafted_alias}") ++ with self.assertRaisesMessage(ValueError, msg): ++ JSONFieldModel.objects.values_list(f"data__{crafted_alias}") + + def test_values_expression_group_by(self): + # values() applies annotate() first, so values selected are grouped by +diff --git a/tests/queries/tests.py b/tests/queries/tests.py +index ffaabf4..ce7fd59 100644 +--- a/tests/queries/tests.py ++++ b/tests/queries/tests.py +@@ -2,6 +2,7 @@ import datetime + import pickle + import sys + import unittest ++from itertools import chain + from operator import attrgetter + + from django.core.exceptions import EmptyResultSet, FieldError, FullResultSet +@@ -1959,13 +1960,18 @@ class Queries5Tests(TestCase): + ) + + def test_extra_select_alias_sql_injection(self): +- crafted_alias = """injected_name" from "queries_note"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, hashes, quotation " +- "marks, semicolons, or SQL comments." +- ) +- with self.assertRaisesMessage(ValueError, msg): +- Note.objects.extra(select={crafted_alias: "1"}) ++ "Column aliases cannot contain whitespace characters, hashes, " ++ "control characters, quotation marks, semicolons, or SQL comments." ++ ) ++ for crafted_alias in [ ++ """injected_name" from "queries_note"; --""", ++ # Control characters. ++ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))), ++ ]: ++ with self.subTest(crafted_alias): ++ with self.assertRaisesMessage(ValueError, msg): ++ Note.objects.extra(select={crafted_alias: "1"}) + + def test_queryset_reuse(self): + # Using querysets doesn't mutate aliases. diff --git a/python-django.spec b/python-django.spec index 143a5ac3dbb83aecc6d125d7581601ba5de20c75..dc990e71ceb67a244bf79831354c0a14742ba6b8 100644 --- a/python-django.spec +++ b/python-django.spec @@ -4,7 +4,7 @@ Summary: A high-level Python Web framework Name: python-django Version: 5.2.7 -Release: 3%{?dist} +Release: 5%{?dist} License: BSD URL: https://www.djangoproject.com/ Source0: https://github.com/django/django/archive/refs/tags/django-%{version}.tar.gz @@ -15,6 +15,8 @@ Patch001: python-django-5.2.7-CVE-2025-64458.patch Patch002: python-django-5.2.7-CVE-2025-64459.patch Patch003: python-django-5.2.7-CVE-2025-64460.patch Patch004: python-django-5.2.7-CVE-2025-13372.patch +Patch005: python-django-5.2.7-CVE-2026-1285.patch +Patch006: python-django-5.2.7-CVE-2026-1287.patch BuildArch: noarch @@ -128,6 +130,10 @@ python3 runtests.py --settings=test_sqlite --verbosity=2 --parallel 1 %changelog +* Mon Mar 02 2026 ze-you-liu - 5.2.7-5 +- [Type] security +- [DESC] Fix CVE-2026-1285, CVE-2026-1287 + * Thu Dec 04 2025 Shop You - 5.2.7-3 - [Type] security - [DESC] Fix CVE-2025-64460 and CVE-2025-13372