File size: 5,204 Bytes
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""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,
    )