carlos / platform Public

tenant-iso-hardening

Base: main @01f890ce (tenant-iso-layout landed). Tip: 7b7571bd, rebased from d217a731 (git rebase --onto origin/main 3a70d278). The rebase was clean, with no conflicts, and the Fable approval was given at d217a731.

Since the old base, the layout work added /data/hostguard and /run/carlos/hostguard, both carlos-writable. The templates already hide both: /data and /run/carlos are empty tmpfs mounts in the namespace. Holds and quarantine are enforced in the activator before it calls the control service. The disk floor and the probe sit downstream of that gate and can only add refusals.

Closes the isolation gaps the nested layout leaves (#214 items 1-3, #178) for carlos-instance@, -seed@, -sidecar@ and the probe. carlos-app@ gets item 2 only.

What changes

  1. CPU, IO and disk (item 1). - setTier now writes CPUWeight=, CPUQuota= and IOWeight= next to the memory caps on the host slice. The values come from the same tier through cgroup.SharesOf: default 100/100%/100, children 100/200%/100. The wire is unchanged. - A per-tenant disk quota is not achievable today. /data is a directory on the mounted root ext4. Project quotas need tune2fs -O project,quota on an unmounted filesystem plus a prjquota mount. Every writer is uid carlos, so reserved blocks can't tell a tenant from the agent. - Chosen guard: refuse and alert, never stop. Below max(1 GiB, 10%) free, /start and /seed answer 507. While the disk stays below that floor, the control service logs instance-control: disk: ... refusing every instance start until there is room every 30s. - There is no "stop the largest instance" shed. Attribution from the control service can't be made reliable: space held by unlinked-but-open files appears in no directory. A stop from there would also bypass the activator's fence and holds.
  2. Version store (item 2). - The namespace replaces /opt/carlos with an empty tmpfs and binds back /opt/carlos/carlos. - Per instance, the unit binds /run/carlos-control/exe/<iid>/app at /opt/carlos/app and .../live at /opt/carlos/live. The control service writes both before every start: app links to /opt/carlos/live/<host>, and live/<host> links to ../app. PID 1 follows the source link at bind time, so argv[0] is unchanged and a repoint takes effect at the next start. - Hashed (long-host) ids work. - carlos-app@ binds /opt/carlos/live/%i over an empty /opt/carlos.
  3. Network (item 3). - IPAddressDeny= covers localhost, multicast, 0/8, RFC 1918, CGNAT, 169.254/16, 192.0.0/24, 198.18/15, 240/4, 64:ff9b::/96, fc00::/7, fe80::/10 and fec0::/10. IMDS is still named explicitly. - The only legitimate on-box need is DNS. The edge reaches instances over unix sockets, sidecars are outbound-only, the relay is public STUN/TURN, and the edge-local door is a unix socket. - An allow is per address, both ways and on every port, so no loopback resolver is ever opened. That includes resolved's 127.0.0.53/54 stubs. - The namespace binds -/run/systemd/resolve/resolv.conf (resolved's upstreams) over /etc/resolv.conf. Before every probe, the control service re-opens the nameservers in that same file with IPAddressAllow= on carlos-instances.slice, after resetting it. - Link-local resolvers are refused (an allow carries no zone), except Amazon's 169.254.169.253. - The unspecified address and IMDS are refused too.
  4. #178. - PrivateIPC=yes and TemporaryFileSystem=/dev/mqueue:ro. - Before exec, the trampoline applies Landlock LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (ABI 6) on a locked thread. It refuses to exec if it can't.

Probe: new lines are opt-own-build, dns, resolver-ports, loopback-v4/v6, private-v4/v6, ipc-private, abstract-unix, cpu-limits and io-weight. resolver-ports fails when an allowed resolver is one of the box's own interface addresses, or answers TCP on port 22. The probe unit carries CPUQuota=50% CPUWeight=50 IOWeight=50 and reads them back from its own cgroup. Missing IPv6 and IO weights with no io.cost or BFQ behind them are unverified, not fail.

Residuals (written up in spec §11.1)

  • IO: IOWeight= is inert without io.cost or BFQ.
  • Disk: tenants that are already awake can keep writing past the floor.
  • Network: - An allowed upstream resolver is open on every port, but it is a remote resolver, never the box. - A resolver change reaches running instances only at their next start. - Loopback within one instance is closed too, so a forking family must use unix sockets.
  • Abstract sockets: a sibling can still bind a name first. That denies the name to its owner, but never connects to it.

Rollout: verify before the fleet roll

  1. carlos-app@ applies to every bucket-mode app today, not just nested ones. Before the fleet roll, start one dev box's carlos-app@ apps under the new template: TemporaryFileSystem=/opt/carlos:ro plus BindReadOnlyPaths=/opt/carlos/live/%i. Check that each still execs and serves, including any operator drop-in whose ExecStart= names another path under /opt/carlos.
  2. abstract-unix needs Landlock in the booted kernel's lsm= list (cat /sys/kernel/security/lsm), not just a 6.12 kernel. Without it, the probe fails abstract-unix and the trampoline refuses to exec, so nested wakes are refused with that named reason.
  3. A 127.0.0.1 resolver (dnsmasq or similar) fails dns, and so does a box with only resolved's stub and no upstream list. Nested wakes are then refused with the named reason.
  4. On one box, also confirm: - systemd 257 accepts bind sources under /run/carlos-control, which the templates mark inaccessible. - PrivateDevices= carries the host /dev/mqueue. - /run/systemd/resolve/resolv.conf lists the VPC resolver.

The probe fails closed on each of these.

Tests

  • New tests pin every directive and every probe line.
  • They also drive ensureExe, the resolver policy, the choice of resolv.conf file, the 507 floor, and a test that a full disk warns and stops nothing.
  • A real Landlock test runs on 6.12.
  • make test on 7b7571bd: the only failures are the known environment ones: socket-path length (activator, edge, instanceexec) and the root package reading ~/.carlos/config.

Deliberately not done:

  • Flat tenants.
  • CPU/IO and network for carlos-app@.
  • #214 items 4-5 (branch tenant-iso-routes).
Commits 0
Discussions 2 0 open
Tasks 0/0 done

Discussions 0 open

  • No discussions on this branch yet.

Tasks

  • No tasks yet.

Activity