https://github.com/python/cpython/commit/fb5474726cceae6c05aad5076b50fbd586527558
commit: fb5474726cceae6c05aad5076b50fbd586527558
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: jaraco <[email protected]>
date: 2025-12-16T10:53:09-05:00
summary:

[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 d716e3b2dd33de27aaa31e8204723195c5ba706c)

Co-authored-by: Bartosz SÅ‚awecki <[email protected]>
Co-authored-by: Jason R. Coombs <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-12-07-02-36-24.gh-issue-142315.02o5E_.rst
M Lib/pdb.py
M Lib/test/test_pdb.py

diff --git a/Lib/pdb.py b/Lib/pdb.py
index 063b12cc856c21..5c9be23e326567 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -172,19 +172,37 @@ 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):
+        # 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(self._target):
+        if os.path.isdir(target):
             print(f'Error: {target} is a directory')
             sys.exit(1)
 
-        # If safe_path(-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 _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
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index dd31649ab262c3..044f6e22da08a1 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -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
diff --git 
a/Misc/NEWS.d/next/Library/2025-12-07-02-36-24.gh-issue-142315.02o5E_.rst 
b/Misc/NEWS.d/next/Library/2025-12-07-02-36-24.gh-issue-142315.02o5E_.rst
new file mode 100644
index 00000000000000..e9c5ba3c0639f2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-12-07-02-36-24.gh-issue-142315.02o5E_.rst
@@ -0,0 +1,2 @@
+Pdb can now run scripts from anonymous pipes used in process substitution.
+Patch by Bartosz Sławecki.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to