summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorNatalia <124304+nessita@users.noreply.github.com>2026-01-21 18:03:20 -0300
committerNatalia <124304+nessita@users.noreply.github.com>2026-03-03 09:17:39 -0300
commitb07ed2a1e445efde54fc64cb8c37e0f4f7fe53e5 (patch)
tree0c9cba1bad625b92217ceac4733793632f4d7f34 /django
parent4d3c184686626d224d9a87451410ecf802b41f7c (diff)
[5.2.x] Fixed CVE-2026-25674 -- Prevented potentially incorrect permissions on file system object creation.
This fix introduces `safe_makedirs()` in the `os` utils as a safer alternative to `os.makedirs()` that avoids umask-related race conditions in multi-threaded environments. This is a workaround for https://github.com/python/cpython/issues/86533 and the solution is based on the fix being proposed for CPython. Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> Co-authored-by: Zackery Spytz <zspytz@gmail.com> Refs CVE-2020-24583 and #31921. Thanks Tarek Nakkouch for the report, and Jake Howard, Jacob Walls, and Shai Berger for reviews. Backport of 019e44f67a8dace67b786e2818938c8691132988 from main.
Diffstat (limited to 'django')
-rw-r--r--django/core/cache/backends/filebased.py10
-rw-r--r--django/core/files/storage/filesystem.py13
-rw-r--r--django/utils/_os.py58
3 files changed, 65 insertions, 16 deletions
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
index 862a8b57d9..9f2ad48ac8 100644
--- a/django/core/cache/backends/filebased.py
+++ b/django/core/cache/backends/filebased.py
@@ -12,6 +12,7 @@ from hashlib import md5
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
from django.core.files import locks
from django.core.files.move import file_move_safe
+from django.utils._os import safe_makedirs
class FileBasedCache(BaseCache):
@@ -115,13 +116,10 @@ class FileBasedCache(BaseCache):
self._delete(fname)
def _createdir(self):
- # Set the umask because os.makedirs() doesn't apply the "mode" argument
+ # Workaround because os.makedirs() doesn't apply the "mode" argument
# to intermediate-level directories.
- old_umask = os.umask(0o077)
- try:
- os.makedirs(self._dir, 0o700, exist_ok=True)
- finally:
- os.umask(old_umask)
+ # https://github.com/python/cpython/issues/86533
+ safe_makedirs(self._dir, mode=0o700, exist_ok=True)
def _key_to_file(self, key, version=None):
"""
diff --git a/django/core/files/storage/filesystem.py b/django/core/files/storage/filesystem.py
index 1bd9aa0a6c..bdb95484e7 100644
--- a/django/core/files/storage/filesystem.py
+++ b/django/core/files/storage/filesystem.py
@@ -7,7 +7,7 @@ from django.conf import settings
from django.core.files import File, locks
from django.core.files.move import file_move_safe
from django.core.signals import setting_changed
-from django.utils._os import safe_join
+from django.utils._os import safe_join, safe_makedirs
from django.utils.deconstruct import deconstructible
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.encoding import filepath_to_uri
@@ -87,15 +87,10 @@ class FileSystemStorage(Storage, StorageSettingsMixin):
directory = os.path.dirname(full_path)
try:
if self.directory_permissions_mode is not None:
- # Set the umask because os.makedirs() doesn't apply the "mode"
+ # Workaround because os.makedirs() doesn't apply the "mode"
# argument to intermediate-level directories.
- old_umask = os.umask(0o777 & ~self.directory_permissions_mode)
- try:
- os.makedirs(
- directory, self.directory_permissions_mode, exist_ok=True
- )
- finally:
- os.umask(old_umask)
+ # https://github.com/python/cpython/issues/86533
+ safe_makedirs(directory, self.directory_permissions_mode, exist_ok=True)
else:
os.makedirs(directory, exist_ok=True)
except FileExistsError:
diff --git a/django/utils/_os.py b/django/utils/_os.py
index e9e1bcbfaf..159e64ceda 100644
--- a/django/utils/_os.py
+++ b/django/utils/_os.py
@@ -1,11 +1,67 @@
import os
import tempfile
-from os.path import abspath, dirname, join, normcase, sep
+from os.path import abspath, curdir, dirname, join, normcase, sep
from pathlib import Path
from django.core.exceptions import SuspiciousFileOperation
+# Copied verbatim (minus `os.path` fixes) from:
+# https://github.com/python/cpython/pull/23901.
+# Python versions >= PY315 may include this fix, so periodic checks are needed
+# to remove this vendored copy of `makedirs` once solved upstream.
+def makedirs(name, mode=0o777, exist_ok=False, *, parent_mode=None):
+ """makedirs(name [, mode=0o777][, exist_ok=False][, parent_mode=None])
+
+ Super-mkdir; create a leaf directory and all intermediate ones. Works like
+ mkdir, except that any intermediate path segment (not just the rightmost)
+ will be created if it does not exist. If the target directory already
+ exists, raise an OSError if exist_ok is False. Otherwise no exception is
+ raised. If parent_mode is not None, it will be used as the mode for any
+ newly-created, intermediate-level directories. Otherwise, intermediate
+ directories are created with the default permissions (respecting umask).
+ This is recursive.
+
+ """
+ head, tail = os.path.split(name)
+ if not tail:
+ head, tail = os.path.split(head)
+ if head and tail and not os.path.exists(head):
+ try:
+ if parent_mode is not None:
+ makedirs(
+ head, mode=parent_mode, exist_ok=exist_ok, parent_mode=parent_mode
+ )
+ else:
+ makedirs(head, exist_ok=exist_ok)
+ except FileExistsError:
+ # Defeats race condition when another thread created the path
+ pass
+ cdir = curdir
+ if isinstance(tail, bytes):
+ cdir = bytes(curdir, "ASCII")
+ if tail == cdir: # xxx/newdir/. exists if xxx/newdir exists
+ return
+ try:
+ os.mkdir(name, mode)
+ # PY315: The call to `chmod()` is not in the CPython proposed code.
+ # Apply `chmod()` after `mkdir()` to enforce the exact requested
+ # permissions, since the kernel masks the mode argument with the
+ # process umask. This guarantees consistent directory permissions
+ # without mutating global umask state.
+ os.chmod(name, mode)
+ except OSError:
+ # Cannot rely on checking for EEXIST, since the operating system
+ # could give priority to other errors like EACCES or EROFS
+ if not exist_ok or not os.path.isdir(name):
+ raise
+
+
+def safe_makedirs(name, mode=0o777, exist_ok=False):
+ """Create directories recursively with explicit `mode` on each level."""
+ makedirs(name=name, mode=mode, exist_ok=exist_ok, parent_mode=mode)
+
+
def safe_join(base, *paths):
"""
Join one or more path components to the base path component intelligently.