diff -r 031fc0231f3d Lib/tarfile.py --- a/Lib/tarfile.py Thu Jan 15 22:53:21 2015 +0100 +++ b/Lib/tarfile.py Mon Jan 19 10:25:16 2015 +0100 @@ -2177,6 +2177,10 @@ try: # For systems that support symbolic and hard links. if tarinfo.issym(): + # a link with that name exists already, we need + # to replace it with our version (see #23228) + if os.path.lexists(targetpath): + os.remove(targetpath) os.symlink(tarinfo.linkname, targetpath) else: # See extract(). diff -r 031fc0231f3d Lib/test/test_tarfile.py --- a/Lib/test/test_tarfile.py Thu Jan 15 22:53:21 2015 +0100 +++ b/Lib/test/test_tarfile.py Mon Jan 19 10:25:16 2015 +0100 @@ -5,6 +5,7 @@ import unittest import tarfile +import tempfile from test import support, script_helper @@ -2167,6 +2168,66 @@ self._test_partial_input("r:bz2") +import contextlib +@support.skip_unless_symlink +class TestTarfileLinkExtract(unittest.TestCase): + """Regression test for #23228""" + + def setUp(self): + self.tempdirs = [tempfile.mkdtemp()] + self.cwd = os.getcwd() + os.chdir(self.tempdirs[0]) + + def tearDown(self): + os.chdir(self.cwd) + for tmpdir in self.tempdirs: + support.rmtree(tmpdir) + + @contextlib.contextmanager + def tempdir(self): + tmp = tempfile.mkdtemp() + self.tempdirs.append(tmp) + cwd = os.getcwd() + try: + os.chdir(tmp) + yield + finally: + os.chdir(cwd) + + def _make_tarfile_with_symlink_cycle(self): + with self.tempdir(): + os.symlink("run", "run") + tarname = os.path.join(os.getcwd(), "lala.tar") + with tarfile.open(tarname, "w") as w: + w.add(".") + return tarname + + def test_unpack_cycle(self): + # cycle in tar and on disk + os.symlink("run", "run") + with tarfile.open(self._make_tarfile_with_symlink_cycle()) as tar: + tar.extractall(".") + self.assertEqual(os.readlink("run"), "run") + + def _make_tarfile_with_broken_symlink(self): + with self.tempdir(): + os.makedirs("var") + os.symlink("/run", "var/run") + tarname = os.path.join(os.getcwd(), "lala.tar") + with tarfile.open(tarname, "w") as w: + w.add(".") + return tarname + + def test_tarfile_crash(self): + os.makedirs("var") + os.symlink("/run", "./var/run") + tarname = self._make_tarfile_with_broken_symlink() + with tarfile.open(tarname, "r") as r: + r.extractall(".") + self.assertTrue(os.path.lexists("./var/run")) + self.assertEqual(os.readlink("./var/run"), "/run") + + def setUpModule(): support.unlink(TEMPDIR) os.makedirs(TEMPDIR)