summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-04-04 09:52:22 +0200
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-04-07 16:17:50 +0200
commit318c16d2b8157b0ca3fa5f69d0306409b57314b9 (patch)
treebccdf205b00405de540f3622274c377bbc88f501
parent506cf74b0ac3a61c1bc341f9beebf8f9c087a7e4 (diff)
[4.2.x] Fixed #36298 -- Truncated the overwritten file content in file_move_safe().
Regression in 58cd4902a71a3695dd6c21dc957f59c333db364c. Thanks Baptiste Mispelon for the report. Backport of 8ad3e80e88201f4c557f6fa79fcfc0f8a0961830 from main.
-rw-r--r--django/core/files/move.py1
-rw-r--r--docs/releases/4.2.21.txt15
-rw-r--r--docs/releases/index.txt1
-rw-r--r--tests/files/tests.py21
4 files changed, 38 insertions, 0 deletions
diff --git a/django/core/files/move.py b/django/core/files/move.py
index 95d69f9d94..44f91061f5 100644
--- a/django/core/files/move.py
+++ b/django/core/files/move.py
@@ -67,6 +67,7 @@ def file_move_safe(
| os.O_CREAT
| getattr(os, "O_BINARY", 0)
| (os.O_EXCL if not allow_overwrite else 0)
+ | os.O_TRUNC
),
)
try:
diff --git a/docs/releases/4.2.21.txt b/docs/releases/4.2.21.txt
new file mode 100644
index 0000000000..36e24df12f
--- /dev/null
+++ b/docs/releases/4.2.21.txt
@@ -0,0 +1,15 @@
+===========================
+Django 4.2.21 release notes
+===========================
+
+*Expected May 7, 2025*
+
+Django 4.2.21 fixes a data loss bug in 4.2.20.
+
+Bugfixes
+========
+
+* Fixed a data corruption possibility in ``file_move_safe()`` when
+ ``allow_overwrite=True``, where leftover content from a previously larger
+ file could remain after overwriting with a smaller one due to lack of
+ truncation (:ticket:`36298`).
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index 00e4465845..af5038a095 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
+ 4.2.21
4.2.20
4.2.19
4.2.18
diff --git a/tests/files/tests.py b/tests/files/tests.py
index b3478d2732..99a289bee5 100644
--- a/tests/files/tests.py
+++ b/tests/files/tests.py
@@ -475,6 +475,27 @@ class FileMoveSafeTests(unittest.TestCase):
os.close(handle_b)
os.close(handle_c)
+ def test_file_move_ensure_truncation(self):
+ with tempfile.NamedTemporaryFile(delete=False) as src:
+ src.write(b"content")
+ src_name = src.name
+ self.addCleanup(
+ lambda: os.remove(src_name) if os.path.exists(src_name) else None
+ )
+
+ with tempfile.NamedTemporaryFile(delete=False) as dest:
+ dest.write(b"This is a longer content.")
+ dest_name = dest.name
+ self.addCleanup(os.remove, dest_name)
+
+ with mock.patch("django.core.files.move.os.rename", side_effect=OSError()):
+ file_move_safe(src_name, dest_name, allow_overwrite=True)
+
+ with open(dest_name, "rb") as f:
+ content = f.read()
+
+ self.assertEqual(content, b"content")
+
class SpooledTempTests(unittest.TestCase):
def test_in_memory_spooled_temp(self):