python/samba: Adjust tarfile extraction filter
[Samba.git] / python / samba / safe_tarfile.py
blob2136617847523f4734534b7042d23ad78082a9ee
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import os
19 import tarfile
20 from pathlib import Path
21 from tarfile import ExtractError, TarInfo, TarFile as UnsafeTarFile
24 class TarFile(UnsafeTarFile):
25 """This TarFile implementation is trying to ameliorate CVE-2007-4559,
26 where tarfile.TarFiles can step outside of the target directory
27 using '../../'.
28 """
30 try:
31 # New in version 3.11.4 (also has been backported)
32 # https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extraction_filter
33 # https://peps.python.org/pep-0706/
34 extraction_filter = staticmethod(tarfile.tar_filter)
35 except AttributeError:
36 def extract(self, member, path="", set_attrs=True, *,
37 numeric_owner=False):
38 self._safetarfile_check()
39 super().extract(member, path, set_attrs=set_attrs,
40 numeric_owner=numeric_owner)
42 def extractall(self, path, members=None, *, numeric_owner=False):
43 self._safetarfile_check()
44 super().extractall(path, members,
45 numeric_owner=numeric_owner)
47 def _safetarfile_check(self):
48 for tarinfo in self.__iter__():
49 if self._is_traversal_attempt(tarinfo=tarinfo):
50 raise ExtractError(
51 "Attempted directory traversal for "
52 f"member: {tarinfo.name}")
53 if self._is_unsafe_symlink(tarinfo=tarinfo):
54 raise ExtractError(
55 "Attempted directory traversal via symlink for "
56 f"member: {tarinfo.linkname}")
57 if self._is_unsafe_link(tarinfo=tarinfo):
58 raise ExtractError(
59 "Attempted directory traversal via link for "
60 f"member: {tarinfo.linkname}")
62 def _resolve_path(self, path):
63 return os.path.realpath(os.path.abspath(path))
65 def _is_path_in_dir(self, path, basedir):
66 return self._resolve_path(os.path.join(basedir,
67 path)).startswith(basedir)
69 def _is_traversal_attempt(self, tarinfo):
70 if (tarinfo.name.startswith(os.sep)
71 or ".." + os.sep in tarinfo.name):
72 return True
73 return False
75 def _is_unsafe_symlink(self, tarinfo):
76 if tarinfo.issym():
77 symlink_file = Path(
78 os.path.normpath(os.path.join(os.getcwd(),
79 tarinfo.linkname)))
80 if not self._is_path_in_dir(symlink_file, os.getcwd()):
81 return True
82 return False
84 def _is_unsafe_link(self, tarinfo):
85 if tarinfo.islnk():
86 link_file = Path(
87 os.path.normpath(os.path.join(os.getcwd(),
88 tarinfo.linkname)))
89 if not self._is_path_in_dir(link_file, os.getcwd()):
90 return True
91 return False
94 open = TarFile.open