-
Notifications
You must be signed in to change notification settings - Fork 195
Fix PEP 768 code injection SyntaxError when temp path contains backslashes #2038
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ab6254b
9901bd9
3746bda
a3b90e5
843c0d1
4fc7a19
3e28555
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -248,3 +248,64 @@ def test_script_parent_pid_with_listen_failure(cli): | |
| cli(["--listen", "8888", "--parent-session-pid", "1234", "spam.py"]) | ||
|
|
||
| assert "--parent-session-pid requires --connect" in str(ex.value) | ||
|
|
||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot [verified]
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deleted
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in |
||
| def test_pep_768_remote_exec_called_with_backslash_path(): | ||
| """Test that attach_to_pid() calls sys.remote_exec and writes valid Python | ||
| to the temp file even when the temp path contains backslashes (Windows).""" | ||
| import contextlib | ||
| from debugpy.server import cli | ||
|
|
||
| mock_windows_tmp_path = r"C:\Users\test\AppData\Local\Temp\tmp0_vuee4s" | ||
| pid = os.getpid() | ||
|
|
||
| # A fake file object that captures writes via a side_effect list. | ||
| # Using MagicMock avoids the BytesIO.close() issue where getvalue() | ||
| # raises ValueError on a closed buffer. | ||
| written_chunks = [] | ||
| fake_file = mock.MagicMock() | ||
| fake_file.name = mock_windows_tmp_path | ||
| fake_file.write.side_effect = written_chunks.append | ||
|
|
||
| fake_file_cm = mock.MagicMock() | ||
| fake_file_cm.__enter__ = mock.Mock(return_value=fake_file) | ||
| fake_file_cm.__exit__ = mock.Mock(return_value=False) | ||
|
|
||
| # Configure cli.options with the minimum fields attach_to_pid() needs. | ||
| original_options = { | ||
| attr: getattr(cli.options, attr) | ||
| for attr in ("mode", "address", "wait_for_client", "log_to", | ||
| "adapter_access_token", "disable_sys_remote_exec", "target") | ||
| } | ||
| try: | ||
| cli.options.mode = "connect" | ||
| cli.options.address = ("127.0.0.1", 5678) | ||
| cli.options.wait_for_client = False | ||
| cli.options.log_to = None | ||
| cli.options.adapter_access_token = None | ||
| cli.options.disable_sys_remote_exec = False | ||
| cli.options.target = pid | ||
|
|
||
| with contextlib.ExitStack() as stack: | ||
| stack.enter_context( | ||
| mock.patch("tempfile.NamedTemporaryFile", return_value=fake_file_cm) | ||
| ) | ||
| mock_remote_exec = stack.enter_context( | ||
| mock.patch.object(sys, "remote_exec", create=True) | ||
| ) | ||
| cli.attach_to_pid() | ||
|
|
||
| # sys.remote_exec must have been called with the original (backslash) path | ||
| # because that is the actual path created on disk. | ||
| mock_remote_exec.assert_called_once_with(pid, mock_windows_tmp_path) | ||
|
|
||
| # The Python code written to the temp file must be syntactically valid. | ||
| injected_code = b"".join(written_chunks).decode() | ||
| compile(injected_code, "<string>", "exec") | ||
|
|
||
| # The os.remove() call inside the injected code must use the repr of the path | ||
| # so that it is a valid Python string literal on all platforms. | ||
| assert "import os;os.remove({});".format(repr(mock_windows_tmp_path)) in injected_code | ||
| finally: | ||
| for attr, value in original_options.items(): | ||
| setattr(cli.options, attr, value) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot This
.replace("\\", "/")patches the symptom rather than the root cause and is the lone raw-string outlier in this function —script_dir/setupalready use the escaping-immuneencode = list(bytearray(...))+codecs.utf_8_decodepattern a few lines above. The Skeptic verified on CPython 3.11.9 that.replacestill leaves a quote gap (a"in the path raisesSyntaxError) and a POSIX regression (a backslash path retargetsos.removeto a different file thansys.remote_execreceives). Both are unreachable today giventempfile's name alphabet, butrepr(tmp_file_path)or reusing the existing encode/decode pattern closes both simultaneously and restores a single representation oftmp_file_path.[verified]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switched to
{tmp_file_path!r}(per PR #2039) — this produces a valid Python string literal for any path without modifying it, closing both the Windows backslash and POSIX quote/backslash-filename gaps.