From bb719e7c807c13febbb584c37f7b73a6f24743c5 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Sun, 20 Sep 2026 18:14:28 +0200 Subject: [PATCH] apt/keyring: keep the keyring cache readable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A fresh chroot download failed with mmdebstrap unable to copy the keyrings: its unshare-mode hooks run under an identity that cannot read the invoking user's private directories, so the 0700 cache directory the module created broke the trusted.gpg.d setup hook with 'Permission denied' (builds reusing an already-cached tarball never hit the path). Create the cache 0755 and chmod the keyrings 0644 instead. The planting guard does not weaken: cached keyrings are trusted as-is, and validate_keyring_dir keeps refusing directories that are not owned by the current user or are writable by group or others — read access for the bootstrap tool is not a planting vector. Legacy 0700 cache directories are validated as before, then widened. --- src/apt/keyring.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/apt/keyring.rs b/src/apt/keyring.rs index f6357f5..40eec9c 100644 --- a/src/apt/keyring.rs +++ b/src/apt/keyring.rs @@ -89,24 +89,27 @@ pub async fn download_cache_keyrings( keyring_dir.display() ) })?; + // Upgrade cache directories created by versions that made them + // private: mmdebstrap's unshare-mode hooks cannot read them. } else { // Remote contexts (e.g. ssh) have no stat/metadata access through // the context API, so the ownership guard cannot be performed; // keep the previous best-effort behavior of tightening the - // directory permissions instead (0700 instead of the former - // world-writable a+rwx). - ctx.command("chmod").arg("700").arg(&keyring_dir).status()?; + // directory permissions instead (no group/others write). } + ctx.command("chmod").arg("755").arg(&keyring_dir).status()?; } else { - // Create the directory private to the invoking user (0700). This is - // sufficient for mmdebstrap in unshare mode: it runs with the same - // real uid (the user namespace only maps that uid to root, file - // access still happens as the real uid), so no world-accessible - // permissions are needed. + // Create the directory readable but not writable by group/others. + // mmdebstrap's unshare-mode hooks run under an identity that cannot + // read the invoking user's private directories, so 0700 breaks the + // keyring copy into the chroot; the planting guard stays on the + // ownership and no-write checks of validate_keyring_dir (the + // skip-if-exists logic below trusts pre-existing keyrings, so the + // directory must never be writable by anyone else). ctx.command("mkdir") .arg("-p") .arg("-m") - .arg("700") + .arg("755") .arg(&keyring_dir) .status()?; } @@ -178,6 +181,11 @@ pub async fn download_cache_keyrings( binary_path.display() ); } + + // Readable like the directory: mmdebstrap's hooks copy these into + // the chroot. Applies to legacy files too, which a restrictive + // umask may have left private, and a permissive one group-writable. + let _ = ctx.command("chmod").arg("644").arg(&binary_path).status(); } log::info!( @@ -348,6 +356,10 @@ mod tests { assert!(validate_keyring_dir(1000, 0o750, 1000).is_ok()); assert!(validate_keyring_dir(1000, 0o1744, 1000).is_ok()); assert!(validate_keyring_dir(0, 0o700, 0).is_ok()); + // The world-readable modes the cache now uses: readable so that + // mmdebstrap's unshare-mode hooks can copy the keyrings, while the + // ownership and no-write checks keep the planting guard. + assert!(validate_keyring_dir(1000, 0o755, 1000).is_ok()); } #[test]