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
+14 -2
View File
@@ -46,8 +46,10 @@ pub enum Phase {
/// Drop the resumed tree's input-untracked stamp caches (quirk:
/// `resume_clear`)
ClearingResumeCaches,
/// Create the unprivileged build user and hand the staged tree to
/// it (rootless builds)
/// Establish the unprivileged build identity for the rules steps
/// (rootless builds): a nested user namespace mapping the chroot's
/// root ownership onto the identity — the tree needs no chown — or,
/// when unavailable, a build user with a chown tree handover
PreparingBuildUser,
/// debian/rules build
Building,
@@ -642,6 +644,16 @@ async fn build_binary_package_impl(
log::info!(
"Reusing the build artifacts of the previous attempt (incremental build)"
);
// Before the overlay mounts (see the function's doc): a
// setpriv-era tree handed over here lets the mapped
// build identity run the rules; a failed handover makes
// the identity setup fall back to the chown handover.
if let Err(e) = local::migrate_upper_ownership(s) {
log::warn!(
"cannot hand the staged tree to the invoking user \
({e}); falling back to the chown handover"
);
}
}
}
let staged_path = if let Some(s) = &session {