mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-09-25 12:00:22 +00:00
python3-django: fix CVE-2026-15337
This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. [1] https://github.com/django/django/commit/c72a5dbb64d0777f3f471f1be94e8b2ca91e0959 [2] https://nvd.nist.gov/vuln/detail/CVE-2026-15337 Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
@@ -0,0 +1,163 @@
|
|||||||
|
From 8c63ca97dd33e3cb7e4a6f78c5f616f2e45ecaf1 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Natalia <124304+nessita@users.noreply.github.com>
|
||||||
|
Date: Fri, 10 Jul 2026 18:30:21 -0300
|
||||||
|
Subject: [PATCH] [5.2.x] Fixed CVE-2026-15337 -- Mitigated potential DoS in
|
||||||
|
check_for_language().
|
||||||
|
|
||||||
|
Language codes longer than 500 characters are now rejected before the
|
||||||
|
cached lookup, so they are no longer retained as cache keys consuming
|
||||||
|
memory from each process.
|
||||||
|
|
||||||
|
Thanks Jaeyoung Jang for the report, and Sarah Boyce for reviews.
|
||||||
|
|
||||||
|
Backport of 27137e655e442e81095f1f8f77ff3870d9fdf169 from main.
|
||||||
|
|
||||||
|
CVE: CVE-2026-15337
|
||||||
|
Upstream-Status: Backport [https://github.com/django/django/commit/c72a5dbb64d0777f3f471f1be94e8b2ca91e0959]
|
||||||
|
|
||||||
|
Backport Changes:
|
||||||
|
- Dropped the docs/release file as current version
|
||||||
|
is 5.0.14 for Scarthgap.
|
||||||
|
|
||||||
|
(cherry picked from commit c72a5dbb64d0777f3f471f1be94e8b2ca91e0959)
|
||||||
|
Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
|
||||||
|
---
|
||||||
|
django/test/signals.py | 2 +-
|
||||||
|
django/utils/translation/trans_real.py | 27 ++++++++++++++++++--------
|
||||||
|
docs/ref/utils.txt | 3 +++
|
||||||
|
tests/i18n/tests.py | 24 ++++++++++++++++++++++-
|
||||||
|
4 files changed, 46 insertions(+), 10 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/django/test/signals.py b/django/test/signals.py
|
||||||
|
index c16f4aa5ee..51ff9c3d98 100644
|
||||||
|
--- a/django/test/signals.py
|
||||||
|
+++ b/django/test/signals.py
|
||||||
|
@@ -152,7 +152,7 @@ def language_changed(*, setting, **kwargs):
|
||||||
|
from django.utils.translation import trans_real
|
||||||
|
|
||||||
|
trans_real._translations = {}
|
||||||
|
- trans_real.check_for_language.cache_clear()
|
||||||
|
+ trans_real.translation_catalog_exists.cache_clear()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(setting_changed)
|
||||||
|
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
|
||||||
|
index 1c42330451..67937a6470 100644
|
||||||
|
--- a/django/utils/translation/trans_real.py
|
||||||
|
+++ b/django/utils/translation/trans_real.py
|
||||||
|
@@ -31,9 +31,10 @@ _default = None
|
||||||
|
# magic gettext number to separate context from message
|
||||||
|
CONTEXT_SEPARATOR = "\x04"
|
||||||
|
|
||||||
|
-# Maximum number of characters that will be parsed from the Accept-Language
|
||||||
|
-# header or cookie to prevent possible denial of service or memory exhaustion
|
||||||
|
-# attacks. About 10x longer than the longest value shown on MDN’s
|
||||||
|
+# Maximum length of a language code that will be processed, to prevent possible
|
||||||
|
+# denial of service or memory exhaustion attacks. Language codes are taken from
|
||||||
|
+# the Accept-Language header, the language cookie, the URL path prefix, or the
|
||||||
|
+# set_language() view. 500 is about 10x the longest value shown on MDN's
|
||||||
|
# Accept-Language page.
|
||||||
|
LANGUAGE_CODE_MAX_LENGTH = 500
|
||||||
|
|
||||||
|
@@ -65,7 +66,7 @@ def reset_cache(*, setting, **kwargs):
|
||||||
|
languages should no longer be accepted.
|
||||||
|
"""
|
||||||
|
if setting in ("LANGUAGES", "LANGUAGE_CODE"):
|
||||||
|
- check_for_language.cache_clear()
|
||||||
|
+ translation_catalog_exists.cache_clear()
|
||||||
|
get_languages.cache_clear()
|
||||||
|
get_supported_language_variant.cache_clear()
|
||||||
|
|
||||||
|
@@ -461,19 +462,29 @@ def all_locale_paths():
|
||||||
|
return [globalpath, *settings.LOCALE_PATHS, *app_paths]
|
||||||
|
|
||||||
|
|
||||||
|
-@functools.lru_cache(maxsize=1000)
|
||||||
|
def check_for_language(lang_code):
|
||||||
|
"""
|
||||||
|
Check whether there is a global language file for the given language
|
||||||
|
code. This is used to decide whether a user-provided language is
|
||||||
|
available.
|
||||||
|
|
||||||
|
- lru_cache should have a maxsize to prevent from memory exhaustion attacks,
|
||||||
|
- as the provided language codes are taken from the HTTP request. See also
|
||||||
|
+ Reject over-length codes before the cached lookup so that oversized,
|
||||||
|
+ attacker-controlled values are not retained as cache keys.
|
||||||
|
+ """
|
||||||
|
+ if lang_code is None or len(lang_code) > LANGUAGE_CODE_MAX_LENGTH:
|
||||||
|
+ return False
|
||||||
|
+ return translation_catalog_exists(lang_code)
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+@functools.lru_cache(maxsize=1000)
|
||||||
|
+def translation_catalog_exists(lang_code):
|
||||||
|
+ """Return whether a translation catalog exists for the given language code.
|
||||||
|
+
|
||||||
|
+ lru_cache should have a maxsize to prevent memory exhaustion attacks. See:
|
||||||
|
<https://www.djangoproject.com/weblog/2007/oct/26/security-fix/>.
|
||||||
|
"""
|
||||||
|
# First, a quick check to make sure lang_code is well-formed (#21458)
|
||||||
|
- if lang_code is None or not language_code_re.search(lang_code):
|
||||||
|
+ if not language_code_re.search(lang_code):
|
||||||
|
return False
|
||||||
|
return any(
|
||||||
|
gettext_module.find("django", path, [to_locale(lang_code)]) is not None
|
||||||
|
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
|
||||||
|
index 1d0178a263..1f44b4eb85 100644
|
||||||
|
--- a/docs/ref/utils.txt
|
||||||
|
+++ b/docs/ref/utils.txt
|
||||||
|
@@ -1082,6 +1082,9 @@ For a complete discussion on the usage of the following see the
|
||||||
|
code (e.g. 'fr', 'pt_BR'). This is used to decide whether a user-provided
|
||||||
|
language is available.
|
||||||
|
|
||||||
|
+ ``lang_code`` has a maximum accepted length of 500 characters. ``False``
|
||||||
|
+ is returned if it exceeds this limit, before any language-file lookup.
|
||||||
|
+
|
||||||
|
.. function:: get_language()
|
||||||
|
|
||||||
|
Returns the currently selected language code. Returns ``None`` if
|
||||||
|
diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py
|
||||||
|
index f74e33bf79..b83c9d6a68 100644
|
||||||
|
--- a/tests/i18n/tests.py
|
||||||
|
+++ b/tests/i18n/tests.py
|
||||||
|
@@ -58,7 +58,10 @@ from django.utils.translation.reloader import (
|
||||||
|
translation_file_changed,
|
||||||
|
watch_for_translation_changes,
|
||||||
|
)
|
||||||
|
-from django.utils.translation.trans_real import LANGUAGE_CODE_MAX_LENGTH
|
||||||
|
+from django.utils.translation.trans_real import (
|
||||||
|
+ LANGUAGE_CODE_MAX_LENGTH,
|
||||||
|
+ translation_catalog_exists,
|
||||||
|
+)
|
||||||
|
|
||||||
|
from .forms import CompanyForm, I18nForm, SelectDateForm
|
||||||
|
from .models import Company, TestModel
|
||||||
|
@@ -1995,6 +1998,25 @@ class CountrySpecificLanguageTests(SimpleTestCase):
|
||||||
|
self.assertFalse(check_for_language("tr-TR.UTF8"))
|
||||||
|
self.assertFalse(check_for_language("de-DE.utf-8"))
|
||||||
|
|
||||||
|
+ def test_check_for_language_lang_code_max_length(self):
|
||||||
|
+ self.addCleanup(translation_catalog_exists.cache_clear)
|
||||||
|
+
|
||||||
|
+ # Overly long codes are rejected before the cached lookup, so they are
|
||||||
|
+ # not retained as cache keys, potentially consuming too much memory.
|
||||||
|
+ # Codes at the maximum length can reach the cached lookup.
|
||||||
|
+ for length, cache_size in [
|
||||||
|
+ (LANGUAGE_CODE_MAX_LENGTH - 1, 1),
|
||||||
|
+ (LANGUAGE_CODE_MAX_LENGTH, 1),
|
||||||
|
+ (LANGUAGE_CODE_MAX_LENGTH + 1, 0),
|
||||||
|
+ ]:
|
||||||
|
+ translation_catalog_exists.cache_clear()
|
||||||
|
+ with self.subTest(length=length):
|
||||||
|
+ self.assertIs(check_for_language("a" * length), False)
|
||||||
|
+ self.assertEqual(
|
||||||
|
+ translation_catalog_exists.cache_info().currsize,
|
||||||
|
+ cache_size,
|
||||||
|
+ )
|
||||||
|
+
|
||||||
|
def test_check_for_language_null(self):
|
||||||
|
self.assertIs(trans_null.check_for_language("en"), True)
|
||||||
|
|
||||||
|
--
|
||||||
|
2.44.4
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ SRC_URI += "file://CVE-2025-64460.patch \
|
|||||||
file://CVE-2025-57833.patch \
|
file://CVE-2025-57833.patch \
|
||||||
file://CVE-2025-59681.patch \
|
file://CVE-2025-59681.patch \
|
||||||
file://CVE-2026-15307.patch \
|
file://CVE-2026-15307.patch \
|
||||||
|
file://CVE-2026-15337.patch \
|
||||||
"
|
"
|
||||||
SRC_URI[sha256sum] = "29019a5763dbd48da1720d687c3522ef40d1c61be6fb2fad27ed79e9f655bc11"
|
SRC_URI[sha256sum] = "29019a5763dbd48da1720d687c3522ef40d1c61be6fb2fad27ed79e9f655bc11"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user