All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] [OE-core] [meta-openembedded] python3-django: fix CVE-2021-3281
@ 2021-02-18 20:47 Stefan Ghinea
  0 siblings, 0 replies; only message in thread
From: Stefan Ghinea @ 2021-02-18 20:47 UTC (permalink / raw)
  To: openembedded-core

In Django 2.2 before 2.2.18, 3.0 before 3.0.12, and 3.1 before 3.1.6, the
django.utils.archive.extract method (used by startapp --template and
startproject --template) allows directory traversal via an archive with
absolute paths or relative paths with dot segments.

References:
https://nvd.nist.gov/vuln/detail/CVE-2021-3281

Upstream patches:
https://github.com/django/django/commit/21e7622dec1f8612c85c2fc37fe8efbfd3311e37
https://github.com/django/django/commit/02e6592835b4559909aa3aaaf67988fef435f624

Signed-off-by: Stefan Ghinea <stefan.ghinea@windriver.com>
---
 .../python3-django-2.2.16/CVE-2021-3281.patch | 138 ++++++++++++++++++
 .../python3-django-3.1.1/CVE-2021-3281.patch  | 135 +++++++++++++++++
 .../python/python3-django_2.2.16.bb           |   2 +
 .../python/python3-django_3.1.1.bb            |   3 +
 4 files changed, 278 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-django-2.2.16/CVE-2021-3281.patch
 create mode 100644 meta-python/recipes-devtools/python/python3-django-3.1.1/CVE-2021-3281.patch

