aaxaxax's picture
initial commit
2aa8b3a
Raw
History Blame
5.2 kB
"""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,
)