Raised SuspiciousFileOperation in safe_join.

Added a test for the condition safe_join is designed to prevent.

Previously, a generic ValueError was raised. It was impossible to tell
an intentional exception raised to implement safe_join's contract from
an unintentional exception caused by incorrect inputs or unexpected
conditions. That resulted in bizarre exception catching patterns, which
this patch removes.

Since safe_join is a private API and since the change is unlikely to
create security issues for users who use it anyway -- at worst, an
uncaught SuspiciousFileOperation exception will bubble up -- it isn't
documented.
This commit is contained in:
Aymeric Augustin 2014-11-11 18:59:49 +01:00
parent 40ba6f21bb
commit b8ba73cd0c
5 changed files with 18 additions and 19 deletions

View File

@ -277,11 +277,7 @@ class FileSystemStorage(Storage):
return directories, files return directories, files
def path(self, name): def path(self, name):
try: return safe_join(self.location, name)
path = safe_join(self.location, name)
except ValueError:
raise SuspiciousFileOperation("Attempted access to '%s' denied." % name)
return os.path.normpath(path)
def size(self, name): def size(self, name):
return os.path.getsize(self.path(name)) return os.path.getsize(self.path(name))

View File

@ -8,6 +8,7 @@ import sys
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation
from django.template.base import TemplateDoesNotExist from django.template.base import TemplateDoesNotExist
from django.template.loader import BaseLoader from django.template.loader import BaseLoader
from django.utils._os import safe_join from django.utils._os import safe_join
@ -47,11 +48,9 @@ class Loader(BaseLoader):
for template_dir in template_dirs: for template_dir in template_dirs:
try: try:
yield safe_join(template_dir, template_name) yield safe_join(template_dir, template_name)
except UnicodeDecodeError: except SuspiciousFileOperation:
# The template dir name was a bytestring that wasn't valid UTF-8. # The joined path was located outside of this template_dir
raise # (it might be inside another one, so this isn't fatal).
except ValueError:
# The joined path was located outside of template_dir.
pass pass
def load_template_source(self, template_name, template_dirs=None): def load_template_source(self, template_name, template_dirs=None):

View File

@ -3,6 +3,7 @@ Wrapper for loading templates from the filesystem.
""" """
from django.conf import settings from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation
from django.template.base import TemplateDoesNotExist from django.template.base import TemplateDoesNotExist
from django.template.loader import BaseLoader from django.template.loader import BaseLoader
from django.utils._os import safe_join from django.utils._os import safe_join
@ -22,13 +23,9 @@ class Loader(BaseLoader):
for template_dir in template_dirs: for template_dir in template_dirs:
try: try:
yield safe_join(template_dir, template_name) yield safe_join(template_dir, template_name)
except UnicodeDecodeError: except SuspiciousFileOperation:
# The template dir name was a bytestring that wasn't valid UTF-8. # The joined path was located outside of this template_dir
raise # (it might be inside another one, so this isn't fatal).
except ValueError:
# The joined path was located outside of this particular
# template_dir (it might be inside another one, so this isn't
# fatal).
pass pass
def load_template_source(self, template_name, template_dirs=None): def load_template_source(self, template_name, template_dirs=None):

View File

@ -4,6 +4,7 @@ import sys
import tempfile import tempfile
from os.path import join, normcase, normpath, abspath, isabs, sep, dirname from os.path import join, normcase, normpath, abspath, isabs, sep, dirname
from django.core.exceptions import SuspiciousFileOperation
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils import six from django.utils import six
@ -77,8 +78,9 @@ def safe_join(base, *paths):
if (not normcase(final_path).startswith(normcase(base_path + sep)) and if (not normcase(final_path).startswith(normcase(base_path + sep)) and
normcase(final_path) != normcase(base_path) and normcase(final_path) != normcase(base_path) and
dirname(normcase(base_path)) != normcase(base_path)): dirname(normcase(base_path)) != normcase(base_path)):
raise ValueError('The joined path (%s) is located outside of the base ' raise SuspiciousFileOperation(
'path component (%s)' % (final_path, base_path)) 'The joined path ({}) is located outside of the base path '
'component ({})'.format(final_path, base_path))
return final_path return final_path

View File

@ -1,6 +1,7 @@
import os import os
import unittest import unittest
from django.core.exceptions import SuspiciousFileOperation
from django.utils._os import safe_join from django.utils._os import safe_join
@ -24,3 +25,7 @@ class SafeJoinTests(unittest.TestCase):
path, path,
os.path.sep, os.path.sep,
) )
def test_parent_path(self):
with self.assertRaises(SuspiciousFileOperation):
safe_join("/abc/", "../def")