"""A stalled unprivileged stdout reader must not retain the registry lock. The subprocess disables installation/caller/crypto operations and uses only its own temporary directory. No sudo, host files, accounts or credentials are used. """ import array import fcntl import os from pathlib import Path import select import subprocess import sys import tempfile import termios import time import unittest from framebridge_expanded import credential_helper CHILD = r""" import importlib.util import sys from pathlib import Path from types import SimpleNamespace spec = importlib.util.spec_from_file_location("pipe_canary_helper", sys.argv[1]) helper = importlib.util.module_from_spec(spec) spec.loader.exec_module(helper) helper.ROOT = Path(sys.argv[2]) helper._installation = lambda: None helper._prepare_root = lambda: None helper._caller = lambda: SimpleNamespace(pw_uid=12345, pw_name="synthetic", pw_dir="/synthetic-only") helper.decrypt = lambda *args: b"X" * 16384 # The only file is our own temporary lock; simulate its expected root metadata. actual_fstat = helper.os.fstat helper.os.fstat = lambda fd: SimpleNamespace(st_mode=actual_fstat(fd).st_mode, st_uid=0, st_nlink=actual_fstat(fd).st_nlink) raise SystemExit(helper.main(["decrypt", "a" * 64, "b" * 64])) """ class CredentialHelperPipeTests(unittest.TestCase): @unittest.skipUnless(hasattr(fcntl, "F_SETPIPE_SZ"), "Linux pipe-size controls required") def test_stalled_stdout_reader_does_not_hold_global_registry_lock(self): with tempfile.TemporaryDirectory(prefix="framebridge-pipe-canary-") as directory: read_fd, write_fd = os.pipe() child = None lock_fd = None try: capacity = fcntl.fcntl(write_fd, fcntl.F_SETPIPE_SZ, 4096) child = subprocess.Popen([sys.executable, "-I", "-S", "-c", CHILD, str(Path(credential_helper.__file__).resolve()), directory], stdin=subprocess.DEVNULL, stdout=write_fd, stderr=subprocess.PIPE, close_fds=True) os.close(write_fd) write_fd = None deadline = time.monotonic() + 5 buffered = array.array("i", [0]) while time.monotonic() < deadline: fcntl.ioctl(read_fd, termios.FIONREAD, buffered, True) if buffered[0] == capacity: break if child.poll() is not None: self.fail("Synthetic helper exited before filling the canary pipe: " + child.stderr.read().decode()) time.sleep(0.01) self.assertEqual(capacity, buffered[0], "Synthetic pipe did not fill") self.assertIsNone(child.poll(), "Helper should be waiting for the synthetic stdout reader") lock_fd = os.open(Path(directory) / ".lock", os.O_RDWR) try: fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except BlockingIOError: self.fail("Helper retained the registry lock while stdout was blocked") finally: fcntl.flock(lock_fd, fcntl.LOCK_UN) received = bytearray() deadline = time.monotonic() + 5 while True: remaining = deadline - time.monotonic() self.assertGreater(remaining, 0, "Synthetic helper pipe did not finish") ready, _, _ = select.select([read_fd], [], [], remaining) self.assertTrue(ready, "Synthetic helper pipe timed out") block = os.read(read_fd, 4096) if not block: break received.extend(block) self.assertEqual(b"X" * 16384, bytes(received)) self.assertEqual(0, child.wait(timeout=3), child.stderr.read().decode()) finally: if child is not None: if child.poll() is None: child.kill() child.wait(timeout=3) child.stderr.close() os.close(read_fd) if write_fd is not None: os.close(write_fd) if lock_fd is not None: os.close(lock_fd) if __name__ == "__main__": unittest.main()