Read the whole bwrap info object before parsing the child pid
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s

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:
tegwick 2026-09-22 02:34:08 +02:00
parent 444611586c
commit 221126cb77
2 changed files with 73 additions and 7 deletions

View file

@ -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:

View file

@ -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)