Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ipykernel/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,15 @@ async def copyToGlobals(self, message):
src_var_name = message["arguments"]["srcVariableName"]
src_frame_id = message["arguments"]["srcFrameId"]

if not str.isidentifier(dst_var_name) or not str.isidentifier(src_var_name):
return {
"type": "response",
"request_seq": message["seq"],
"success": False,
"command": message["command"],
"message": "dstVariableName and srcVariableName must be valid identifiers",
}

expression = f"globals()['{dst_var_name}']"
seq = message["seq"]
return await self._forward_message(
Expand Down
18 changes: 18 additions & 0 deletions tests/test_debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,24 @@ def my_test():
assert global_var["value"] == local_var["value"] and global_var["type"] == local_var["type"] # noqa: PT018


def test_copy_to_globals_rejects_non_identifier(kernel_with_debug):
# A dstVariableName that is not a valid identifier would break out of the
# single-quoted globals()['...'] expression forwarded to setExpression.
reply = wait_for_debug_request(
kernel_with_debug,
"copyToGlobals",
{
"srcVariableName": "src",
"dstVariableName": "x'] or __import__('os').system('echo pwned') or globals()['y",
"srcFrameId": 0,
},
)
# The request is rejected locally instead of being forwarded to
# setExpression, so the response still carries the copyToGlobals command.
assert reply["success"] is False
assert reply["command"] == "copyToGlobals"


def test_debug_requests_sequential(kernel_with_debug):
# Issue https://github.com/ipython/ipykernel/issues/1412
# Control channel requests should be executed sequentially not concurrently.
Expand Down
Loading