context,deb: run the rules as a nested-namespace build identity

Rootless builds handed the staged tree to the build user with a
recursive chown; through the overlay staging every lower-layer file
the chown touched copy-ups — the full data movement of the tree, 19%
of a kernel build's wall clock.

The rules steps are now flagged and the unshare driver enters the
chroot by pivot_root instead of chroot(2), then creates a nested user
namespace mapping the context's root onto a fixed build id: the
chroot's files are the invoking user's host-side and the context maps
that user to uid 0, so the staged tree is owned by the build identity
by construction — no chown, no data movement. The kernel refuses user
namespace creation to chrooted processes, hence the pivot, and the
bind is recursive and taken inside the namespace: a plain bind of the
chroot's host-made submounts fails EINVAL on locked children, while
the rbind carries them across the pivot, and a fresh /proc is mounted
before the old root is shed (umount resolves through /proc/mounts).

fakeroot's chown wrappers attempt the real chown and swallow only
EPERM; as the mapped identity, root targets are unmapped and fail
EINVAL, so mapped builds export FAKEROOTDONTTRYCHOWN — fakeroot's own
knob — and the fake ownership is still recorded. Files the build
writes come out as the invoking user host-side, which also fixes the
silently missing .buildinfo/.changes of rootless builds: the host-side
rewrite of debian/files hit a subuid-owned file with EACCES.

The wrapper is probed per build; when nested namespaces are
unavailable (old util-linux, userns disabled), the previous setpriv
plus selective parallel chown handover runs unchanged. A setpriv-era
staged tree is handed to the invoking user once, host-side and
metadata-only, before the overlay mounts it — afterwards overlayfs
serves stale owners from its attribute cache.
This commit is contained in:
2026-10-03 13:00:26 +02:00
parent 5544873107
commit 2729f9d9e7
6 changed files with 526 additions and 58 deletions
+5 -3
View File
@@ -2136,9 +2136,11 @@ pub(crate) fn plan_upper_sync(
///
/// Removals go through the privileged path: the upperdir's entries were
/// copy-up'd by whoever wrote them during the build — in a rootless build
/// the unprivileged build user (a subuid of the invoking user host-side) —
/// so a plain removal by the invoking user fails with EPERM exactly when a
/// host-modified file's stale upper copy must go.
/// the unprivileged build identity, whose files are the invoking user's
/// own host-side under the mapped namespace (a subuid of theirs with the
/// setpriv fallback, or of earlier builds) — so a plain removal by the
/// invoking user fails with EPERM exactly when a host-modified file's
/// stale upper copy must go.
pub(crate) fn apply_upper_sync(upper_root: &Path, ops: &[UpperOp]) -> io::Result<usize> {
let mut removed_prefixes: Vec<String> = Vec::new();
let mut removed = 0;