Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 08892bf

Browse files
committed
[3.0.x] Fixed CVE-2020-24583, #31921 -- Fixed permissions on intermediate-level static and storage directories on Python 3.7+.
Thanks WhiteSage for the report. Backport of ea0febbba531a3ecc8c77b570efbfb68ca7155db from master.
1 parent db8b935 commit 08892bf

File tree

6 files changed

+75
-26
lines changed

6 files changed

+75
-26
lines changed

django/core/files/storage.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,9 @@ def _save(self, name, content):
237237
directory = os.path.dirname(full_path)
238238
try:
239239
if self.directory_permissions_mode is not None:
240-
# os.makedirs applies the global umask, so we reset it,
241-
# for consistency with file_permissions_mode behavior.
242-
old_umask = os.umask(0)
240+
# Set the umask because os.makedirs() doesn't apply the "mode"
241+
# argument to intermediate-level directories.
242+
old_umask = os.umask(0o777 & ~self.directory_permissions_mode)
243243
try:
244244
os.makedirs(directory, self.directory_permissions_mode, exist_ok=True)
245245
finally:

docs/releases/2.2.16.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@ Django 2.2.16 release notes
44

55
*Expected September 1, 2020*
66

7-
Django 2.2.16 fixes two data loss bugs in 2.2.15.
7+
Django 2.2.16 fixes a security issue and two data loss bugs in 2.2.15.
8+
9+
CVE-2020-24583: Incorrect permissions on intermediate-level directories on Python 3.7+
10+
======================================================================================
11+
12+
On Python 3.7+, :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` mode was not
13+
applied to intermediate-level directories created in the process of uploading
14+
files and to intermediate-level collected static directories when using the
15+
:djadmin:`collectstatic` management command.
16+
17+
You should review and manually fix permissions on existing intermediate-level
18+
directories.
819

920
Bugfixes
1021
========

docs/releases/3.0.10.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@ Django 3.0.10 release notes
44

55
*Expected September 1, 2020*
66

7-
Django 3.0.10 fixes two data loss bugs in 3.0.9.
7+
Django 3.0.10 fixes a security issue and two data loss bugs in 3.0.9.
8+
9+
CVE-2020-24583: Incorrect permissions on intermediate-level directories on Python 3.7+
10+
======================================================================================
11+
12+
On Python 3.7+, :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` mode was not
13+
applied to intermediate-level directories created in the process of uploading
14+
files and to intermediate-level collected static directories when using the
15+
:djadmin:`collectstatic` management command.
16+
17+
You should review and manually fix permissions on existing intermediate-level
18+
directories.
819

920
Bugfixes
1021
========

tests/file_storage/tests.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import unittest
88
from datetime import datetime, timedelta
99
from io import StringIO
10+
from pathlib import Path
1011
from urllib.request import urlopen
1112

1213
from django.core.cache import cache
@@ -910,16 +911,19 @@ def test_file_upload_default_permissions(self):
910911
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765)
911912
def test_file_upload_directory_permissions(self):
912913
self.storage = FileSystemStorage(self.storage_dir)
913-
name = self.storage.save("the_directory/the_file", ContentFile("data"))
914-
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
915-
self.assertEqual(dir_mode, 0o765)
914+
name = self.storage.save('the_directory/subdir/the_file', ContentFile('data'))
915+
file_path = Path(self.storage.path(name))
916+
self.assertEqual(file_path.parent.stat().st_mode & 0o777, 0o765)
917+
self.assertEqual(file_path.parent.parent.stat().st_mode & 0o777, 0o765)
916918

917919
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=None)
918920
def test_file_upload_directory_default_permissions(self):
919921
self.storage = FileSystemStorage(self.storage_dir)
920-
name = self.storage.save("the_directory/the_file", ContentFile("data"))
921-
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
922-
self.assertEqual(dir_mode, 0o777 & ~self.umask)
922+
name = self.storage.save('the_directory/subdir/the_file', ContentFile('data'))
923+
file_path = Path(self.storage.path(name))
924+
expected_mode = 0o777 & ~self.umask
925+
self.assertEqual(file_path.parent.stat().st_mode & 0o777, expected_mode)
926+
self.assertEqual(file_path.parent.parent.stat().st_mode & 0o777, expected_mode)
923927

