diff --git a/docs/design/net-zone.md b/docs/design/net-zone.md index 18d47ea..7dfcf3b 100755 --- a/docs/design/net-zone.md +++ b/docs/design/net-zone.md @@ -253,10 +253,34 @@ plan line states only what kryptikd itself sets up. ### Not built, and why it is not a gap in the boundary -- **MAC/IP port pinning.** Designed for nftables `bridge` rules. Its - purpose was to contain a zone that could re-address its end; that zone no - longer exists (no `CAP_NET_ADMIN`/`CAP_NET_RAW` in routed zones), so - pinning is defence in depth, not the boundary. +- **MAC/IP port pinning. Decided: not built.** Designed for nftables + `bridge` rules. Its purpose was to contain a zone that could re-address + its end or forge a frame; that zone no longer exists. A routed zone's + bounding set is `CAP_NET_BIND_SERVICE` alone, a zone policy that would + keep `CAP_NET_ADMIN` or `CAP_NET_RAW` for a routed zone is refused, and + packet sockets are refused by family: it cannot change its address or its + MAC, cannot send from an address that is not its own, and cannot put a + frame on the wire that the kernel did not build for it. The launcher + suite reads the bounding set (exactly `0x400`) and the boundary suite + asks for an `AF_PACKET` socket. Pinning would enforce the same thing a + second time, in the net zone, which is the zone treated as hostile, and + to do it the kernel would have to carry `NF_TABLES_BRIDGE` and + `BRIDGE_NETFILTER` built in (netfilter cannot be a module here: the net + zone loads its rules from inside a user namespace). That is more kernel + reachable from a hostile zone in exchange for a check the capability + drop already makes, so it is not built. It comes back on the table if a + routed zone is ever allowed either network capability. +- **dhcpcd's own privilege separation. Decided: it cannot have it.** + dhcpcd is built with a privilege-separation user and runs without it in + the net zone, saying so once. To separate, it must `setgroups`, + `setgid`, `setuid` and `chroot`: the zone's `setgroups` is `deny` for + good, its passwd is synthesized (root and nobody), and its bounding set + is `CAP_NET_BIND_SERVICE`, `CAP_NET_ADMIN` and `CAP_NET_RAW`, with none + of `CAP_SETUID`, `CAP_SETGID` or `CAP_SYS_CHROOT`. Giving the hostile + zone three more capabilities so that one of its programs can build a + smaller sandbox inside it is a net loss. The zone is the sandbox: its + own user, mount, network and pid namespaces, seccomp and Landlock, and + nothing dhcpcd could do as the zone's root reaches past them. - **Refusing direct traffic to the uplink's own addresses from routed zones.** The forward chain accepts anything from the bridge to the uplink, so a routed zone can address the VM gateway or the slirp resolver diff --git a/docs/roadmap.md b/docs/roadmap.md index f29a5b8..2d23908 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -317,10 +317,14 @@ passes, not when its code is written. - [ ] **Core scheduling per zone, and the SMT decision.** Zones carry their own cookie; ADR-011 is revisited with a measurement, and the command line says `nosmt` or does not for a written reason. -- [ ] **The net zone's remaining hardening.** Bridge ports pinned to their - assigned MAC and address (`docs/design/net-zone.md`, not built), and - dhcpcd with privilege separation inside the zone or a recorded reason - it cannot have it. +- [x] **The net zone's remaining hardening.** Both halves are decided, with + the reasons in [the net zone design](design/net-zone.md#not-built-and-why-it-is-not-a-gap-in-the-boundary). + Bridge ports are not pinned: a routed zone has neither network + capability and no packet sockets, which the suites check, and pinning + would put two more netfilter subsystems into the kernel to make the + same check again inside the zone treated as hostile. dhcpcd cannot + have its own privilege separation there: it would need `setgroups` + and three capabilities the net zone does not have and should not get. - [ ] **Someone else has attacked it.** The two hand-written trust boundaries, the broker's protocol and `kryptik-wlproxy`'s wire parser, fuzzed in CI with a corpus kept in the tree; and one review of kryptikd's launch path