diff --git a/meta-python/recipes-devtools/python/python3-django-2.2.16/CVE-2021-3281.patch b/meta-python/recipes-devtools/python/python3-django-2.2.16/CVE-2021-3281.patch
new file mode 100644
index 000000000..36591ce6f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-django-2.2.16/CVE-2021-3281.patch
@@ -0,0 +1,138 @@
+From 21e7622dec1f8612c85c2fc37fe8efbfd3311e37 Mon Sep 17 00:00:00 2001
+From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
+Date: Fri, 22 Jan 2021 12:23:18 +0100
+Subject: [PATCH] Fixed CVE-2021-3281 -- Fixed potential directory-traversal
+ via archive.extract().
+
+Thanks Florian Apolloner, Shai Berger, and Simon Charette for reviews.
+
+Thanks Wang Baohua for the report.
+
+Backport of 05413afa8c18cdb978fcdf470e09f7a12b234a23 from master.
+
+Upstream-Status: Backport
+CVE: CVE-2021-3281
+
+Reference to upstream patch:
+[https://github.com/django/django/commit/21e7622dec1f8612c85c2fc37fe8efbfd3311e37]
+
+[SG: Adapted stable/2.2.x patch for 2.2.16]
+Signed-off-by: Stefan Ghinea <stefan.ghinea@windriver.com>
+---
+ django/utils/archive.py           | 17 ++++++++++++++---
+ docs/releases/2.2.16.txt          | 10 ++++++++++
+ tests/utils_tests/test_archive.py | 21 +++++++++++++++++++++
+ 3 files changed, 45 insertions(+), 3 deletions(-)
+
+diff --git a/django/utils/archive.py b/django/utils/archive.py
+index 5b9998f..f2f153a 100644
+--- a/django/utils/archive.py
++++ b/django/utils/archive.py
+@@ -27,6 +27,8 @@ import stat
+ import tarfile
+ import zipfile
+ 
++from django.core.exceptions import SuspiciousOperation
++
+ 
+ class ArchiveException(Exception):
+     """
+@@ -133,6 +135,13 @@ class BaseArchive:
+                 return False
+         return True
+ 
++    def target_filename(self, to_path, name):
++        target_path = os.path.abspath(to_path)
++        filename = os.path.abspath(os.path.join(target_path, name))
++        if not filename.startswith(target_path):
++            raise SuspiciousOperation("Archive contains invalid path: '%s'" % name)
++        return filename
++
+     def extract(self):
+         raise NotImplementedError('subclasses of BaseArchive must provide an extract() method')
+ 
+@@ -155,7 +164,7 @@ class TarArchive(BaseArchive):
+             name = member.name
+             if leading:
+                 name = self.split_leading_dir(name)[1]
+-            filename = os.path.join(to_path, name)
++            filename = self.target_filename(to_path, name)
+             if member.isdir():
+                 if filename and not os.path.exists(filename):
+                     os.makedirs(filename)
+@@ -198,11 +207,13 @@ class ZipArchive(BaseArchive):
+             info = self._archive.getinfo(name)
+             if leading:
+                 name = self.split_leading_dir(name)[1]
+-            filename = os.path.join(to_path, name)
++            if not name:
++                continue
++            filename = self.target_filename(to_path, name)
+             dirname = os.path.dirname(filename)
+             if dirname and not os.path.exists(dirname):
+                 os.makedirs(dirname)
+-            if filename.endswith(('/', '\\')):
++            if name.endswith(('/', '\\')):
+                 # A directory
+                 if not os.path.exists(filename):
+                     os.makedirs(filename)
+diff --git a/docs/releases/2.2.16.txt b/docs/releases/2.2.16.txt
+index 31231fb..94682a1 100644
+--- a/docs/releases/2.2.16.txt
++++ b/docs/releases/2.2.16.txt
+@@ -4,6 +4,16 @@ Django 2.2.16 release notes
+ 
+ *September 1, 2020*
+ 
++Backported from Django 2.2.18 a fix for a security issue.
++
++CVE-2021-3281: Potential directory-traversal via ``archive.extract()``
++======================================================================
++
++The ``django.utils.archive.extract()`` function, used by
++:option:`startapp --template` and :option:`startproject --template`, allowed
++directory-traversal via an archive with absolute paths or relative paths with
++dot segments.
++
+ Django 2.2.16 fixes two security issues and two data loss bugs in 2.2.15.
+ 
+ CVE-2020-24583: Incorrect permissions on intermediate-level directories on Python 3.7+
+diff --git a/tests/utils_tests/test_archive.py b/tests/utils_tests/test_archive.py
+index d58d211..ed7908d 100644
+--- a/tests/utils_tests/test_archive.py
++++ b/tests/utils_tests/test_archive.py
+@@ -5,6 +5,8 @@ import sys
+ import tempfile
+ import unittest
+ 
++from django.core.exceptions import SuspiciousOperation
++from django.test import SimpleTestCase
+ from django.utils.archive import Archive, extract
+ 
+ TEST_DIR = os.path.join(os.path.dirname(__file__), 'archives')
+@@ -87,3 +89,22 @@ class TestGzipTar(ArchiveTester, unittest.TestCase):
+ 
+ class TestBzip2Tar(ArchiveTester, unittest.TestCase):
+     archive = 'foobar.tar.bz2'
++
++
++class TestArchiveInvalid(SimpleTestCase):
++    def test_extract_function_traversal(self):
++        archives_dir = os.path.join(os.path.dirname(__file__), 'traversal_archives')
++        tests = [
++            ('traversal.tar', '..'),
++            ('traversal_absolute.tar', '/tmp/evil.py'),
++        ]
++        if sys.platform == 'win32':
++            tests += [
++                ('traversal_disk_win.tar', 'd:evil.py'),
++                ('traversal_disk_win.zip', 'd:evil.py'),
++            ]
++        msg = "Archive contains invalid path: '%s'"
++        for entry, invalid_path in tests:
++            with self.subTest(entry), tempfile.TemporaryDirectory() as tmpdir:
++                with self.assertRaisesMessage(SuspiciousOperation, msg % invalid_path):
++                    extract(os.path.join(archives_dir, entry), tmpdir)
+-- 
+2.17.1
+
diff --git a/meta-python/recipes-devtools/python/python3-django-3.1.1/CVE-2021-3281.patch b/meta-python/recipes-devtools/python/python3-django-3.1.1/CVE-2021-3281.patch
new file mode 100644
index 000000000..08b87a45a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-django-3.1.1/CVE-2021-3281.patch
@@ -0,0 +1,135 @@
+From 02e6592835b4559909aa3aaaf67988fef435f624 Mon Sep 17 00:00:00 2001
+From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
+Date: Fri, 22 Jan 2021 12:23:18 +0100
+Subject: [PATCH] Fixed CVE-2021-3281 -- Fixed potential directory-traversal
+ via archive.extract().
+
+Thanks Florian Apolloner, Shai Berger, and Simon Charette for reviews.
+
+Thanks Wang Baohua for the report.
+
+Backport of 05413afa8c18cdb978fcdf470e09f7a12b234a23 from master.
+
+Upstream-Status: Backport
+CVE: CVE-2021-3281
+
+Reference to upstream patch:
+[https://github.com/django/django/commit/02e6592835b4559909aa3aaaf67988fef435f624]
+
+[SG: Adapted stable/3.1.x patch for 3.1.1]
+Signed-off-by: Stefan Ghinea <stefan.ghinea@windriver.com>
+---
+ django/utils/archive.py           | 17 ++++++++++++++---
+ docs/releases/3.1.1.txt           | 10 ++++++++++
+ tests/utils_tests/test_archive.py | 21 +++++++++++++++++++++
+ 3 files changed, 45 insertions(+), 3 deletions(-)
+
+diff --git a/django/utils/archive.py b/django/utils/archive.py
+index 235809f..d5a0cf0 100644
+--- a/django/utils/archive.py
++++ b/django/utils/archive.py
+@@ -27,6 +27,8 @@ import stat
+ import tarfile
+ import zipfile
+ 
++from django.core.exceptions import SuspiciousOperation
++
+ 
+ class ArchiveException(Exception):
+     """
+@@ -133,6 +135,13 @@ class BaseArchive:
+                 return False
+         return True
+ 
++    def target_filename(self, to_path, name):
++        target_path = os.path.abspath(to_path)
++        filename = os.path.abspath(os.path.join(target_path, name))
++        if not filename.startswith(target_path):
++            raise SuspiciousOperation("Archive contains invalid path: '%s'" % name)
++        return filename
++
+     def extract(self):
+         raise NotImplementedError('subclasses of BaseArchive must provide an extract() method')
+ 
+@@ -155,7 +164,7 @@ class TarArchive(BaseArchive):
+             name = member.name
+             if leading:
+                 name = self.split_leading_dir(name)[1]
+-            filename = os.path.join(to_path, name)
++            filename = self.target_filename(to_path, name)
+             if member.isdir():
+                 if filename:
+                     os.makedirs(filename, exist_ok=True)
+@@ -198,8 +207,10 @@ class ZipArchive(BaseArchive):
+             info = self._archive.getinfo(name)
+             if leading:
+                 name = self.split_leading_dir(name)[1]
+-            filename = os.path.join(to_path, name)
+-            if filename.endswith(('/', '\\')):
++            if not name:
++                continue
++            filename = self.target_filename(to_path, name)
++            if name.endswith(('/', '\\')):
+                 # A directory
+                 os.makedirs(filename, exist_ok=True)
+             else:
+diff --git a/docs/releases/3.1.1.txt b/docs/releases/3.1.1.txt
+index 0a702e9..ed23464 100644
+--- a/docs/releases/3.1.1.txt
++++ b/docs/releases/3.1.1.txt
+@@ -4,6 +4,16 @@ Django 3.1.1 release notes
+ 
+ *September 1, 2020*
+ 
++Backported from Django 3.1.6 a fix for a security issue.
++
++CVE-2021-3281: Potential directory-traversal via ``archive.extract()``
++======================================================================
++
++The ``django.utils.archive.extract()`` function, used by
++:option:`startapp --template` and :option:`startproject --template`, allowed
++directory-traversal via an archive with absolute paths or relative paths with
++dot segments.
++
+ Django 3.1.1 fixes two security issues and several bugs in 3.1.
+ 
+ CVE-2020-24583: Incorrect permissions on intermediate-level directories on Python 3.7+
+diff --git a/tests/utils_tests/test_archive.py b/tests/utils_tests/test_archive.py
+index dc7c4b4..8fdf3ec 100644
+--- a/tests/utils_tests/test_archive.py
++++ b/tests/utils_tests/test_archive.py
+@@ -4,6 +4,8 @@ import sys
+ import tempfile
+ import unittest
+ 
++from django.core.exceptions import SuspiciousOperation
++from django.test import SimpleTestCase
+ from django.utils import archive
+ 
+ 
+@@ -45,3 +47,22 @@ class TestArchive(unittest.TestCase):
+                 # A file is readable even if permission data is missing.
+                 filepath = os.path.join(tmpdir, 'no_permissions')
+                 self.assertEqual(os.stat(filepath).st_mode & mask, 0o666 & ~umask)
++
++
++class TestArchiveInvalid(SimpleTestCase):
++    def test_extract_function_traversal(self):
++        archives_dir = os.path.join(os.path.dirname(__file__), 'traversal_archives')
++        tests = [
++            ('traversal.tar', '..'),
++            ('traversal_absolute.tar', '/tmp/evil.py'),
++        ]
++        if sys.platform == 'win32':
++            tests += [
++                ('traversal_disk_win.tar', 'd:evil.py'),
++                ('traversal_disk_win.zip', 'd:evil.py'),
++            ]
++        msg = "Archive contains invalid path: '%s'"
++        for entry, invalid_path in tests:
++            with self.subTest(entry), tempfile.TemporaryDirectory() as tmpdir:
++                with self.assertRaisesMessage(SuspiciousOperation, msg % invalid_path):
++                    archive.extract(os.path.join(archives_dir, entry), tmpdir)
+-- 
+2.17.1
+
diff --git a/meta-python/recipes-devtools/python/python3-django_2.2.16.bb b/meta-python/recipes-devtools/python/python3-django_2.2.16.bb
index 0715abbd4..2ad14f83e 100644
--- a/meta-python/recipes-devtools/python/python3-django_2.2.16.bb
+++ b/meta-python/recipes-devtools/python/python3-django_2.2.16.bb
@@ -7,3 +7,5 @@ SRC_URI[sha256sum] = "62cf45e5ee425c52e411c0742e641a6588b7e8af0d2c274a27940931b2
 RDEPENDS_${PN} += "\
     ${PYTHON_PN}-sqlparse \
 "
+SRC_URI += "file://CVE-2021-3281.patch \
+"
diff --git a/meta-python/recipes-devtools/python/python3-django_3.1.1.bb b/meta-python/recipes-devtools/python/python3-django_3.1.1.bb
index ed6513d0c..bd7261355 100644
--- a/meta-python/recipes-devtools/python/python3-django_3.1.1.bb
+++ b/meta-python/recipes-devtools/python/python3-django_3.1.1.bb
@@ -8,6 +8,9 @@ RDEPENDS_${PN} += "\
     ${PYTHON_PN}-sqlparse \
 "
 
+SRC_URI += "file://CVE-2021-3281.patch \
+"
+
 # Set DEFAULT_PREFERENCE so that the LTS version of django is built by
 # default. To build the 3.x branch, 
 # PREFERRED_VERSION_python3-django = "3.1.1" can be added to local.conf
-- 
2.17.1


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2021-02-18 20:47 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-02-18 20:47 [PATCH] [OE-core] [meta-openembedded] python3-django: fix CVE-2021-3281 Stefan Ghinea

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.