|
| 1 | +# 0004: Isolation is a child OS process over a Unix socket |
| 2 | + |
| 3 | +Since 5.0.0. Code: `src/py_isolated.erl`, `priv/py_isolated_child.py`, |
| 4 | +`priv/_erlang_impl/_isolated.py`. |
| 5 | + |
| 6 | +## Situation |
| 7 | + |
| 8 | +Embedded modes cannot stop a Python call stuck in C, cannot bound its |
| 9 | +memory or CPU, and a segfault in an extension kills the node. Running |
| 10 | +untrusted or unknown Python code needs those guarantees, with the public |
| 11 | +API unchanged so a pool can mix modes. |
| 12 | + |
| 13 | +## Decision |
| 14 | + |
| 15 | +An isolated context is one child process per context, started with |
| 16 | +`open_port` so the VM reaps it, talking to its `py_context` process over |
| 17 | +a Unix socket with the frame format of the callback pipe |
| 18 | +(`<<Id:64, Len:32, Body>>`, body `<<Status, ETF>>`). Interrupts are a |
| 19 | +signal to the child's main thread with a `SIGKILL` backstop; limits are |
| 20 | +rlimits, cgroups v2 on Linux and an RSS watchdog on macOS; a crash |
| 21 | +restarts the child within a budget. The child uses the standard asyncio |
| 22 | +loop; there is no C code of its own in the VM beyond `os_kill`. |
| 23 | + |
| 24 | +Not chosen: a seccomp or Capsicum sandbox (a later hardening step, the |
| 25 | +process boundary is the first one), a NIF-side sub-process pool, and a |
| 26 | +new wire protocol (the existing frame and ETF conventions are enough). |
| 27 | + |
| 28 | +## Consequences |
| 29 | + |
| 30 | +- Everything crossing the socket is a term. NIF resources (channels, |
| 31 | + native buffers, object references) do not cross; the API says so with |
| 32 | + `{error, not_supported_in_isolated}`. |
| 33 | +- The child can create atoms (`binary_to_term` is not called with |
| 34 | + `safe` because handles and control terms need atoms); untrusted code |
| 35 | + must not mint unbounded distinct atoms. |
| 36 | +- Each call copies arguments and results through the socket; bulk data |
| 37 | + needs shared memory (0006). |
| 38 | +- Python state is lost on restart; the context stays usable. |
| 39 | +- Platform code lives in the child launcher: parent-death signal |
| 40 | + (`prctl` on Linux, `procctl` on FreeBSD), memory limits per OS. |
0 commit comments