| """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" |
|
|
| |
| 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, |
| ) |
|
|