diff mbox series

[meta-oe,kirkstone,1/1] python3-django: fix CVE-2024-27351

Message ID 20251114120343.4109634-1-saravanan.kadambathursubramaniyam@windriver.com
State New
Headers show
Series [meta-oe,kirkstone,1/1] python3-django: fix CVE-2024-27351 | expand

Commit Message

Saravanan Nov. 14, 2025, 12:03 p.m. UTC
Reference:
https://nvd.nist.gov/vuln/detail/CVE-2024-27351

Upstream-patch:
https://github.com/django/django/commit/072963e4c4d0b3a7a8c5412bc0c7d27d1a9c3521

Signed-off-by: Saravanan <saravanan.kadambathursubramaniyam@windriver.com>
---
 .../CVE-2024-27351.patch                      | 149 ++++++++++++++++++
 .../CVE-2024-27351.patch                      | 145 +++++++++++++++++
 .../python/python3-django_2.2.28.bb           |   1 +
 .../python/python3-django_3.2.23.bb           |   1 +
 4 files changed, 296 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-django-2.2.28/CVE-2024-27351.patch
 create mode 100644 meta-python/recipes-devtools/python/python3-django-3.2.23/CVE-2024-27351.patch
diff mbox series

Patch

diff --git a/meta-python/recipes-devtools/python/python3-django-2.2.28/CVE-2024-27351.patch b/meta-python/recipes-devtools/python/python3-django-2.2.28/CVE-2024-27351.patch
new file mode 100644
index 0000000000..f240b0852e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-django-2.2.28/CVE-2024-27351.patch
@@ -0,0 +1,149 @@ 
+From 072963e4c4d0b3a7a8c5412bc0c7d27d1a9c3521 Mon Sep 17 00:00:00 2001
+From: Shai Berger <shai@platonix.com>
+Date: Mon, 19 Feb 2024 13:56:37 +0100
+Subject: [PATCH] Fixed CVE-2024-27351 -- Prevented potential ReDoS in
+ Truncator.words().
+
+Thanks Seokchan Yoon for the report.
+
+CVE: CVE-2024-27351
+
+Upstream-Status: Backport
+https://github.com/django/django/commit/072963e4c4d0b3a7a8c5412bc0c7d27d1a9c3521
+
+Signed-off-by: Shai Berger <shai@platonix.com>
+Co-Authored-By: Mariusz Felisiak <felisiak.mariusz@gmail.com>
+Signed-off-by: Saravanan <saravanan.kadambathursubramaniyam@windriver.com>
+---
+ django/utils/text.py           | 57 ++++++++++++++++++++++++++++++++--
+ docs/releases/2.2.28.txt       |  8 +++++
+ tests/utils_tests/test_text.py | 26 ++++++++++++++++
+ 3 files changed, 89 insertions(+), 2 deletions(-)
+
+diff --git a/django/utils/text.py b/django/utils/text.py
+index 06a377b..2c4040e 100644
+--- a/django/utils/text.py
++++ b/django/utils/text.py
+@@ -15,8 +15,61 @@ def capfirst(x):
+     return x and str(x)[0].upper() + str(x)[1:]
+
+
+-# Set up regular expressions
+-re_words = re.compile(r'<[^>]+?>|([^<>\s]+)', re.S)
++# ----- Begin security-related performance workaround -----
++
++# We used to have, below
++#
++# re_words = _lazy_re_compile(r"<[^>]+?>|([^<>\s]+)", re.S)
++#
++# But it was shown that this regex, in the way we use it here, has some
++# catastrophic edge-case performance features. Namely, when it is applied to
++# text with only open brackets "<<<...". The class below provides the services
++# and correct answers for the use cases, but in these edge cases does it much
++# faster.
++re_notag = _lazy_re_compile(r"([^<>\s]+)", re.S)
++re_prt = _lazy_re_compile(r"<|([^<>\s]+)", re.S)
++
++
++class WordsRegex:
++    @staticmethod
++    def search(text, pos):
++        # Look for "<" or a non-tag word.
++        partial = re_prt.search(text, pos)
++        if partial is None or partial[1] is not None:
++            return partial
++
++        # "<" was found, look for a closing ">".
++        end = text.find(">", partial.end(0))
++        if end < 0:
++            # ">" cannot be found, look for a word.
++            return re_notag.search(text, pos + 1)
++        else:
++            # "<" followed by a ">" was found -- fake a match.
++            end += 1
++            return FakeMatch(text[partial.start(0): end], end)
++
++
++class FakeMatch:
++    __slots__ = ["_text", "_end"]
++
++    def end(self, group=0):
++        assert group == 0, "This specific object takes only group=0"
++        return self._end
++
++    def __getitem__(self, group):
++        if group == 1:
++            return None
++        assert group == 0, "This specific object takes only group in {0,1}"
++        return self._text
++
++    def __init__(self, text, end):
++        self._text, self._end = text, end
++
++
++# ----- End security-related performance workaround -----
++
++# Set up regular expressions.
++re_words = WordsRegex
+ re_chars = re.compile(r'<[^>]+?>|(.)', re.S)
+ re_tag = re.compile(r'<(/)?(\S+?)(?:(\s*/)|\s.*?)?>', re.S)
+ re_newlines = re.compile(r'\r\n|\r')  # Used in normalize_newlines
+diff --git a/docs/releases/2.2.28.txt b/docs/releases/2.2.28.txt
+index c653cb6..8f79fd0 100644
+--- a/docs/releases/2.2.28.txt
++++ b/docs/releases/2.2.28.txt
+@@ -6,6 +6,14 @@ Django 2.2.28 release notes
+
+ Django 2.2.28 fixes two security issues with severity "high" in 2.2.27.
+
++CVE-2024-27351: Potential regular expression denial-of-service in ``django.utils.text.Truncator.words()``
++=========================================================================================================
++
++``django.utils.text.Truncator.words()`` method (with ``html=True``) and
++:tfilter:`truncatewords_html` template filter were subject to a potential
++regular expression denial-of-service attack using a suitably crafted string
++(follow up to :cve:`2019-14232` and :cve:`2023-43665`).
++
+ CVE-2022-28346: Potential SQL injection in ``QuerySet.annotate()``, ``aggregate()``, and ``extra()``
+ ====================================================================================================
+ 
+diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
+index cb3063d..7e9f2b3 100644
+--- a/tests/utils_tests/test_text.py
++++ b/tests/utils_tests/test_text.py
+@@ -156,6 +156,32 @@ class TestUtilsText(SimpleTestCase):
+         truncator = text.Truncator('<p>I &lt;3 python, what about you?</p>')
+         self.assertEqual('<p>I &lt;3 python,…</p>', truncator.words(3, html=True))
+ 
++        # Only open brackets.
++        test = "<" * 60_000
++        truncator = text.Truncator(test)
++        self.assertEqual(truncator.words(1, html=True), test)
++
++        # Tags with special chars in attrs.
++        truncator = text.Truncator(
++            """<i style="margin: 5%; font: *;">Hello, my dear lady!</i>"""
++        )
++        self.assertEqual(
++            """<i style="margin: 5%; font: *;">Hello, my dear…</i>""",
++            truncator.words(3, html=True),
++        )
++
++        # Tags with special non-latin chars in attrs.
++        truncator = text.Truncator("""<p data-x="א">Hello, my dear lady!</p>""")
++        self.assertEqual(
++            """<p data-x="א">Hello, my dear…</p>""",
++            truncator.words(3, html=True),
++        )
++
++        # Misplaced brackets.
++        truncator = text.Truncator("hello >< world")
++        self.assertEqual(truncator.words(1, html=True), "hello…")
++        self.assertEqual(truncator.words(2, html=True), "hello >< world")
++
+     @patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
+     def test_truncate_words_html_size_limit(self):
+         max_len = text.Truncator.MAX_LENGTH_HTML
+-- 
+2.35.5
+
diff --git a/meta-python/recipes-devtools/python/python3-django-3.2.23/CVE-2024-27351.patch b/meta-python/recipes-devtools/python/python3-django-3.2.23/CVE-2024-27351.patch
new file mode 100644
index 0000000000..1251a41bb5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-django-3.2.23/CVE-2024-27351.patch
@@ -0,0 +1,145 @@ 
+From 072963e4c4d0b3a7a8c5412bc0c7d27d1a9c3521 Mon Sep 17 00:00:00 2001
+From: Shai Berger <shai@platonix.com>
+Date: Mon, 19 Feb 2024 13:56:37 +0100
+Subject: [PATCH] Fixed CVE-2024-27351 -- Prevented potential ReDoS in
+ Truncator.words().
+
+Thanks Seokchan Yoon for the report.
+
+CVE: CVE-2024-27351
+
+Upstream-Status: Backport
+https://github.com/django/django/commit/072963e4c4d0b3a7a8c5412bc0c7d27d1a9c3521
+
+Signed-off-by: Shai Berger <shai@platonix.com>
+Co-Authored-By: Mariusz Felisiak <felisiak.mariusz@gmail.com>
+Signed-off-by: Saravanan <saravanan.kadambathursubramaniyam@windriver.com>
+---
+ django/utils/text.py           | 57 ++++++++++++++++++++++++++++++++--
+ docs/releases/3.2.23.txt       |  8 +++++
+ tests/utils_tests/test_text.py | 26 ++++++++++++++++
+ 3 files changed, 89 insertions(+), 2 deletions(-)
+
+diff --git a/django/utils/text.py b/django/utils/text.py
+index 83e258f..88da9a2 100644
+--- a/django/utils/text.py
++++ b/django/utils/text.py
+@@ -18,8 +18,61 @@ def capfirst(x):
+     return x and str(x)[0].upper() + str(x)[1:]
+
+
+-# Set up regular expressions
+-re_words = _lazy_re_compile(r'<[^>]+?>|([^<>\s]+)', re.S)
++# ----- Begin security-related performance workaround -----
++
++# We used to have, below
++#
++# re_words = _lazy_re_compile(r"<[^>]+?>|([^<>\s]+)", re.S)
++#
++# But it was shown that this regex, in the way we use it here, has some
++# catastrophic edge-case performance features. Namely, when it is applied to
++# text with only open brackets "<<<...". The class below provides the services
++# and correct answers for the use cases, but in these edge cases does it much
++# faster.
++re_notag = _lazy_re_compile(r"([^<>\s]+)", re.S)
++re_prt = _lazy_re_compile(r"<|([^<>\s]+)", re.S)
++
++
++class WordsRegex:
++    @staticmethod
++    def search(text, pos):
++        # Look for "<" or a non-tag word.
++        partial = re_prt.search(text, pos)
++        if partial is None or partial[1] is not None:
++            return partial
++
++        # "<" was found, look for a closing ">".
++        end = text.find(">", partial.end(0))
++        if end < 0:
++            # ">" cannot be found, look for a word.
++            return re_notag.search(text, pos + 1)
++        else:
++            # "<" followed by a ">" was found -- fake a match.
++            end += 1
++            return FakeMatch(text[partial.start(0): end], end)
++
++
++class FakeMatch:
++    __slots__ = ["_text", "_end"]
++
++    def end(self, group=0):
++        assert group == 0, "This specific object takes only group=0"
++        return self._end
++
++    def __getitem__(self, group):
++        if group == 1:
++            return None
++        assert group == 0, "This specific object takes only group in {0,1}"
++        return self._text
++
++    def __init__(self, text, end):
++        self._text, self._end = text, end
++
++
++# ----- End security-related performance workaround -----
++
++# Set up regular expressions.
++re_words = WordsRegex
+ re_chars = _lazy_re_compile(r'<[^>]+?>|(.)', re.S)
+ re_tag = _lazy_re_compile(r'<(/)?(\S+?)(?:(\s*/)|\s.*?)?>', re.S)
+ re_newlines = _lazy_re_compile(r'\r\n|\r')  # Used in normalize_newlines
+diff --git a/docs/releases/3.2.23.txt b/docs/releases/3.2.23.txt
+index ba23d11..dd9d68a 100644
+--- a/docs/releases/3.2.23.txt
++++ b/docs/releases/3.2.23.txt
+@@ -17,3 +17,11 @@ large number of Unicode characters.
+ In order to avoid the vulnerability, invalid values longer than
+ ``UsernameField.max_length`` are no longer normalized, since they cannot pass
+ validation anyway.
++
++CVE-2024-27351: Potential regular expression denial-of-service in ``django.utils.text.Truncator.words()``
++=========================================================================================================
++
++``django.utils.text.Truncator.words()`` method (with ``html=True``) and
++:tfilter:`truncatewords_html` template filter were subject to a potential
++regular expression denial-of-service attack using a suitably crafted string
++(follow up to :cve:`2019-14232` and :cve:`2023-43665`).
+diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
+index 0a6f0bc..758919c 100644
+--- a/tests/utils_tests/test_text.py
++++ b/tests/utils_tests/test_text.py
+@@ -159,6 +159,32 @@ class TestUtilsText(SimpleTestCase):
+         truncator = text.Truncator('<p>I &lt;3 python, what about you?</p>')
+         self.assertEqual('<p>I &lt;3 python,…</p>', truncator.words(3, html=True))
+
++        # Only open brackets.
++        test = "<" * 60_000
++        truncator = text.Truncator(test)
++        self.assertEqual(truncator.words(1, html=True), test)
++
++        # Tags with special chars in attrs.
++        truncator = text.Truncator(
++            """<i style="margin: 5%; font: *;">Hello, my dear lady!</i>"""
++        )
++        self.assertEqual(
++            """<i style="margin: 5%; font: *;">Hello, my dear…</i>""",
++            truncator.words(3, html=True),
++        )
++
++        # Tags with special non-latin chars in attrs.
++        truncator = text.Truncator("""<p data-x="א">Hello, my dear lady!</p>""")
++        self.assertEqual(
++            """<p data-x="א">Hello, my dear…</p>""",
++            truncator.words(3, html=True),
++        )
++
++        # Misplaced brackets.
++        truncator = text.Truncator("hello >< world")
++        self.assertEqual(truncator.words(1, html=True), "hello…")
++        self.assertEqual(truncator.words(2, html=True), "hello >< world")
++
+     @patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
+     def test_truncate_words_html_size_limit(self):
+         max_len = text.Truncator.MAX_LENGTH_HTML
+--
+2.35.5
diff --git a/meta-python/recipes-devtools/python/python3-django_2.2.28.bb b/meta-python/recipes-devtools/python/python3-django_2.2.28.bb
index 0478fd3883..f394397453 100644
--- a/meta-python/recipes-devtools/python/python3-django_2.2.28.bb
+++ b/meta-python/recipes-devtools/python/python3-django_2.2.28.bb
@@ -24,6 +24,7 @@  SRC_URI += "file://CVE-2023-31047.patch \
             file://CVE-2024-45230.patch \
             file://CVE-2024-45231.patch \
             file://CVE-2024-53907.patch \
+            file://CVE-2024-27351.patch \
            "
 
 SRC_URI[sha256sum] = "0200b657afbf1bc08003845ddda053c7641b9b24951e52acd51f6abda33a7413"
diff --git a/meta-python/recipes-devtools/python/python3-django_3.2.23.bb b/meta-python/recipes-devtools/python/python3-django_3.2.23.bb
index beecaa607c..e049b6552c 100644
--- a/meta-python/recipes-devtools/python/python3-django_3.2.23.bb
+++ b/meta-python/recipes-devtools/python/python3-django_3.2.23.bb
@@ -6,6 +6,7 @@  SRC_URI[sha256sum] = "82968f3640e29ef4a773af2c28448f5f7a08d001c6ac05b32d02aeee65
 RDEPENDS:${PN} += "\
     ${PYTHON_PN}-sqlparse \
 "
+SRC_URI += "file://CVE-2024-27351.patch"
 
 # Set DEFAULT_PREFERENCE so that the LTS version of django is built by
 # default. To build the 3.x branch,