apt: keep the keyring cache private to the invoking user
The shared world-writable /tmp/pkh-keyrings directory, combined with the skip-if-exists logic, let any local user pre-plant keyrings that pkh then trusts into the chroot's trusted.gpg.d. Use a per-uid 0700 directory instead, refuse to reuse a pre-existing directory that is not owned by the current user or is group/other-writable, and drop the now unnecessary world-accessibility chmods (mmdebstrap in unshare mode runs with the same real uid).
This commit is contained in:
+57
-9
@@ -11,7 +11,8 @@
|
||||
//! unmounting before they can be removed.
|
||||
//! - **Cached chroot tarballs** (`~/.cache/pkh/*-buildd.tar.xz`) and their
|
||||
//! **stale download lockfiles** (`~/.cache/pkh/*.lock`).
|
||||
//! - **The shared apt keyring directory** (`/tmp/pkh-keyrings`), used by
|
||||
//! - **The apt keyring cache directories** (`pkh-keyrings` and the per-uid
|
||||
//! `pkh-keyrings-<uid>` under the system temp directory), used by
|
||||
//! mmdebstrap runs.
|
||||
//! - **Build logs** (`~/.cache/pkh/logs/deb-*.log`) written by `pkh deb`. By
|
||||
//! default only logs beyond a small retention window (the newest
|
||||
@@ -67,7 +68,8 @@ pub(crate) const KEEP_LOGS: usize = 10;
|
||||
enum Artifact {
|
||||
/// A residual build/chroot directory under the system temp dir.
|
||||
TempDir(PathBuf),
|
||||
/// The shared apt keyring directory (`/tmp/pkh-keyrings`).
|
||||
/// The apt keyring cache directory (`pkh-keyrings*` under the system
|
||||
/// temp dir).
|
||||
KeyringDir(PathBuf),
|
||||
/// A stale chroot tarball download lockfile.
|
||||
LockFile(PathBuf),
|
||||
@@ -105,11 +107,24 @@ pub fn cache_dir() -> Option<PathBuf> {
|
||||
ProjectDirs::from("com", "pkh", "pkh").map(|d| d.cache_dir().to_path_buf())
|
||||
}
|
||||
|
||||
/// Check whether a directory entry name is the apt keyring cache directory,
|
||||
/// i.e. the legacy shared `pkh-keyrings` name or the per-uid
|
||||
/// `pkh-keyrings-<uid>` used by current pkh versions.
|
||||
fn is_keyring_dir_name(name: &str) -> bool {
|
||||
if name == "pkh-keyrings" {
|
||||
return true;
|
||||
}
|
||||
match name.strip_prefix("pkh-keyrings-") {
|
||||
Some(uid) => !uid.is_empty() && uid.bytes().all(|b| b.is_ascii_digit()),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether a directory entry name is a residual pkh build directory,
|
||||
/// i.e. it matches `pkh-<digits>` or `pkh-<digits>-<digits>`.
|
||||
///
|
||||
/// This deliberately rejects the `pkh-keyrings` and `pkh-build-*` names so they
|
||||
/// are not mistaken for residual build chroots.
|
||||
/// This deliberately rejects the `pkh-keyrings*` and `pkh-build-*` names so
|
||||
/// they are not mistaken for residual build chroots.
|
||||
fn is_residual_temp_dir(name: &str) -> bool {
|
||||
let Some(rest) = name.strip_prefix("pkh-") else {
|
||||
return false;
|
||||
@@ -189,7 +204,7 @@ fn discover_artifacts(temp_dir: &Path, cache_dir: Option<&Path>) -> Vec<Artifact
|
||||
}
|
||||
if is_residual_temp_dir(&name) {
|
||||
artifacts.push(Artifact::TempDir(path));
|
||||
} else if name == "pkh-keyrings" {
|
||||
} else if is_keyring_dir_name(&name) {
|
||||
artifacts.push(Artifact::KeyringDir(path));
|
||||
}
|
||||
}
|
||||
@@ -404,6 +419,7 @@ mod tests {
|
||||
|
||||
// Keyrings and unshare work dirs must NOT match.
|
||||
assert!(!is_residual_temp_dir("pkh-keyrings"));
|
||||
assert!(!is_residual_temp_dir("pkh-keyrings-1000"));
|
||||
assert!(!is_residual_temp_dir("pkh-build-1700000000"));
|
||||
|
||||
// Non-numeric / malformed names.
|
||||
@@ -415,6 +431,21 @@ mod tests {
|
||||
assert!(!is_residual_temp_dir("other-123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_keyring_dir_name() {
|
||||
// Legacy shared name and current per-uid names are matched.
|
||||
assert!(is_keyring_dir_name("pkh-keyrings"));
|
||||
assert!(is_keyring_dir_name("pkh-keyrings-0"));
|
||||
assert!(is_keyring_dir_name("pkh-keyrings-1000"));
|
||||
|
||||
// Everything else is not.
|
||||
assert!(!is_keyring_dir_name("pkh-keyrings-"));
|
||||
assert!(!is_keyring_dir_name("pkh-keyrings-abc"));
|
||||
assert!(!is_keyring_dir_name("pkh-keyrings-1000-2"));
|
||||
assert!(!is_keyring_dir_name("keyrings"));
|
||||
assert!(!is_keyring_dir_name("pkh-1000"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unescape_mountpath() {
|
||||
assert_eq!(unescape_mountpath("/tmp/simple"), "/tmp/simple");
|
||||
@@ -455,8 +486,9 @@ none /tmp/other proc rw 0 0
|
||||
// Residual build dirs.
|
||||
fs::create_dir_all(temp_path.join("pkh-1700000000")).unwrap();
|
||||
fs::create_dir_all(temp_path.join("pkh-1700000001-2")).unwrap();
|
||||
// Keyring dir.
|
||||
// Keyring dirs (legacy shared name and per-uid name).
|
||||
fs::create_dir_all(temp_path.join("pkh-keyrings")).unwrap();
|
||||
fs::create_dir_all(temp_path.join("pkh-keyrings-1000")).unwrap();
|
||||
// Non-matching entries that should be ignored.
|
||||
fs::create_dir_all(temp_path.join("pkh-build-1700000000")).unwrap();
|
||||
fs::create_dir_all(temp_path.join("other-dir")).unwrap();
|
||||
@@ -477,21 +509,21 @@ none /tmp/other proc rw 0 0
|
||||
let artifacts = discover_artifacts(temp_path, Some(cache_path));
|
||||
|
||||
let mut temp_dirs = 0;
|
||||
let mut keyring = false;
|
||||
let mut keyring_dirs = 0;
|
||||
let mut locks = 0;
|
||||
let mut tarballs = 0;
|
||||
let mut logs = 0;
|
||||
for a in &artifacts {
|
||||
match a {
|
||||
Artifact::TempDir(_) => temp_dirs += 1,
|
||||
Artifact::KeyringDir(_) => keyring = true,
|
||||
Artifact::KeyringDir(_) => keyring_dirs += 1,
|
||||
Artifact::LockFile(_) => locks += 1,
|
||||
Artifact::Tarball(_) => tarballs += 1,
|
||||
Artifact::LogFile(_) => logs += 1,
|
||||
}
|
||||
}
|
||||
assert_eq!(temp_dirs, 2);
|
||||
assert!(keyring);
|
||||
assert_eq!(keyring_dirs, 2);
|
||||
assert_eq!(locks, 1);
|
||||
assert_eq!(tarballs, 2);
|
||||
assert_eq!(logs, 1);
|
||||
@@ -564,6 +596,7 @@ none /tmp/other proc rw 0 0
|
||||
let dir = temp_path.join("pkh-1700000000");
|
||||
fs::create_dir_all(&dir).unwrap();
|
||||
fs::create_dir_all(temp_path.join("pkh-keyrings")).unwrap();
|
||||
fs::create_dir_all(temp_path.join("pkh-keyrings-1000")).unwrap();
|
||||
fs::write(cache_path.join("noble-buildd.tar.xz"), "tarball").unwrap();
|
||||
fs::write(cache_path.join("noble-buildd.tar.lock"), "lock").unwrap();
|
||||
|
||||
@@ -587,6 +620,12 @@ none /tmp/other proc rw 0 0
|
||||
.iter()
|
||||
.any(|p| p == &temp_path.join("pkh-keyrings"))
|
||||
);
|
||||
assert!(
|
||||
report
|
||||
.removed
|
||||
.iter()
|
||||
.any(|p| p == &temp_path.join("pkh-keyrings-1000"))
|
||||
);
|
||||
assert!(
|
||||
report
|
||||
.removed
|
||||
@@ -604,6 +643,7 @@ none /tmp/other proc rw 0 0
|
||||
// Nothing was actually removed.
|
||||
assert!(dir.exists());
|
||||
assert!(temp_path.join("pkh-keyrings").exists());
|
||||
assert!(temp_path.join("pkh-keyrings-1000").exists());
|
||||
assert!(cache_path.join("noble-buildd.tar.xz").exists());
|
||||
assert!(cache_path.join("noble-buildd.tar.lock").exists());
|
||||
}
|
||||
@@ -619,6 +659,7 @@ none /tmp/other proc rw 0 0
|
||||
let cache_path = cache.path();
|
||||
|
||||
fs::create_dir_all(temp_path.join("pkh-keyrings")).unwrap();
|
||||
fs::create_dir_all(temp_path.join("pkh-keyrings-1000")).unwrap();
|
||||
fs::write(cache_path.join("noble-buildd.tar.lock"), "lock").unwrap();
|
||||
fs::write(cache_path.join("noble-buildd.tar.xz"), "tarball").unwrap();
|
||||
|
||||
@@ -639,6 +680,12 @@ none /tmp/other proc rw 0 0
|
||||
.iter()
|
||||
.any(|p| p == &temp_path.join("pkh-keyrings"))
|
||||
);
|
||||
assert!(
|
||||
report
|
||||
.removed
|
||||
.iter()
|
||||
.any(|p| p == &temp_path.join("pkh-keyrings-1000"))
|
||||
);
|
||||
assert!(
|
||||
report
|
||||
.removed
|
||||
@@ -647,6 +694,7 @@ none /tmp/other proc rw 0 0
|
||||
);
|
||||
|
||||
assert!(!temp_path.join("pkh-keyrings").exists());
|
||||
assert!(!temp_path.join("pkh-keyrings-1000").exists());
|
||||
assert!(!cache_path.join("noble-buildd.tar.lock").exists());
|
||||
// Tarball is preserved when --all is not set.
|
||||
assert!(cache_path.join("noble-buildd.tar.xz").exists());
|
||||
|
||||
Reference in New Issue
Block a user