924928

925929
class FileStoragePathParsing(SimpleTestCase):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
html {height: 100%;}

tests/staticfiles_tests/test_storage.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import tempfile
55
import unittest
66
from io import StringIO
7+
from pathlib import Path
78
from unittest import mock
89

910
from django.conf import settings
@@ -530,25 +531,39 @@ def run_collectstatic(self, **kwargs):
530531
)
531532
def test_collect_static_files_permissions(self):
532533
call_command('collectstatic', **self.command_params)
533-
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
534-
test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
535-
file_mode = os.stat(test_file)[0] & 0o777
536-
dir_mode = os.stat(test_dir)[0] & 0o777
534+
static_root = Path(settings.STATIC_ROOT)
535+
test_file = static_root / 'test.txt'
536+
file_mode = test_file.stat().st_mode & 0o777
537537
self.assertEqual(file_mode, 0o655)
538-
self.assertEqual(dir_mode, 0o765)
538+
tests = [
539+
static_root / 'subdir',
540+
static_root / 'nested',
541+
static_root / 'nested' / 'css',
542+
]
543+
for directory in tests:
544+
with self.subTest(directory=directory):
545+
dir_mode = directory.stat().st_mode & 0o777
546+
self.assertEqual(dir_mode, 0o765)
539547

540548
@override_settings(
541549
FILE_UPLOAD_PERMISSIONS=None,
542550
FILE_UPLOAD_DIRECTORY_PERMISSIONS=None,
543551
)
544552
def test_collect_static_files_default_permissions(self):
545553
call_command('collectstatic', **self.command_params)
546-
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
547-
test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
548-
file_mode = os.stat(test_file)[0] & 0o777
549-
dir_mode = os.stat(test_dir)[0] & 0o777
554+
static_root = Path(settings.STATIC_ROOT)
555+
test_file = static_root / 'test.txt'
556+
file_mode = test_file.stat().st_mode & 0o777
550557
self.assertEqual(file_mode, 0o666 & ~self.umask)
551-
self.assertEqual(dir_mode, 0o777 & ~self.umask)
558+
tests = [
559+
static_root / 'subdir',
560+
static_root / 'nested',
561+
static_root / 'nested' / 'css',
562+
]
563+
for directory in tests:
564+
with self.subTest(directory=directory):
565+
dir_mode = directory.stat().st_mode & 0o777
566+
self.assertEqual(dir_mode, 0o777 & ~self.umask)
552567

553568
@override_settings(
554569
FILE_UPLOAD_PERMISSIONS=0o655,
@@ -557,12 +572,19 @@ def test_collect_static_files_default_permissions(self):
557572
)
558573
def test_collect_static_files_subclass_of_static_storage(self):
559574
call_command('collectstatic', **self.command_params)
560-
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
561-
test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
562-
file_mode = os.stat(test_file)[0] & 0o777
563-
dir_mode = os.stat(test_dir)[0] & 0o777
575+
static_root = Path(settings.STATIC_ROOT)
576+
test_file = static_root / 'test.txt'
577+
file_mode = test_file.stat().st_mode & 0o777
564578
self.assertEqual(file_mode, 0o640)
565-
self.assertEqual(dir_mode, 0o740)
579+
tests = [
580+
static_root / 'subdir',
581+
static_root / 'nested',
582+
static_root / 'nested' / 'css',
583+
]
584+
for directory in tests:
585+
with self.subTest(directory=directory):
586+
dir_mode = directory.stat().st_mode & 0o777
587+
self.assertEqual(dir_mode, 0o740)
566588

567589

568590
@override_settings(

0 commit comments

Comments
 (0)