fullpath = os.path.join("source_dir", name)
assert not os.path.exists(fullpath)
+
def test_restore_malicious_symlinks(self):
'''
Creates a full backup containing a symlink and a file of the same name.
assert not os.path.lexists(fullpath)
+class TarfileTest(BaseTest):
+ pwd = None
+
+ def setUp(self):
+ self.pwd = os.getcwd()
+ os.makedirs("backup_dir", exist_ok=True)
+
+ def tearDown(self):
+ '''
+ Remove temporary files created by unit tests and restore the API
+ functions in *os*.
+ '''
+ os.chdir(self.pwd)
+ shutil.rmtree("backup_dir")
+
+ def test_extract_malicious_symlinks_unlink(self):
+ '''
+ Test symlink mitigation: The destination must be deleted prior to
+ extraction.
+ '''
+ tar_path = os.path.join("backup_dir", "malicious-archive")
+
+ # add symlinks to existing archive
+
+ def add_symlink (a, name, dst):
+ l = tarfile.TarInfo(name)
+ l.type = tarfile.SYMTYPE
+ l.linkname = dst
+ a.addfile(l)
+
+ def add_file (a, name):
+ f = tarfile.TarInfo(name)
+ f.type = tarfile.REGTYPE
+ a.addfile(f)
+
+ # Add a symlink pointing to must-not-exist, then append a file
+ # object at the same path. The file must not end up at
+ # “must-not-exist” (the pointee) but at “not-as-symlink” (the
+ # pointer) that was unlinked prior to extraction.
+ testpath = "test/not-a-symlink"
+ testdst = "must-not-exist"
+
+ try:
+ with tarfile.open(tar_path, mode="w") as a:
+ add_symlink(a, testpath, testdst)
+ add_file(a, testpath)
+ except tarfile.ReadError as e:
+ if self.MODE == '#' or self.MODE.endswith ("gz"):
+ pass
+ else:
+ raise
+ except ValueError as e:
+ if self.MODE.startswith ('#'):
+ pass # O_APPEND of concat archives not feasible
+ else:
+ raise
+
+ def test_extract(dst, unlink):
+ with tarfile.open(tar_path, mode="r") as a:
+ os.makedirs(dst, exist_ok=True)
+ olddir = os.getcwd()
+ try:
+ os.chdir(dst)
+ a.extractall(unlink=unlink)
+ finally:
+ os.chdir(olddir)
+
+ fullpath = os.path.join(dst, testpath)
+ fulldst = os.path.join(dst, "test/%s" % testdst)
+
+ if unlink is True:
+ # Check whether the file was extracted. The object at the
+ # symlink location (source) must be the file. The must not
+ # be an object at the symlink destination.
+ assert not os.path.islink(fullpath)
+ assert not os.path.exists(fulldst)
+ else:
+ # Without unlink protection, the file must be found at the
+ # symlink destination with the symlink intact.
+ assert os.path.islink(fullpath)
+ assert os.path.exists(fulldst)
+
+
+ test_extract("test_dst_unlinked" , True)
+ test_extract("test_dst_symlinked", False)
+
+
def fsapi_access_true (self):
"""
Chicanery for testing improper use of the *os* module.