"""Linux process restriction — DESIGN ONLY. NOTHING HERE HAS EVER RUN. Read this paragraph before anything else in the file. There is **no implementation** in this module and no test exercising one. The development environment for this work had no Linux host: the sandbox that would have provided one reported "not supported on this device", so every line below is an intention, not a behaviour. Any statement that distinct isolates processes on Linux is false today. The module is committed anyway, rather than left as a comment in a plan, because :func:`describe_linux_support` has to exist and has to return ``applied=False``. A caller on Linux asking "am I isolated?" must get a truthful no, and a ``ProcessRestriction(mode="required")`` must refuse to spawn rather than proceed unprotected. That refusal *is* implemented and is the only functional code here. ---- Intended design, for whoever implements it on a real Linux host =============================================================== The target is the same containment property the Windows Job Object provides — bounded processes, bounded memory, bounded CPU, no orphans — plus the syscall narrowing that Windows cannot easily express. 1. **cgroup v2** (the resource half; closest analogue to the Job Object) Create a delegated sub-cgroup per job under the agent's own cgroup, then write: - ``pids.max`` — the analogue of ``ActiveProcessLimit``. Prevents fork bombs and stops a runner spawning helpers. - ``memory.max`` and ``memory.swap.max`` — hard memory ceiling. ``memory.max`` alone is insufficient: without a swap cap the process can exceed the intended footprint via swap. - ``cpu.max`` — quota/period pair for CPU bandwidth. - ``cgroup.kill`` — write ``1`` to kill every member atomically. This is the analogue of ``KILL_ON_JOB_CLOSE`` and is the correct cancellation primitive; it is race-free in a way that signalling a process group is not. Open questions a real implementation must answer: whether the agent runs under systemd (delegation via a transient scope is the clean path) or must manage ``/sys/fs/cgroup`` directly, and what happens on a host with cgroup v1 only, where these controls differ enough that they need a separate path or an honest refusal. 2. **Namespaces** (the visibility half; no Windows analogue) ``CLONE_NEWNS`` with a private mount propagation and a read-only bind of the model directory; ``CLONE_NEWNET`` with **no** interfaces configured, which makes the loopback-only property structural for the child rather than policy; ``CLONE_NEWPID`` so the child cannot see or signal agent processes; ``CLONE_NEWIPC`` and ``CLONE_NEWUTS`` for completeness. ``CLONE_NEWUSER`` is what makes the rest available without root, and is also the biggest open question: unprivileged user namespaces are disabled by default or restricted on several distributions, so the implementation must detect that and degrade to a stated, reported reduction in isolation rather than silently continuing. 3. **seccomp-bpf** (the syscall half) A default-deny filter with an explicit allowlist, installed with ``NO_NEW_PRIVS`` after the model file is opened and before ``execve``. The allowlist for a llama.cpp inference process is small and stable: read, write, mmap, munmap, mprotect, futex, clock_gettime, exit_group, and the thread primitives. Notably absent: ``socket``, ``connect``, ``execve`` after the first, ``ptrace``. The honest difficulty is that llama.cpp's syscall set varies with build options (CUDA, ROCm, OpenBLAS each add device ioctls), so a single filter will not fit every build, and a filter that kills a legitimate run is worse than no filter for anyone trying to contribute compute. This needs measurement on real builds before it can ship enabled. Verification the implementer owes --------------------------------- Mirroring ``tests/test_agent_isolation.py``, which proves the Windows limits by watching real children fail: - a child that forks past ``pids.max`` is refused; - a child that allocates past ``memory.max`` is killed; - a child that tries ``socket(AF_INET)`` fails with ``EPERM`` under the filter; - ``cgroup.kill`` terminates the whole tree, with no survivors; - a child cannot see the agent's PIDs in ``/proc``. Until those pass on a real Linux kernel, this file stays as it is. """ from __future__ import annotations import sys from .base import RestrictionReport MECHANISM = "linux-namespaces-seccomp-cgroups" #: The single fact this module asserts. IMPLEMENTED = False UNTESTED_NOTICE = ( "Linux process restriction is designed but NOT implemented and NOT tested. " "No namespace, seccomp filter or cgroup limit is applied. Treat a Linux " "agent as running with the operator's full privileges." ) def describe_linux_support() -> RestrictionReport: """Report the truth: nothing is enforced on Linux.""" return RestrictionReport( platform=sys.platform, applied=False, mechanism=MECHANISM, limits={"implemented": IMPLEMENTED}, reason=UNTESTED_NOTICE, )