Skip to content

Commit 6ac06b7

Browse files
jeckersbcgwalters
authored andcommitted
store: Centralize composefs directory creation with mode 0700
The install-time composefs directory creation in repo.rs used create_dir_all() which relies on the process umask for permissions, potentially creating /sysroot/composefs with overly permissive modes and leaking information. Centralize the directory creation into a new ensure_composefs_dir() helper in store/mod.rs that explicitly sets mode 0700. Both the install-time path (repo.rs) and the runtime lazy-init path (Storage::get_ensure_composefs) now use this single helper. The helper also always updates permissions on existing directories, so systems installed with an older version of bootc will have their composefs directory permissions corrected on upgrade. Also removes #[allow(dead_code)] from COMPOSEFS_MODE since it is now actively used, and adds unit tests verifying the directory permissions, idempotency, and correction of pre-existing wrong permissions. Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: John Eckersberg <jeckersb@redhat.com>
1 parent 9eb80ce commit 6ac06b7

2 files changed

Lines changed: 87 additions & 9 deletions

File tree

crates/lib/src/bootc_composefs/repo.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ pub(crate) async fn initialize_composefs_repository(
2626
) -> Result<(String, impl FsVerityHashValue)> {
2727
let rootfs_dir = &root_setup.physical_root;
2828

29-
rootfs_dir
30-
.create_dir_all("composefs")
31-
.context("Creating dir composefs")?;
29+
crate::store::ensure_composefs_dir(rootfs_dir)?;
3230

3331
let repo = open_composefs_repo(rootfs_dir)?;
3432

crates/lib/src/store/mod.rs

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ use anyhow::{Context, Result};
2424
use bootc_mount::tempmount::TempMount;
2525
use camino::Utf8PathBuf;
2626
use cap_std_ext::cap_std;
27-
use cap_std_ext::cap_std::fs::{Dir, DirBuilder, DirBuilderExt as _};
27+
use cap_std_ext::cap_std::fs::{
28+
Dir, DirBuilder, DirBuilderExt as _, Permissions, PermissionsExt as _,
29+
};
2830
use cap_std_ext::dirext::CapStdExtDirExt;
2931
use fn_error_context::context;
3032

@@ -51,8 +53,29 @@ pub const SYSROOT: &str = "sysroot";
5153

5254
/// The toplevel composefs directory path
5355
pub const COMPOSEFS: &str = "composefs";
54-
#[allow(dead_code)]
55-
pub const COMPOSEFS_MODE: Mode = Mode::from_raw_mode(0o700);
56+
57+
/// The mode for the composefs directory; this is intentionally restrictive
58+
/// to avoid leaking information.
59+
pub(crate) const COMPOSEFS_MODE: Mode = Mode::from_raw_mode(0o700);
60+
61+
/// Ensure the composefs directory exists in the given physical root
62+
/// with the correct permissions (mode 0700).
63+
pub(crate) fn ensure_composefs_dir(physical_root: &Dir) -> Result<()> {
64+
let mut db = DirBuilder::new();
65+
db.mode(COMPOSEFS_MODE.as_raw_mode());
66+
physical_root
67+
.ensure_dir_with(COMPOSEFS, &db)
68+
.context("Creating composefs directory")?;
69+
// Always update permissions, in case the directory pre-existed
70+
// with incorrect mode (e.g. from an older version of bootc).
71+
physical_root
72+
.set_permissions(
73+
COMPOSEFS,
74+
Permissions::from_mode(COMPOSEFS_MODE.as_raw_mode()),
75+
)
76+
.context("Setting composefs directory permissions")?;
77+
Ok(())
78+
}
5679

5780
/// The path to the bootc root directory, relative to the physical
5881
/// system root
@@ -399,9 +422,7 @@ impl Storage {
399422
return Ok(Arc::clone(composefs));
400423
}
401424

402-
let mut db = DirBuilder::new();
403-
db.mode(COMPOSEFS_MODE.as_raw_mode());
404-
self.physical_root.ensure_dir_with(COMPOSEFS, &db)?;
425+
ensure_composefs_dir(&self.physical_root)?;
405426

406427
// Bootstrap verity off of the ostree state. In practice this means disabled by
407428
// default right now.
@@ -430,3 +451,62 @@ impl Storage {
430451
.context("update_timestamps")
431452
}
432453
}
454+
455+
#[cfg(test)]
456+
mod tests {
457+
use super::*;
458+
459+
/// The raw mode returned by metadata includes file type bits (S_IFDIR,
460+
/// etc.) in addition to permission bits. This constant masks to only
461+
/// the permission bits (owner/group/other rwx).
462+
const PERMS: Mode = Mode::from_raw_mode(0o777);
463+
464+
#[test]
465+
fn test_ensure_composefs_dir_mode() -> Result<()> {
466+
use cap_std_ext::cap_primitives::fs::PermissionsExt as _;
467+
468+
let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
469+
470+
let assert_mode = || -> Result<()> {
471+
let perms = td.metadata(COMPOSEFS)?.permissions();
472+
let mode = Mode::from_raw_mode(perms.mode());
473+
assert_eq!(mode & PERMS, COMPOSEFS_MODE);
474+
Ok(())
475+
};
476+
477+
ensure_composefs_dir(&td)?;
478+
assert_mode()?;
479+
480+
// Calling again should be a no-op (ensure is idempotent)
481+
ensure_composefs_dir(&td)?;
482+
assert_mode()?;
483+
484+
Ok(())
485+
}
486+
487+
#[test]
488+
fn test_ensure_composefs_dir_fixes_existing() -> Result<()> {
489+
use cap_std_ext::cap_primitives::fs::PermissionsExt as _;
490+
491+
let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
492+
493+
// Create with overly permissive mode (simulating old bootc behavior)
494+
let mut db = DirBuilder::new();
495+
db.mode(0o755);
496+
td.create_dir_with(COMPOSEFS, &db)?;
497+
498+
// Verify it starts with wrong permissions
499+
let perms = td.metadata(COMPOSEFS)?.permissions();
500+
let mode = Mode::from_raw_mode(perms.mode());
501+
assert_eq!(mode & PERMS, Mode::from_raw_mode(0o755));
502+
503+
// ensure_composefs_dir should fix the permissions
504+
ensure_composefs_dir(&td)?;
505+
506+
let perms = td.metadata(COMPOSEFS)?.permissions();
507+
let mode = Mode::from_raw_mode(perms.mode());
508+
assert_eq!(mode & PERMS, COMPOSEFS_MODE);
509+
510+
Ok(())
511+
}
512+
}

0 commit comments

Comments
 (0)