mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
[3.9] gh-135034: Normalize link targets in tarfile, add os.path.realpath(strict='allow_missing') (GH-135037) (GH-135084)
Addresses CVEs 2024-12718, 2025-4138, 2025-4330, and 2025-4517.
(cherry picked from commit 3612d8f517)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
Co-authored-by: Petr Viktorin <encukou@gmail.com>
Co-authored-by: Seth Michael Larson <seth@python.org>
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
24eaf53bc6
commit
dd8f187d07
11 changed files with 949 additions and 137 deletions
|
|
@ -1,8 +1,10 @@
|
|||
import ntpath
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
import warnings
|
||||
from ntpath import ALLOW_MISSING
|
||||
from test.support import TestFailed, FakePath
|
||||
from test import support, test_genericpath
|
||||
from tempfile import TemporaryFile
|
||||
|
|
@ -72,6 +74,27 @@ def tester(fn, wantResult):
|
|||
%(str(fn), str(wantResult), repr(gotResult)))
|
||||
|
||||
|
||||
def _parameterize(*parameters):
|
||||
"""Simplistic decorator to parametrize a test
|
||||
|
||||
Runs the decorated test multiple times in subTest, with a value from
|
||||
'parameters' passed as an extra positional argument.
|
||||
Calls doCleanups() after each run.
|
||||
|
||||
Not for general use. Intended to avoid indenting for easier backports.
|
||||
|
||||
See https://discuss.python.org/t/91827 for discussing generalizations.
|
||||
"""
|
||||
def _parametrize_decorator(func):
|
||||
def _parameterized(self, *args, **kwargs):
|
||||
for parameter in parameters:
|
||||
with self.subTest(parameter):
|
||||
func(self, *args, parameter, **kwargs)
|
||||
self.doCleanups()
|
||||
return _parameterized
|
||||
return _parametrize_decorator
|
||||
|
||||
|
||||
class NtpathTestCase(unittest.TestCase):
|
||||
def assertPathEqual(self, path1, path2):
|
||||
if path1 == path2 or _norm(path1) == _norm(path2):
|
||||
|
|
@ -242,6 +265,27 @@ def test_realpath_curdir(self):
|
|||
tester("ntpath.realpath('.\\.')", expected)
|
||||
tester("ntpath.realpath('\\'.join(['.'] * 100))", expected)
|
||||
|
||||
def test_realpath_curdir_strict(self):
|
||||
expected = ntpath.normpath(os.getcwd())
|
||||
tester("ntpath.realpath('.', strict=True)", expected)
|
||||
tester("ntpath.realpath('./.', strict=True)", expected)
|
||||
tester("ntpath.realpath('/'.join(['.'] * 100), strict=True)", expected)
|
||||
tester("ntpath.realpath('.\\.', strict=True)", expected)
|
||||
tester("ntpath.realpath('\\'.join(['.'] * 100), strict=True)", expected)
|
||||
|
||||
def test_realpath_curdir_missing_ok(self):
|
||||
expected = ntpath.normpath(os.getcwd())
|
||||
tester("ntpath.realpath('.', strict=ALLOW_MISSING)",
|
||||
expected)
|
||||
tester("ntpath.realpath('./.', strict=ALLOW_MISSING)",
|
||||
expected)
|
||||
tester("ntpath.realpath('/'.join(['.'] * 100), strict=ALLOW_MISSING)",
|
||||
expected)
|
||||
tester("ntpath.realpath('.\\.', strict=ALLOW_MISSING)",
|
||||
expected)
|
||||
tester("ntpath.realpath('\\'.join(['.'] * 100), strict=ALLOW_MISSING)",
|
||||
expected)
|
||||
|
||||
def test_realpath_pardir(self):
|
||||
expected = ntpath.normpath(os.getcwd())
|
||||
tester("ntpath.realpath('..')", ntpath.dirname(expected))
|
||||
|
|
@ -254,17 +298,43 @@ def test_realpath_pardir(self):
|
|||
tester("ntpath.realpath('\\'.join(['..'] * 50))",
|
||||
ntpath.splitdrive(expected)[0] + '\\')
|
||||
|
||||
def test_realpath_pardir_strict(self):
|
||||
expected = ntpath.normpath(os.getcwd())
|
||||
tester("ntpath.realpath('..', strict=True)", ntpath.dirname(expected))
|
||||
tester("ntpath.realpath('../..', strict=True)",
|
||||
ntpath.dirname(ntpath.dirname(expected)))
|
||||
tester("ntpath.realpath('/'.join(['..'] * 50), strict=True)",
|
||||
ntpath.splitdrive(expected)[0] + '\\')
|
||||
tester("ntpath.realpath('..\\..', strict=True)",
|
||||
ntpath.dirname(ntpath.dirname(expected)))
|
||||
tester("ntpath.realpath('\\'.join(['..'] * 50), strict=True)",
|
||||
ntpath.splitdrive(expected)[0] + '\\')
|
||||
|
||||
def test_realpath_pardir_missing_ok(self):
|
||||
expected = ntpath.normpath(os.getcwd())
|
||||
tester("ntpath.realpath('..', strict=ALLOW_MISSING)",
|
||||
ntpath.dirname(expected))
|
||||
tester("ntpath.realpath('../..', strict=ALLOW_MISSING)",
|
||||
ntpath.dirname(ntpath.dirname(expected)))
|
||||
tester("ntpath.realpath('/'.join(['..'] * 50), strict=ALLOW_MISSING)",
|
||||
ntpath.splitdrive(expected)[0] + '\\')
|
||||
tester("ntpath.realpath('..\\..', strict=ALLOW_MISSING)",
|
||||
ntpath.dirname(ntpath.dirname(expected)))
|
||||
tester("ntpath.realpath('\\'.join(['..'] * 50), strict=ALLOW_MISSING)",
|
||||
ntpath.splitdrive(expected)[0] + '\\')
|
||||
|
||||
@support.skip_unless_symlink
|
||||
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
|
||||
def test_realpath_basic(self):
|
||||
@_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
|
||||
def test_realpath_basic(self, kwargs):
|
||||
ABSTFN = ntpath.abspath(support.TESTFN)
|
||||
open(ABSTFN, "wb").close()
|
||||
self.addCleanup(support.unlink, ABSTFN)
|
||||
self.addCleanup(support.unlink, ABSTFN + "1")
|
||||
|
||||
os.symlink(ABSTFN, ABSTFN + "1")
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "1"), ABSTFN)
|
||||
self.assertPathEqual(ntpath.realpath(os.fsencode(ABSTFN + "1")),
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "1", **kwargs), ABSTFN)
|
||||
self.assertPathEqual(ntpath.realpath(os.fsencode(ABSTFN + "1"), **kwargs),
|
||||
os.fsencode(ABSTFN))
|
||||
|
||||
@support.skip_unless_symlink
|
||||
|
|
@ -280,14 +350,15 @@ def test_realpath_strict(self):
|
|||
|
||||
@support.skip_unless_symlink
|
||||
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
|
||||
def test_realpath_relative(self):
|
||||
@_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
|
||||
def test_realpath_relative(self, kwargs):
|
||||
ABSTFN = ntpath.abspath(support.TESTFN)
|
||||
open(ABSTFN, "wb").close()
|
||||
self.addCleanup(support.unlink, ABSTFN)
|
||||
self.addCleanup(support.unlink, ABSTFN + "1")
|
||||
|
||||
os.symlink(ABSTFN, ntpath.relpath(ABSTFN + "1"))
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "1"), ABSTFN)
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "1", **kwargs), ABSTFN)
|
||||
|
||||
@support.skip_unless_symlink
|
||||
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
|
||||
|
|
@ -439,7 +510,62 @@ def test_realpath_symlink_loops_strict(self):
|
|||
|
||||
@support.skip_unless_symlink
|
||||
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
|
||||
def test_realpath_symlink_prefix(self):
|
||||
def test_realpath_symlink_loops_raise(self):
|
||||
# Symlink loops raise OSError in ALLOW_MISSING mode
|
||||
ABSTFN = ntpath.abspath(support.TESTFN)
|
||||
self.addCleanup(support.unlink, ABSTFN)
|
||||
self.addCleanup(support.unlink, ABSTFN + "1")
|
||||
self.addCleanup(support.unlink, ABSTFN + "2")
|
||||
self.addCleanup(support.unlink, ABSTFN + "y")
|
||||
self.addCleanup(support.unlink, ABSTFN + "c")
|
||||
self.addCleanup(support.unlink, ABSTFN + "a")
|
||||
self.addCleanup(support.unlink, ABSTFN + "x")
|
||||
|
||||
os.symlink(ABSTFN, ABSTFN)
|
||||
self.assertRaises(OSError, ntpath.realpath, ABSTFN, strict=ALLOW_MISSING)
|
||||
|
||||
os.symlink(ABSTFN + "1", ABSTFN + "2")
|
||||
os.symlink(ABSTFN + "2", ABSTFN + "1")
|
||||
self.assertRaises(OSError, ntpath.realpath, ABSTFN + "1",
|
||||
strict=ALLOW_MISSING)
|
||||
self.assertRaises(OSError, ntpath.realpath, ABSTFN + "2",
|
||||
strict=ALLOW_MISSING)
|
||||
self.assertRaises(OSError, ntpath.realpath, ABSTFN + "1\\x",
|
||||
strict=ALLOW_MISSING)
|
||||
|
||||
# Windows eliminates '..' components before resolving links;
|
||||
# realpath is not expected to raise if this removes the loop.
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "1\\.."),
|
||||
ntpath.dirname(ABSTFN))
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "1\\..\\x"),
|
||||
ntpath.dirname(ABSTFN) + "\\x")
|
||||
|
||||
os.symlink(ABSTFN + "x", ABSTFN + "y")
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "1\\..\\"
|
||||
+ ntpath.basename(ABSTFN) + "y"),
|
||||
ABSTFN + "x")
|
||||
self.assertRaises(
|
||||
OSError, ntpath.realpath,
|
||||
ABSTFN + "1\\..\\" + ntpath.basename(ABSTFN) + "1",
|
||||
strict=ALLOW_MISSING)
|
||||
|
||||
os.symlink(ntpath.basename(ABSTFN) + "a\\b", ABSTFN + "a")
|
||||
self.assertRaises(OSError, ntpath.realpath, ABSTFN + "a",
|
||||
strict=ALLOW_MISSING)
|
||||
|
||||
os.symlink("..\\" + ntpath.basename(ntpath.dirname(ABSTFN))
|
||||
+ "\\" + ntpath.basename(ABSTFN) + "c", ABSTFN + "c")
|
||||
self.assertRaises(OSError, ntpath.realpath, ABSTFN + "c",
|
||||
strict=ALLOW_MISSING)
|
||||
|
||||
# Test using relative path as well.
|
||||
self.assertRaises(OSError, ntpath.realpath, ntpath.basename(ABSTFN),
|
||||
strict=ALLOW_MISSING)
|
||||
|
||||
@support.skip_unless_symlink
|
||||
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
|
||||
@_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
|
||||
def test_realpath_symlink_prefix(self, kwargs):
|
||||
ABSTFN = ntpath.abspath(support.TESTFN)
|
||||
self.addCleanup(support.unlink, ABSTFN + "3")
|
||||
self.addCleanup(support.unlink, "\\\\?\\" + ABSTFN + "3.")
|
||||
|
|
@ -454,9 +580,9 @@ def test_realpath_symlink_prefix(self):
|
|||
f.write(b'1')
|
||||
os.symlink("\\\\?\\" + ABSTFN + "3.", ABSTFN + "3.link")
|
||||
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "3link"),
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "3link", **kwargs),
|
||||
ABSTFN + "3")
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "3.link"),
|
||||
self.assertPathEqual(ntpath.realpath(ABSTFN + "3.link", **kwargs),
|
||||
"\\\\?\\" + ABSTFN + "3.")
|
||||
|
||||
# Resolved paths should be usable to open target files
|
||||
|
|
@ -466,14 +592,17 @@ def test_realpath_symlink_prefix(self):
|
|||
self.assertEqual(f.read(), b'1')
|
||||
|
||||
# When the prefix is included, it is not stripped
|
||||
self.assertPathEqual(ntpath.realpath("\\\\?\\" + ABSTFN + "3link"),
|
||||
self.assertPathEqual(ntpath.realpath("\\\\?\\" + ABSTFN + "3link", **kwargs),
|
||||
"\\\\?\\" + ABSTFN + "3")
|
||||
self.assertPathEqual(ntpath.realpath("\\\\?\\" + ABSTFN + "3.link"),
|
||||
self.assertPathEqual(ntpath.realpath("\\\\?\\" + ABSTFN + "3.link", **kwargs),
|
||||
"\\\\?\\" + ABSTFN + "3.")
|
||||
|
||||
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
|
||||
def test_realpath_nul(self):
|
||||
tester("ntpath.realpath('NUL')", r'\\.\NUL')
|
||||
tester("ntpath.realpath('NUL', strict=False)", r'\\.\NUL')
|
||||
tester("ntpath.realpath('NUL', strict=True)", r'\\.\NUL')
|
||||
tester("ntpath.realpath('NUL', strict=ALLOW_MISSING)", r'\\.\NUL')
|
||||
|
||||
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
|
||||
@unittest.skipUnless(HAVE_GETSHORTPATHNAME, 'need _getshortpathname')
|
||||
|
|
@ -497,12 +626,20 @@ def test_realpath_cwd(self):
|
|||
|
||||
self.assertPathEqual(test_file_long, ntpath.realpath(test_file_short))
|
||||
|
||||
with support.change_cwd(test_dir_long):
|
||||
self.assertPathEqual(test_file_long, ntpath.realpath("file.txt"))
|
||||
with support.change_cwd(test_dir_long.lower()):
|
||||
self.assertPathEqual(test_file_long, ntpath.realpath("file.txt"))
|
||||
with support.change_cwd(test_dir_short):
|
||||
self.assertPathEqual(test_file_long, ntpath.realpath("file.txt"))
|
||||
for kwargs in {}, {'strict': True}, {'strict': ALLOW_MISSING}:
|
||||
with self.subTest(**kwargs):
|
||||
with support.change_cwd(test_dir_long):
|
||||
self.assertPathEqual(
|
||||
test_file_long,
|
||||
ntpath.realpath("file.txt", **kwargs))
|
||||
with support.change_cwd(test_dir_long.lower()):
|
||||
self.assertPathEqual(
|
||||
test_file_long,
|
||||
ntpath.realpath("file.txt", **kwargs))
|
||||
with support.change_cwd(test_dir_short):
|
||||
self.assertPathEqual(
|
||||
test_file_long,
|
||||
ntpath.realpath("file.txt", **kwargs))
|
||||
|
||||
def test_expandvars(self):
|
||||
with support.EnvironmentVarGuard() as env:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue