mirror of
https://github.com/python/cpython.git
synced 2025-12-31 04:23:37 +00:00
[3.13] gh-142315: Don't pass the "real path" of Pdb script target to system functions (GH-142371) (#142498)
gh-142315: Don't pass the "real path" of Pdb script target to system functions (GH-142371
---------
(cherry picked from commit d716e3b2dd)
Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
This commit is contained in:
parent
bce4a20195
commit
fb5474726c
3 changed files with 88 additions and 9 deletions
36
Lib/pdb.py
36
Lib/pdb.py
|
|
@ -172,20 +172,38 @@ class _ExecutableTarget:
|
|||
|
||||
class _ScriptTarget(_ExecutableTarget):
|
||||
def __init__(self, target):
|
||||
self._target = os.path.realpath(target)
|
||||
self._check(target)
|
||||
self._target = self._safe_realpath(target)
|
||||
|
||||
if not os.path.exists(self._target):
|
||||
print(f'Error: {target} does not exist')
|
||||
sys.exit(1)
|
||||
if os.path.isdir(self._target):
|
||||
print(f'Error: {target} is a directory')
|
||||
sys.exit(1)
|
||||
|
||||
# If safe_path(-P) is not set, sys.path[0] is the directory
|
||||
# If PYTHONSAFEPATH (-P) is not set, sys.path[0] is the directory
|
||||
# of pdb, and we should replace it with the directory of the script
|
||||
if not sys.flags.safe_path:
|
||||
sys.path[0] = os.path.dirname(self._target)
|
||||
|
||||
@staticmethod
|
||||
def _check(target):
|
||||
"""
|
||||
Check that target is plausibly a script.
|
||||
"""
|
||||
if not os.path.exists(target):
|
||||
print(f'Error: {target} does not exist')
|
||||
sys.exit(1)
|
||||
if os.path.isdir(target):
|
||||
print(f'Error: {target} is a directory')
|
||||
sys.exit(1)
|
||||
|
||||
@staticmethod
|
||||
def _safe_realpath(path):
|
||||
"""
|
||||
Return the canonical path (realpath) if it is accessible from the userspace.
|
||||
Otherwise (for example, if the path is a symlink to an anonymous pipe),
|
||||
return the original path.
|
||||
|
||||
See GH-142315.
|
||||
"""
|
||||
realpath = os.path.realpath(path)
|
||||
return realpath if os.path.exists(realpath) else path
|
||||
|
||||
def __repr__(self):
|
||||
return self._target
|
||||
|
||||
|
|
|
|||
|
|
@ -3070,6 +3070,24 @@ def _assert_find_function(self, file_content, func_name, expected):
|
|||
self.assertEqual(
|
||||
expected, pdb.find_function(func_name, os_helper.TESTFN))
|
||||
|
||||
def _fd_dir_for_pipe_targets(self):
|
||||
"""Return a directory exposing live file descriptors, if any."""
|
||||
proc_fd = "/proc/self/fd"
|
||||
if os.path.isdir(proc_fd) and os.path.exists(os.path.join(proc_fd, '0')):
|
||||
return proc_fd
|
||||
|
||||
dev_fd = "/dev/fd"
|
||||
if os.path.isdir(dev_fd) and os.path.exists(os.path.join(dev_fd, '0')):
|
||||
if sys.platform.startswith("freebsd"):
|
||||
try:
|
||||
if os.stat("/dev").st_dev == os.stat(dev_fd).st_dev:
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
return dev_fd
|
||||
|
||||
return None
|
||||
|
||||
def test_find_function_empty_file(self):
|
||||
self._assert_find_function(b'', 'foo', None)
|
||||
|
||||
|
|
@ -3128,6 +3146,47 @@ def test_spec(self):
|
|||
stdout, _ = self.run_pdb_script(script, commands)
|
||||
self.assertIn('None', stdout)
|
||||
|
||||
def test_script_target_anonymous_pipe(self):
|
||||
"""
|
||||
_ScriptTarget doesn't fail on an anonymous pipe.
|
||||
|
||||
GH-142315
|
||||
"""
|
||||
fd_dir = self._fd_dir_for_pipe_targets()
|
||||
if fd_dir is None:
|
||||
self.skipTest('anonymous pipe targets require /proc/self/fd or /dev/fd')
|
||||
|
||||
read_fd, write_fd = os.pipe()
|
||||
|
||||
def safe_close(fd):
|
||||
try:
|
||||
os.close(fd)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.addCleanup(safe_close, read_fd)
|
||||
self.addCleanup(safe_close, write_fd)
|
||||
|
||||
pipe_path = os.path.join(fd_dir, str(read_fd))
|
||||
if not os.path.exists(pipe_path):
|
||||
self.skipTest('fd directory does not expose anonymous pipes')
|
||||
|
||||
script_source = 'marker = "via_pipe"\n'
|
||||
os.write(write_fd, script_source.encode('utf-8'))
|
||||
os.close(write_fd)
|
||||
|
||||
original_path0 = sys.path[0]
|
||||
self.addCleanup(sys.path.__setitem__, 0, original_path0)
|
||||
|
||||
target = pdb._ScriptTarget(pipe_path)
|
||||
code_text = target.code
|
||||
namespace = target.namespace
|
||||
exec(code_text, namespace)
|
||||
|
||||
self.assertEqual(namespace['marker'], 'via_pipe')
|
||||
self.assertEqual(namespace['__file__'], target.filename)
|
||||
self.assertIsNone(namespace['__spec__'])
|
||||
|
||||
def test_find_function_first_executable_line(self):
|
||||
code = textwrap.dedent("""\
|
||||
def foo(): pass
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Pdb can now run scripts from anonymous pipes used in process substitution.
|
||||
Patch by Bartosz Sławecki.
|
||||
Loading…
Add table
Add a link
Reference in a new issue