Skip to content

Commit 39b7b26

Browse files
committed
install: Skip immutable deployment checkouts in final SELinux relabeling
Ostree sets the immutable ext4 attribute on each deployment checkout directory, which causes lsetfilecon() to return EPERM during the final SELinux relabeling pass even though those files are already correctly labeled by the earlier composefs import pass. Rather than skipping the entire ostree/deploy subtree (which would leave stateroot metadata and var directories unlabeled), enumerate the actual checkout directories under ostree/deploy/<stateroot>/deploy/ and skip only those immutable roots by dev/ino. Also generalise ensure_dir_labeled_recurse to accept a slice of (dev, ino) pairs to skip rather than a single Option, so multiple checkout directories can be excluded in one pass. Assisted-by: OpenCode (Claude Sonnet 4.6) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent c29c97e commit 39b7b26

2 files changed

Lines changed: 43 additions & 17 deletions

File tree

crates/lib/src/install.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,7 +1208,7 @@ async fn install_container(
12081208
&root_setup.physical_root,
12091209
&mut pathbuf,
12101210
policy,
1211-
Some(deployment_root_devino),
1211+
&[deployment_root_devino],
12121212
)
12131213
.with_context(|| format!("Recursive SELinux relabeling of {d}"))?;
12141214
}
@@ -2093,7 +2093,34 @@ async fn install_to_filesystem_impl(
20932093
if let Some(policy) = state.load_policy()? {
20942094
tracing::info!("Performing final SELinux relabeling of physical root");
20952095
let mut path = Utf8PathBuf::from("");
2096-
crate::lsm::ensure_dir_labeled_recurse(&rootfs.physical_root, &mut path, &policy, None)
2096+
// Ostree sets the immutable ext4 attribute on each deployment checkout
2097+
// directory, which causes lsetfilecon() to return EPERM. Those dirs
2098+
// are already correctly labeled by the earlier composefs import pass,
2099+
// so skip each checkout by dev/ino. We enumerate them directly rather
2100+
// than skipping the whole ostree/deploy subtree so that the stateroot
2101+
// and var directories (which are not immutable) still get labeled.
2102+
let mut skip: Vec<(libc::dev_t, libc::ino64_t)> = Vec::new();
2103+
let deploy_dir = "ostree/deploy";
2104+
if let Some(statered_dir) = rootfs.physical_root.open_dir_optional(deploy_dir)? {
2105+
for stateroot in statered_dir.entries()? {
2106+
let stateroot = stateroot?;
2107+
if !stateroot.file_type()?.is_dir() {
2108+
continue;
2109+
}
2110+
let deploy_subdir = stateroot.open_dir()?.open_dir_optional("deploy")?;
2111+
if let Some(deploy_subdir) = deploy_subdir {
2112+
for checkout in deploy_subdir.entries()? {
2113+
let checkout = checkout?;
2114+
if !checkout.file_type()?.is_dir() {
2115+
continue;
2116+
}
2117+
let m = checkout.metadata()?;
2118+
skip.push((m.dev() as libc::dev_t, m.ino() as libc::ino64_t));
2119+
}
2120+
}
2121+
}
2122+
}
2123+
crate::lsm::ensure_dir_labeled_recurse(&rootfs.physical_root, &mut path, &policy, &skip)
20972124
.context("Final SELinux relabeling of physical root")?;
20982125
} else {
20992126
tracing::debug!("Skipping final SELinux relabel (SELinux is disabled)");

crates/lib/src/lsm.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,13 @@ pub(crate) fn relabel_recurse(
391391
/// Uses the `walk` API with `noxdev` and `skip_mountpoints` to avoid crossing
392392
/// mount point boundaries
393393
/// (e.g. into sysfs, procfs, etc.).
394-
/// The provided `skip` parameter is a device/inode pair that we will ignore
395-
/// (and not traverse into).
394+
/// The provided `skip` parameter is a list of device/inode pairs to ignore
395+
/// (and not traverse into). Pass an empty slice to skip nothing.
396396
pub(crate) fn ensure_dir_labeled_recurse(
397397
root: &Dir,
398398
path: &mut Utf8PathBuf,
399399
policy: &ostree::SePolicy,
400-
skip: Option<(libc::dev_t, libc::ino64_t)>,
400+
skip: &[(libc::dev_t, libc::ino64_t)],
401401
) -> Result<()> {
402402
use cap_std_ext::dirext::WalkConfiguration;
403403
use std::ops::ControlFlow;
@@ -432,18 +432,17 @@ pub(crate) fn ensure_dir_labeled_recurse(
432432
let metadata = component.entry.metadata()?;
433433

434434
// Check if this entry should be skipped
435-
if let Some((skip_dev, skip_ino)) = skip {
436-
if (metadata.dev(), metadata.ino()) == (skip_dev, skip_ino) {
437-
tracing::debug!("Skipping dev={skip_dev} inode={skip_ino}");
438-
// For directories, Break skips traversal into the directory
439-
// but continues with the next sibling. For non-directories,
440-
// Break would skip all remaining siblings, so use Continue
441-
// to skip only this entry.
442-
if component.file_type.is_dir() {
443-
return Ok(ControlFlow::Break(()));
444-
} else {
445-
return Ok(ControlFlow::Continue(()));
446-
}
435+
let devino = (metadata.dev() as libc::dev_t, metadata.ino() as libc::ino64_t);
436+
if skip.contains(&devino) {
437+
tracing::debug!("Skipping dev={} inode={}", devino.0, devino.1);
438+
// For directories, Break skips traversal into the directory
439+
// but continues with the next sibling. For non-directories,
440+
// Break would skip all remaining siblings, so use Continue
441+
// to skip only this entry.
442+
if component.file_type.is_dir() {
443+
return Ok(ControlFlow::Break(()));
444+
} else {
445+
return Ok(ControlFlow::Continue(()));
447446
}
448447
}
449448

0 commit comments

Comments
 (0)