summaryrefslogtreecommitdiffstats
path: root/meta-python/recipes-devtools
diff options
context:
space:
mode:
authorSaravanan <saravanan.kadambathursubramaniyam@windriver.com>2025-11-30 17:50:17 +0530
committerGyorgy Sarvari <skandigraun@gmail.com>2025-11-30 15:16:36 +0100
commit8b438a9d7bb259b94244fc61ef7281404e506674 (patch)
tree054495e3a74fb89e3a99ace828cabeed2d2a6139 /meta-python/recipes-devtools
parent740980aabacc371ed0b9156ca1c489d2d6677710 (diff)
downloadmeta-openembedded-8b438a9d7bb259b94244fc61ef7281404e506674.tar.gz
python3-django: fix CVE-2024-39330
Reference: https://nvd.nist.gov/vuln/detail/CVE-2024-39330 Upstream-patch: https://github.com/django/django/commit/2b00edc0151a660d1eb86da4059904a0fc4e095e Signed-off-by: Saravanan <saravanan.kadambathursubramaniyam@windriver.com> Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
Diffstat (limited to 'meta-python/recipes-devtools')
-rw-r--r--meta-python/recipes-devtools/python/python3-django-3.2.25/CVE-2024-39330.patch184
-rw-r--r--meta-python/recipes-devtools/python/python3-django/CVE-2024-39330.patch181
-rw-r--r--meta-python/recipes-devtools/python/python3-django_2.2.28.bb1
-rw-r--r--meta-python/recipes-devtools/python/python3-django_3.2.25.bb1
4 files changed, 367 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-django-3.2.25/CVE-2024-39330.patch b/meta-python/recipes-devtools/python/python3-django-3.2.25/CVE-2024-39330.patch
new file mode 100644
index 0000000000..81e9a38ed1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-django-3.2.25/CVE-2024-39330.patch
@@ -0,0 +1,184 @@
1From 2b00edc0151a660d1eb86da4059904a0fc4e095e Mon Sep 17 00:00:00 2001
2From: Natalia <124304+nessita@users.noreply.github.com>
3Date: Wed, 20 Mar 2024 13:55:21 -0300
4Subject: [PATCH] Fixed CVE-2024-39330 -- Added extra file name validation in
5 Storage's save method.
6
7Thanks to Josh Schneier for the report, and to Carlton Gibson and Sarah
8Boyce for the reviews.
9
10CVE: CVE-2024-39330
11
12Upstream-Status: Backport
13https://github.com/django/django/commit/2b00edc0151a660d1eb86da4059904a0fc4e095e
14
15Signed-off-by: Saravanan <saravanan.kadambathursubramaniyam@windriver.com>
16---
17 django/core/files/storage.py | 11 ++++++
18 django/core/files/utils.py | 7 ++--
19 docs/releases/3.2.25.txt | 12 ++++++
20 tests/file_storage/test_base.py | 70 +++++++++++++++++++++++++++++++++
21 tests/file_storage/tests.py | 6 ---
22 5 files changed, 96 insertions(+), 10 deletions(-)
23 create mode 100644 tests/file_storage/test_base.py
24
25diff --git a/django/core/files/storage.py b/django/core/files/storage.py
26index 22984f9..680f5ec 100644
27--- a/django/core/files/storage.py
28+++ b/django/core/files/storage.py
29@@ -50,7 +50,18 @@ class Storage:
30 if not hasattr(content, 'chunks'):
31 content = File(content, name)
32
33+ # Ensure that the name is valid, before and after having the storage
34+ # system potentially modifying the name. This duplicates the check made
35+ # inside `get_available_name` but it's necessary for those cases where
36+ # `get_available_name` is overriden and validation is lost.
37+ validate_file_name(name, allow_relative_path=True)
38+
39+ # Potentially find a different name depending on storage constraints.
40 name = self.get_available_name(name, max_length=max_length)
41+ # Validate the (potentially) new name.
42+ validate_file_name(name, allow_relative_path=True)
43+
44+ # The save operation should return the actual name of the file saved.
45 name = self._save(name, content)
46 # Ensure that the name returned from the storage system is still valid.
47 validate_file_name(name, allow_relative_path=True)
48diff --git a/django/core/files/utils.py b/django/core/files/utils.py
49index f28cea1..a1fea44 100644
50--- a/django/core/files/utils.py
51+++ b/django/core/files/utils.py
52@@ -10,10 +10,9 @@ def validate_file_name(name, allow_relative_path=False):
53 raise SuspiciousFileOperation("Could not derive file name from '%s'" % name)
54
55 if allow_relative_path:
56- # Use PurePosixPath() because this branch is checked only in
57- # FileField.generate_filename() where all file paths are expected to be
58- # Unix style (with forward slashes).
59- path = pathlib.PurePosixPath(name)
60+ # Ensure that name can be treated as a pure posix path, i.e. Unix
61+ # style (with forward slashes).
62+ path = pathlib.PurePosixPath(str(name).replace("\\", "/"))
63 if path.is_absolute() or '..' in path.parts:
64 raise SuspiciousFileOperation(
65 "Detected path traversal attempt in '%s'" % name
66diff --git a/docs/releases/3.2.25.txt b/docs/releases/3.2.25.txt
67index a613b08..60236d5 100644
68--- a/docs/releases/3.2.25.txt
69+++ b/docs/releases/3.2.25.txt
70@@ -47,6 +47,18 @@ The :meth:`~django.contrib.auth.backends.ModelBackend.authenticate()` method
71 allowed remote attackers to enumerate users via a timing attack involving login
72 requests for users with unusable passwords.
73
74+CVE-2024-39330: Potential directory-traversal via ``Storage.save()``
75+====================================================================
76+
77+Derived classes of the :class:`~django.core.files.storage.Storage` base class
78+which override :meth:`generate_filename()
79+<django.core.files.storage.Storage.generate_filename()>` without replicating
80+the file path validations existing in the parent class, allowed for potential
81+directory-traversal via certain inputs when calling :meth:`save()
82+<django.core.files.storage.Storage.save()>`.
83+
84+Built-in ``Storage`` sub-classes were not affected by this vulnerability.
85+
86 Bugfixes
87 ========
88
89diff --git a/tests/file_storage/test_base.py b/tests/file_storage/test_base.py
90new file mode 100644
91index 0000000..c5338b8
92--- /dev/null
93+++ b/tests/file_storage/test_base.py
94@@ -0,0 +1,70 @@
95+import os
96+from unittest import mock
97+
98+from django.core.exceptions import SuspiciousFileOperation
99+from django.core.files.storage import Storage
100+from django.test import SimpleTestCase
101+
102+
103+class CustomStorage(Storage):
104+ """Simple Storage subclass implementing the bare minimum for testing."""
105+
106+ def exists(self, name):
107+ return False
108+
109+ def _save(self, name):
110+ return name
111+
112+
113+class StorageValidateFileNameTests(SimpleTestCase):
114+ invalid_file_names = [
115+ os.path.join("path", "to", os.pardir, "test.file"),
116+ os.path.join(os.path.sep, "path", "to", "test.file"),
117+ ]
118+ error_msg = "Detected path traversal attempt in '%s'"
119+
120+ def test_validate_before_get_available_name(self):
121+ s = CustomStorage()
122+ # The initial name passed to `save` is not valid nor safe, fail early.
123+ for name in self.invalid_file_names:
124+ with (
125+ self.subTest(name=name),
126+ mock.patch.object(s, "get_available_name") as mock_get_available_name,
127+ mock.patch.object(s, "_save") as mock_internal_save,
128+ ):
129+ with self.assertRaisesMessage(
130+ SuspiciousFileOperation, self.error_msg % name
131+ ):
132+ s.save(name, content="irrelevant")
133+ self.assertEqual(mock_get_available_name.mock_calls, [])
134+ self.assertEqual(mock_internal_save.mock_calls, [])
135+
136+ def test_validate_after_get_available_name(self):
137+ s = CustomStorage()
138+ # The initial name passed to `save` is valid and safe, but the returned
139+ # name from `get_available_name` is not.
140+ for name in self.invalid_file_names:
141+ with (
142+ self.subTest(name=name),
143+ mock.patch.object(s, "get_available_name", return_value=name),
144+ mock.patch.object(s, "_save") as mock_internal_save,
145+ ):
146+ with self.assertRaisesMessage(
147+ SuspiciousFileOperation, self.error_msg % name
148+ ):
149+ s.save("valid-file-name.txt", content="irrelevant")
150+ self.assertEqual(mock_internal_save.mock_calls, [])
151+
152+ def test_validate_after_internal_save(self):
153+ s = CustomStorage()
154+ # The initial name passed to `save` is valid and safe, but the result
155+ # from `_save` is not (this is achieved by monkeypatching _save).
156+ for name in self.invalid_file_names:
157+ with (
158+ self.subTest(name=name),
159+ mock.patch.object(s, "_save", return_value=name),
160+ ):
161+ with self.assertRaisesMessage(
162+ SuspiciousFileOperation, self.error_msg % name
163+ ):
164+ s.save("valid-file-name.txt", content="irrelevant")
165diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
166index 7238093..6d17a71 100644
167--- a/tests/file_storage/tests.py
168+++ b/tests/file_storage/tests.py
169@@ -297,12 +297,6 @@ class FileStorageTests(SimpleTestCase):
170
171 self.storage.delete('path/to/test.file')
172
173- def test_file_save_abs_path(self):
174- test_name = 'path/to/test.file'
175- f = ContentFile('file saved with path')
176- f_name = self.storage.save(os.path.join(self.temp_dir, test_name), f)
177- self.assertEqual(f_name, test_name)
178-
179 def test_save_doesnt_close(self):
180 with TemporaryUploadedFile('test', 'text/plain', 1, 'utf8') as file:
181 file.write(b'1')
182--
1832.48.1
184
diff --git a/meta-python/recipes-devtools/python/python3-django/CVE-2024-39330.patch b/meta-python/recipes-devtools/python/python3-django/CVE-2024-39330.patch
new file mode 100644
index 0000000000..759716617a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-django/CVE-2024-39330.patch
@@ -0,0 +1,181 @@
1From 2b00edc0151a660d1eb86da4059904a0fc4e095e Mon Sep 17 00:00:00 2001
2From: Natalia <124304+nessita@users.noreply.github.com>
3Date: Wed, 20 Mar 2024 13:55:21 -0300
4Subject: [PATCH] Fixed CVE-2024-39330 -- Added extra file name validation in
5 Storage's save method.
6
7Thanks to Josh Schneier for the report, and to Carlton Gibson and Sarah
8Boyce for the reviews.
9
10CVE: CVE-2024-39330
11
12Upstream-Status: Backport
13https://github.com/django/django/commit/2b00edc0151a660d1eb86da4059904a0fc4e095e
14
15Signed-off-by: Saravanan <saravanan.kadambathursubramaniyam@windriver.com>
16---
17 django/core/files/storage.py | 11 ++++++
18 django/core/files/utils.py | 7 ++--
19 docs/releases/2.2.28.txt | 12 ++++++
20 tests/file_storage/test_base.py | 70 +++++++++++++++++++++++++++++++++
21 tests/file_storage/tests.py | 6 ---
22 5 files changed, 96 insertions(+), 10 deletions(-)
23 create mode 100644 tests/file_storage/test_base.py
24
25diff --git a/django/core/files/storage.py b/django/core/files/storage.py
26index ea5bbc8..8c633ec 100644
27--- a/django/core/files/storage.py
28+++ b/django/core/files/storage.py
29@@ -50,7 +50,18 @@ class Storage:
30 if not hasattr(content, 'chunks'):
31 content = File(content, name)
32
33+ # Ensure that the name is valid, before and after having the storage
34+ # system potentially modifying the name. This duplicates the check made
35+ # inside `get_available_name` but it's necessary for those cases where
36+ # `get_available_name` is overriden and validation is lost.
37+ validate_file_name(name, allow_relative_path=True)
38+
39+ # Potentially find a different name depending on storage constraints.
40 name = self.get_available_name(name, max_length=max_length)
41+ # Validate the (potentially) new name.
42+ validate_file_name(name, allow_relative_path=True)
43+
44+ # The save operation should return the actual name of the file saved.
45 name = self._save(name, content)
46 # Ensure that the name returned from the storage system is still valid.
47 validate_file_name(name, allow_relative_path=True)
48diff --git a/django/core/files/utils.py b/django/core/files/utils.py
49index f28cea1..a1fea44 100644
50--- a/django/core/files/utils.py
51+++ b/django/core/files/utils.py
52@@ -10,10 +10,9 @@ def validate_file_name(name, allow_relative_path=False):
53 raise SuspiciousFileOperation("Could not derive file name from '%s'" % name)
54
55 if allow_relative_path:
56- # Use PurePosixPath() because this branch is checked only in
57- # FileField.generate_filename() where all file paths are expected to be
58- # Unix style (with forward slashes).
59- path = pathlib.PurePosixPath(name)
60+ # Ensure that name can be treated as a pure posix path, i.e. Unix
61+ # style (with forward slashes).
62+ path = pathlib.PurePosixPath(str(name).replace("\\", "/"))
63 if path.is_absolute() or '..' in path.parts:
64 raise SuspiciousFileOperation(
65 "Detected path traversal attempt in '%s'" % name
66diff --git a/docs/releases/2.2.28.txt b/docs/releases/2.2.28.txt
67index 22fa80e..3503f38 100644
68--- a/docs/releases/2.2.28.txt
69+++ b/docs/releases/2.2.28.txt
70@@ -131,3 +131,15 @@ The :meth:`~django.contrib.auth.backends.ModelBackend.authenticate()` method
71 allowed remote attackers to enumerate users via a timing attack involving login
72 requests for users with unusable passwords.
73
74+CVE-2024-39330: Potential directory-traversal via ``Storage.save()``
75+====================================================================
76+
77+Derived classes of the :class:`~django.core.files.storage.Storage` base class
78+which override :meth:`generate_filename()
79+<django.core.files.storage.Storage.generate_filename()>` without replicating
80+the file path validations existing in the parent class, allowed for potential
81+directory-traversal via certain inputs when calling :meth:`save()
82+<django.core.files.storage.Storage.save()>`.
83+
84+Built-in ``Storage`` sub-classes were not affected by this vulnerability.
85+
86diff --git a/tests/file_storage/test_base.py b/tests/file_storage/test_base.py
87new file mode 100644
88index 0000000..c5338b8
89--- /dev/null
90+++ b/tests/file_storage/test_base.py
91@@ -0,0 +1,70 @@
92+import os
93+from unittest import mock
94+
95+from django.core.exceptions import SuspiciousFileOperation
96+from django.core.files.storage import Storage
97+from django.test import SimpleTestCase
98+
99+
100+class CustomStorage(Storage):
101+ """Simple Storage subclass implementing the bare minimum for testing."""
102+
103+ def exists(self, name):
104+ return False
105+
106+ def _save(self, name):
107+ return name
108+
109+
110+class StorageValidateFileNameTests(SimpleTestCase):
111+ invalid_file_names = [
112+ os.path.join("path", "to", os.pardir, "test.file"),
113+ os.path.join(os.path.sep, "path", "to", "test.file"),
114+ ]
115+ error_msg = "Detected path traversal attempt in '%s'"
116+
117+ def test_validate_before_get_available_name(self):
118+ s = CustomStorage()
119+ # The initial name passed to `save` is not valid nor safe, fail early.
120+ for name in self.invalid_file_names:
121+ with (
122+ self.subTest(name=name),
123+ mock.patch.object(s, "get_available_name") as mock_get_available_name,
124+ mock.patch.object(s, "_save") as mock_internal_save,
125+ ):
126+ with self.assertRaisesMessage(
127+ SuspiciousFileOperation, self.error_msg % name
128+ ):
129+ s.save(name, content="irrelevant")
130+ self.assertEqual(mock_get_available_name.mock_calls, [])
131+ self.assertEqual(mock_internal_save.mock_calls, [])
132+
133+ def test_validate_after_get_available_name(self):
134+ s = CustomStorage()
135+ # The initial name passed to `save` is valid and safe, but the returned
136+ # name from `get_available_name` is not.
137+ for name in self.invalid_file_names:
138+ with (
139+ self.subTest(name=name),
140+ mock.patch.object(s, "get_available_name", return_value=name),
141+ mock.patch.object(s, "_save") as mock_internal_save,
142+ ):
143+ with self.assertRaisesMessage(
144+ SuspiciousFileOperation, self.error_msg % name
145+ ):
146+ s.save("valid-file-name.txt", content="irrelevant")
147+ self.assertEqual(mock_internal_save.mock_calls, [])
148+
149+ def test_validate_after_internal_save(self):
150+ s = CustomStorage()
151+ # The initial name passed to `save` is valid and safe, but the result
152+ # from `_save` is not (this is achieved by monkeypatching _save).
153+ for name in self.invalid_file_names:
154+ with (
155+ self.subTest(name=name),
156+ mock.patch.object(s, "_save", return_value=name),
157+ ):
158+ with self.assertRaisesMessage(
159+ SuspiciousFileOperation, self.error_msg % name
160+ ):
161+ s.save("valid-file-name.txt", content="irrelevant")
162diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
163index 4c6f692..0e69264 100644
164--- a/tests/file_storage/tests.py
165+++ b/tests/file_storage/tests.py
166@@ -291,12 +291,6 @@ class FileStorageTests(SimpleTestCase):
167
168 self.storage.delete('path/to/test.file')
169
170- def test_file_save_abs_path(self):
171- test_name = 'path/to/test.file'
172- f = ContentFile('file saved with path')
173- f_name = self.storage.save(os.path.join(self.temp_dir, test_name), f)
174- self.assertEqual(f_name, test_name)
175-
176 def test_save_doesnt_close(self):
177 with TemporaryUploadedFile('test', 'text/plain', 1, 'utf8') as file:
178 file.write(b'1')
179--
1802.48.1
181
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 3000e93f81..b5b2570ddb 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
@@ -29,6 +29,7 @@ SRC_URI += "file://CVE-2023-31047.patch \
29 file://CVE-2024-56374.patch \ 29 file://CVE-2024-56374.patch \
30 file://CVE-2025-57833.patch \ 30 file://CVE-2025-57833.patch \
31 file://CVE-2024-39329.patch \ 31 file://CVE-2024-39329.patch \
32 file://CVE-2024-39330.patch \
32 " 33 "
33 34
34SRC_URI[sha256sum] = "0200b657afbf1bc08003845ddda053c7641b9b24951e52acd51f6abda33a7413" 35SRC_URI[sha256sum] = "0200b657afbf1bc08003845ddda053c7641b9b24951e52acd51f6abda33a7413"
diff --git a/meta-python/recipes-devtools/python/python3-django_3.2.25.bb b/meta-python/recipes-devtools/python/python3-django_3.2.25.bb
index 4301eccaae..2d94b2109c 100644
--- a/meta-python/recipes-devtools/python/python3-django_3.2.25.bb
+++ b/meta-python/recipes-devtools/python/python3-django_3.2.25.bb
@@ -11,6 +11,7 @@ SRC_URI += "\
11 file://CVE-2024-56374.patch \ 11 file://CVE-2024-56374.patch \
12 file://CVE-2025-57833.patch \ 12 file://CVE-2025-57833.patch \
13 file://CVE-2024-39329.patch \ 13 file://CVE-2024-39329.patch \
14 file://CVE-2024-39330.patch \
14" 15"
15 16
16# Set DEFAULT_PREFERENCE so that the LTS version of django is built by 17# Set DEFAULT_PREFERENCE so that the LTS version of django is built by