From fdb96c7b1c5a3b79f5c6876e56e6030ae119c36e Mon Sep 17 00:00:00 2001 From: retoor Date: Fri, 23 Oct 2026 21:23:29 +0200 Subject: [PATCH] Document apt field trial and gap G7 Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 3 +++ README.md | 10 +++++++++- VERSION | 2 +- docs/RESEARCH.md | 28 ++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3337eb9..ef8131f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.1.3 - 2026-10-23 +- Docs: field trial of apt and common tools, new gap G7 (host-owned lower layer), README limits section. No code change. + ## 0.1.2 - 2026-10-23 - Docs: researched solutions for all known gaps (G1 to G6) in `docs/RESEARCH.md`, with measurements. Nothing implemented yet. diff --git a/README.md b/README.md index d70fbdb..5e47523 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # doos -Version: see `VERSION` (currently 0.1.2). Release history: `CHANGELOG.md`. +Version: see `VERSION` (currently 0.1.3). Release history: `CHANGELOG.md`. User-level containers in C++20: overlayfs over the host's `/usr`, `/etc`, `/opt`, user/mount/pid namespaces, networking through slirp4netns. No root at runtime. @@ -38,6 +38,14 @@ Container writes to `/usr`, `/etc`, `/opt` stay in the container (copy-on-write) A container is `running` while its first `attach` command is alive; more `attach` calls join it. Data persists until `delete --yes`. +## Current limits you will hit + +Tools run fine (curl, git, gcc, python3, pip, ping all verified). Package management does +not: `apt-get update` needs a seeded `/var` and `-o APT::Sandbox::User=root`, and +`apt-get install` cannot finish configuring packages because host-root-owned files are +unwritable and unchownable inside. Details, measurements and the fix are in +`docs/RESEARCH.md` (G7, "Field trial"). + ## Configuration | Variable | Meaning | diff --git a/VERSION b/VERSION index d917d3e..b1e80bb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.2 +0.1.3 diff --git a/docs/RESEARCH.md b/docs/RESEARCH.md index ab5c93b..900fa01 100644 --- a/docs/RESEARCH.md +++ b/docs/RESEARCH.md @@ -54,6 +54,7 @@ removed afterwards. Rows marked "unmeasured" have no run behind them. | G4 | Host `/etc` readable | Allowlist-only private `/etc` instead of an overlay of host `/etc` | minor | `spec.hpp` lists, `setupRoot` | | G5 | `--net host` shares abstract sockets | Keep opt-in; evaluate pasta as an isolated replacement | patch or minor | `network.cpp` | | G6 | No seccomp, nested userns allowed | Hand-built seccomp-bpf denylist after `no_new_privs` | minor | new `Seccomp` class, `sandbox.cpp` | +| G7 | Host-owned lower dirs are not writable; `chown --reference` of host files fails; `/var` is empty so apt has no databases | Image-based lower layer (extracted rootfs, owned by mapped ids) plus G3; short term: pre-seeded directory skeleton | major (changes layout) | `Container::prepareLayout`, `setupRoot`, new image import | ### G1: memory, cpu and pids limits (cgroup v2) @@ -133,3 +134,30 @@ Unmeasured: compatibility impact on real workloads. 3. G4 (private `/etc`): needs the per-tool allowlist runs first. 4. G2, then G3 (G3 changes the delete path, do it deliberately). 5. G5: needs a test matrix before any change. + +## Field trial: apt and common tools (v0.1.2, measured) + +Run in a fresh `--net slirp` container on the host above (Ubuntu with apt 3.2.0, dpkg 1.23.7). + +| Step | Result | +|---|---| +| curl https, git ls-remote, gcc compile+run, python3 (`ssl`, `sqlite3`), pip, ping 1.1.1.1 | all work | +| `apt-get update` as shipped | fails: `/var/lib/apt/lists/partial is missing` (`/var` is an empty private dir) | +| `apt-get install` as shipped | fails: `/var/lib/dpkg/lock-frontend` missing, no dpkg status database | +| after seeding `/var` (dirs plus copy of host `dpkg/status`, `info`, `alternatives`, 40 MB) | apt starts, but the download method dies: `setegid 65534 failed`, `seteuid 42 failed` (sandbox user `_apt` cannot exist with a single id) | +| with `-o APT::Sandbox::User=root` | `apt-get update` works: 58.9 MB fetched in 3 s | +| `apt-get install cowsay` | download works; unpack fails: `unable to create '/usr/games/cowsay.dpkg-new': Permission denied` | +| after pre-creating the host directory tree in the upper dirs (13164 dirs for `/usr`, done with a shell loop, 38 s; not yet implemented in C++) | unpack works, `/usr/games/cowsay hoi` runs; host `/usr/games/cowsay` still absent (isolation holds) | +| configure step | fails: `chown: changing ownership of '/etc/shells.tmp': Invalid argument` (debianutils postinst copies owner of `/etc/shells`, which shows as `65534`), so dpkg reports 4 packages not fully installed | + +Root causes: +1. Host files owned by host root appear as `nobody` (uid 65534). Container root has `CAP_DAC_OVERRIDE` only over inodes whose owner is mapped, so it cannot write into host-root-owned directories of the lower layer, and cannot `chown` to or from an unmapped owner. +2. `/var` has no package database. +3. apt wants a second uid (G3). + +Conclusion: the current design (host `/usr`, `/etc`, `/opt` as lower layer) is good for running tools and for isolated experiments, not for package management. Bare tools and compilers work. Honest status: **`apt-get install` does not complete today.** + +Researched ways forward, in order of effort: +- Short term, writable skeleton: at create time mirror every directory of the lower dirs into the upper dir with the same mode (directories owned by the user are writable; merged view is unchanged). Directories unreadable to the user (for example `/etc/ssl/private`, mode 700, root-owned) are skipped and stay as they are. Measured effect: writes into `/usr/bin` work, attach time unchanged (41 ms). Unmeasured: C++ implementation speed, and it does not fix the `chown` failure. +- Proper fix, image layer: use an extracted distro rootfs (for example from `podman export` or `skopeo copy` plus unpack, both installed here; `mmdebstrap` and `debootstrap` are not) as the lower dir, with ownership shifted into the subuid range (G3). Then the tree is owned by mapped ids, `/var/lib/dpkg` exists, `chown` works, `_apt` exists, and host `/etc` stops leaking (G4). This also removes the dependence on the host distro. Cost: image import step, disk per image (shared lower, so once), and a layout change, hence a major bump. +- Alternative to evaluate: `fuse-overlayfs` (installed) supports uid mappings between layers. Unmeasured; adds a FUSE process per container.