doos 1.1.0: limits, hardening, pasta, signatures, Debian image

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 21:59:26 +02:00
co-authored by Claude Sonnet 5.5
parent d446c29372
commit 530582e826
24 changed files with 1182 additions and 127 deletions
+14 -10
View File
@@ -7,11 +7,12 @@ How doos works internally. Behavior contract is `SPEC.md`; this explains the cod
| File | Class / role |
|---|---|
| `src/spec.hpp` | `spec::` constants: caps, mount lists, device list, paths. Single source of truth with `SPEC.md`. |
| `src/util.{hpp,cpp}` | `SysError`, `Fd` (RAII fd), `FileLock` (bounded flock), `parseSize`/`formatSize`, `runProcess` (fork/exec, capped capture), `fsutil` (mkdirs, read/write, bounded `removeTree`, `mirrorDirs` skeleton, `copyTree`, `copyFiles`, `diskUsage` with hardlink dedupe). |
| `src/util.{hpp,cpp}` | `SysError`, `Fd` (RAII fd), `FileLock` (bounded flock), `parseSize`/`formatSize`, `base64Decode`, `runProcess` (fork/exec, capped capture), `fsutil` (mkdirs, read/write, bounded `removeTree`, `mirrorDirs` skeleton, `copyTree`/`copyEntries` (with skip predicate), `copyFiles`, `diskUsage` with hardlink dedupe). |
| `src/idmap.{hpp,cpp}` | `IdMap` (single or subuid mapping via newuidmap/newgidmap, subuid range lookup), `UserNsJob` (run a function as root of a user namespace with the same map: image extraction, deleting subuid-owned trees). |
| `src/image.{hpp,cpp}` | `Catalog` (alpine, ubuntu resolution from publishers), `ImageStore` (add with download, sha256 check, extraction; list; delete). |
| `src/cgroup.{hpp,cpp}` | `Cgroup`: one cgroup v2 leaf per running container under the user's delegated cgroup, used for `cgroup.freeze`. |
| `src/network.{hpp,cpp}` | `NetMode` + `Network` (parse, resolv.conf per mode), `SlirpSession` (owns the slirp4netns process). |
| `src/image.{hpp,cpp}` | `Catalog` (alpine, ubuntu, debian resolution from publishers), `verifySignature` (gpg with a throwaway home, pinned fingerprint), `ImageStore` (add with download, sha256 and signature check, extraction; list; delete). |
| `src/cgroup.{hpp,cpp}` | `Limits`, `Cgroup`: `doos/<name>/{main,shell}` under the user's delegated cgroup; limits on the parent, freeze on `main`, later attaches in `shell`, OOM and pid-limit counters. |
| `src/hardening.{hpp,cpp}` | `Hardening::apply`: capability bounding set to the Docker default, seccomp-bpf denylist built at runtime (arch check, x32 guard, clone3 ENOSYS, CLONE_NEWUSER filter). |
| `src/network.{hpp,cpp}` | `NetMode` (slirp, pasta, host, none) + `Network` (parse, resolv.conf per mode), `SlirpSession`, `PastaSession` (own the helper processes). |
| `src/container.{hpp,cpp}` | `Options` (what the user asked for), `Container` (paths, meta, pid file, layout, one-time seeding, usage), `Store` (list, get_or_create with on-demand image fetch, remove). |
| `src/sandbox.{hpp,cpp}` | `Sandbox`: start a container (clone + id map + mounts + pivot_root + init), watch it (size cap), or join a running one (setns). |
| `src/cli.{hpp,cpp}` | `Cli`: argument parsing, the four container commands and `image list/add/delete`. |
@@ -33,18 +34,21 @@ start():
clone(NEWUSER|NEWNS|NEWPID|NEWUTS|NEWIPC[|NEWNET]) child = container pid 1
IdMap::apply(child, ids) single: write /proc maps; subuid: newuidmap + newgidmap
slirp mode: SlirpSession.start(child) waits for ready-fd
Cgroup::create + add(child) best effort; without it the size cap only warns
slirp/pasta session for the netns of child
Cgroup::create(limits) + addMain(child) best effort; without it nothing is enforced and doos says so
recordPid(child) (pid + starttime, defeats pid reuse)
lock.unlock() joiners must not wait for container lifetime
write 'G' on sync pipe child continues
parent: ignore SIGINT/SIGQUIT, forward SIGTERM/SIGHUP, watch(child)
watch: poll(pidfd, 10 s); each wake scans usage -> warn 80%, freeze 100%, thaw <95%
watch: poll(pidfd, until next scan); scan = usage from inside the id map (UserNsJob) -> warn 80%, freeze 100%,
thaw <95%; next scan after max(2 s, 10 x scan time)
after exit: report OOM kills / pid-limit hits
on exit: pid file cleared, slirp stopped, cgroup removed (RAII)
child (containerInit, pid 1):
PDEATHSIG=SIGKILL, close inherited lock fd, wait for 'G'
setupRoot -> sethostname -> loopback up (isolated netns) -> rlimits/env/no_new_privs
fork worker -> exec command
fork worker -> applyLimitsAndEnv (RLIMIT_FSIZE = size cap, Hardening::apply unless relaxed) -> exec command
pid 1 loop: forward signals, reap all, on worker exit kill(-1, SIGKILL), exit with its code
```
@@ -74,7 +78,7 @@ unmount of the old root.
`seeded.<name>` marker so it runs once and prints what it did and what it skipped:
1. Directory skeleton of every overlaid host dir into its upper layer (`fsutil::mirrorDirs`).
2. Regular files of `/var/lib/dpkg`, `triggers`, `alternatives` copied into the upper (`copyFiles`), then empty lock files.
3. `/etc` copied into `private/etc` (`copyTree`).
3. `/etc` copied into `private/etc`: the allowlist (`copyEntries` with the credential deny predicate) under default hardening, everything readable (`copyTree`) under relaxed.
Reasons are in `SPEC.md` (host root-owned files appear as `nobody`).
@@ -88,8 +92,8 @@ missing, and pins the container's id mode to the image's.
## join flow
Open `/proc/<pid>/ns/{user,mnt,uts,ipc,net,pid}` and `/proc/<pid>/root` first, fork,
`setns` user first (grants capability for the rest), `fchdir`+`chroot` to the container
Open `/proc/<pid>/ns/{user,mnt,uts,ipc,net,pid}` and `/proc/<pid>/root` first, fork, move the child into the
container's `shell` cgroup (same limits, never frozen), `setns` user first (grants capability for the rest), `fchdir`+`chroot` to the container
root, fork again (pid ns applies to children), apply env and limits, exec.
In `host` network mode the net namespace is not joined.