Skip to content

Commit 382620c

Browse files
authored
don't resolve the kernel's own connection file through the cwd (#1576)
init_connection_file makes up kernel-<pid>.json when no connection file was passed on the command line, then hands that invented name to filefind over the current directory and connection_dir, so a file of that name sitting in the kernel's working directory wins and load_connection_file adopts what is in it, HMAC key and all five ports included. The launches that reach this are the ones without -f: ipython kernel, python -m ipykernel_launcher on its own, and every embed_kernel caller, and their working directory is frequently a checked-out project or a shared directory nobody vetted. Whoever wrote that file then holds the key the kernel signs and verifies with, which is all of the message authentication there is, so they can hand it an execute_request. I noticed it while following where the connection file actually gets created and saw that the create branch is the fallback rather than the default. Planting the file in a temp directory and calling init_connection_file confirmed it, session.key came back as the planted string and shell_port as the planted 51111. get_connection_file repeats the same cwd-first lookup, so even once the kernel owns its file in connection_dir, %connect_info and connect_qtconsole resolve the planted basename ahead of the real one and hand that out instead. The fix creates the file straight away when the name was ours to invent instead of looking it up, and puts connection_dir ahead of the working directory in get_connection_file; an explicit relative -f still resolves against the working directory the way it always did, with a regression beside the existing connection file tests in tests/test_kernelapp.py.
2 parents c72d316 + 1565319 commit 382620c

3 files changed

Lines changed: 43 additions & 1 deletion

File tree

‎ipykernel/connect.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ def get_connection_file(app: IPKernelApp | None = None) -> str:
3434
raise RuntimeError(msg)
3535

3636
app = IPKernelApp.instance()
37-
return filefind(app.connection_file, [".", app.connection_dir])
37+
# The kernel's own file lives in connection_dir; the working directory is
38+
# only a fallback for a relative name that was passed on the command line.
39+
return filefind(app.connection_file, [app.connection_dir, "."])
3840

3941

4042
def _find_connection_file(connection_file):

‎ipykernel/kernelapp.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,13 @@ def cleanup_connection_file(self):
331331
def init_connection_file(self):
332332
"""Initialize our connection file."""
333333
if not self.connection_file:
334+
# Nobody gave us a name, so this file is ours to create. Don't go
335+
# looking for the name we just invented: a file carrying it in the
336+
# working directory is not this kernel's connection info.
334337
self.connection_file = "kernel-%s.json" % os.getpid()
338+
Path(self.abs_connection_file).parent.mkdir(mode=0o700, exist_ok=True, parents=True)
339+
atexit.register(self.cleanup_connection_file)
340+
return
335341
try:
336342
self.connection_file = filefind(self.connection_file, [".", self.connection_dir])
337343
except OSError:

‎tests/test_kernelapp.py‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from jupyter_core.paths import secure_write
1111
from traitlets.config.loader import Config
1212

13+
from ipykernel.connect import get_connection_file
1314
from ipykernel.kernelapp import IPKernelApp
1415

1516
from .conftest import MockKernel
@@ -181,3 +182,36 @@ def test_init_sockets_tcp_without_curve_logs_warning():
181182
assert any("Kernel is running over TCP without encryption" in m for m in messages), (
182183
"Expected a warning about missing encryption when transport=tcp without curve keys"
183184
)
185+
186+
187+
def test_default_connection_file_ignores_working_directory():
188+
planted = {
189+
"ip": "127.0.0.1",
190+
"transport": "tcp",
191+
"signature_scheme": "hmac-sha256",
192+
"key": "planted-key",
193+
"shell_port": 51111,
194+
"iopub_port": 51112,
195+
"stdin_port": 51113,
196+
"control_port": 51114,
197+
"hb_port": 51115,
198+
}
199+
basename = "kernel-%s.json" % os.getpid()
200+
with TemporaryWorkingDirectory() as d:
201+
with open(os.path.join(d, basename), "w") as f:
202+
json.dump(planted, f)
203+
204+
app = IPKernelApp(connection_dir=os.path.join(d, "runtime"))
205+
app.init_connection_file()
206+
207+
# the kernel owns the file of that name in connection_dir and took
208+
# nothing from the one sitting in the working directory
209+
assert app.connection_file == basename
210+
assert app.abs_connection_file == os.path.join(app.connection_dir, basename)
211+
assert app.session.key != b"planted-key"
212+
assert app.shell_port == 0
213+
214+
# and that is the file handed out to other clients
215+
with open(app.abs_connection_file, "w") as f:
216+
json.dump({"key": "real-key"}, f)
217+
assert get_connection_file(app) == app.abs_connection_file

0 commit comments

Comments
 (0)