Read the whole bwrap info object before parsing the child pid
bwrap writes --info-fd JSON in several writes. A single os.read could
return only '{"child-pid": N' and fail with a JSON error, observed
reproducibly on railiance01 for the rebuilt owner runtime. Read until a
complete object or EOF within the existing 10 s bound; a truncated object
at EOF is still refused. 207 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 272244@bnt-lap001
Assistant-Session: c8962fa7-b290-47df-865f-403ddb6c77e9
This commit is contained in:
parent
444611586c
commit
221126cb77
2 changed files with 73 additions and 7 deletions
|
|
@ -113,14 +113,31 @@ class BwrapExtension(SandboxExtension):
|
|||
|
||||
@staticmethod
|
||||
def _read_child_pid(proc: subprocess.Popen, info_fd: int) -> int:
|
||||
ready, _, _ = select.select([info_fd], [], [], 10)
|
||||
if not ready:
|
||||
proc.kill()
|
||||
raise RuntimeError("timed out waiting for bwrap namespace child pid")
|
||||
raw = os.read(info_fd, 16_384)
|
||||
# bwrap writes the info object in several writes; one read can return
|
||||
# only its first line. Read until EOF or a complete object, bounded.
|
||||
deadline = time.monotonic() + 10
|
||||
raw = b""
|
||||
info = None
|
||||
while info is None:
|
||||
remaining = deadline - time.monotonic()
|
||||
ready, _, _ = select.select([info_fd], [], [], max(remaining, 0))
|
||||
if not ready:
|
||||
proc.kill()
|
||||
raise RuntimeError("timed out waiting for bwrap namespace child pid")
|
||||
chunk = os.read(info_fd, 16_384)
|
||||
raw += chunk
|
||||
if len(raw) > 16_384:
|
||||
proc.kill()
|
||||
raise RuntimeError("bwrap did not report a valid namespace child pid")
|
||||
try:
|
||||
info = json.loads(raw)
|
||||
except json.JSONDecodeError as exc:
|
||||
if not chunk:
|
||||
proc.kill()
|
||||
raise RuntimeError("bwrap did not report a valid namespace child pid") from exc
|
||||
try:
|
||||
child_pid = int(json.loads(raw)["child-pid"])
|
||||
except (json.JSONDecodeError, KeyError, TypeError, ValueError) as exc:
|
||||
child_pid = int(info["child-pid"])
|
||||
except (KeyError, TypeError, ValueError) as exc:
|
||||
proc.kill()
|
||||
raise RuntimeError("bwrap did not report a valid namespace child pid") from exc
|
||||
if child_pid <= 0:
|
||||
|
|
|
|||
|
|
@ -362,3 +362,52 @@ def test_runner_bounds_output_and_normalizes_timeout(tmp_path) -> None:
|
|||
assert result["stdout"] == "123"
|
||||
assert result["stderr"] == "abc"
|
||||
assert result["output_truncated"] is True
|
||||
|
||||
|
||||
class _FakeProc:
|
||||
def __init__(self) -> None:
|
||||
self.killed = False
|
||||
|
||||
def kill(self) -> None:
|
||||
self.killed = True
|
||||
|
||||
|
||||
def test_read_child_pid_accepts_info_split_across_writes() -> None:
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
read_fd, write_fd = os.pipe()
|
||||
|
||||
def writer() -> None:
|
||||
os.write(write_fd, b'{\n "child-pid": 1234567')
|
||||
time.sleep(0.05)
|
||||
os.write(write_fd, b',\n "cgroup-namespace": 4026531835\n}\n')
|
||||
os.close(write_fd)
|
||||
|
||||
thread = threading.Thread(target=writer)
|
||||
thread.start()
|
||||
try:
|
||||
proc = _FakeProc()
|
||||
assert BwrapExtension._read_child_pid(proc, read_fd) == 1234567
|
||||
assert not proc.killed
|
||||
finally:
|
||||
thread.join()
|
||||
os.close(read_fd)
|
||||
|
||||
|
||||
def test_read_child_pid_refuses_truncated_info_at_eof() -> None:
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
read_fd, write_fd = os.pipe()
|
||||
os.write(write_fd, b'{\n "child-pid": 1234567')
|
||||
os.close(write_fd)
|
||||
proc = _FakeProc()
|
||||
try:
|
||||
with pytest.raises(RuntimeError, match="valid namespace child pid"):
|
||||
BwrapExtension._read_child_pid(proc, read_fd)
|
||||
assert proc.killed
|
||||
finally:
|
||||
os.close(read_fd)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue