Skip to content

Commit d9a6064

Browse files
committed
lsm: Use walk API with noxdev for recursive SELinux relabeling
Rewrite ensure_dir_labeled_recurse to use the cap_std_ext walk API with noxdev instead of manually recursing through directories. This prevents the recursive labeling from crossing mount point boundaries, avoiding failures when pseudo-filesystems like sysfs are mounted under the target root. Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: John Eckersberg <jeckersb@redhat.com>
1 parent c3c1602 commit d9a6064

1 file changed

Lines changed: 46 additions & 24 deletions

File tree

crates/lib/src/lsm.rs

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -387,14 +387,20 @@ pub(crate) fn relabel_recurse(
387387
relabel_recurse_inner(root, &mut path, as_path.as_mut(), policy)
388388
}
389389

390-
/// A wrapper for creating a directory, also optionally setting a SELinux label.
391-
/// The provided `skip` parameter is a device/inode that we will ignore (and not traverse).
390+
/// Recursively ensure all files under a directory have SELinux labels.
391+
/// Uses the `walk` API with `noxdev` to avoid crossing mount point boundaries
392+
/// (e.g. into sysfs, procfs, etc.).
393+
/// The provided `skip` parameter is a device/inode pair that we will ignore
394+
/// (and not traverse into).
392395
pub(crate) fn ensure_dir_labeled_recurse(
393396
root: &Dir,
394397
path: &mut Utf8PathBuf,
395398
policy: &ostree::SePolicy,
396399
skip: Option<(libc::dev_t, libc::ino64_t)>,
397400
) -> Result<()> {
401+
use cap_std_ext::dirext::WalkConfiguration;
402+
use std::ops::ControlFlow;
403+
398404
// Juggle the cap-std requirement for relative paths vs the libselinux
399405
// requirement for absolute paths by special casing the empty string "" as "."
400406
// just for the initial directory enumeration.
@@ -406,43 +412,59 @@ pub(crate) fn ensure_dir_labeled_recurse(
406412

407413
let mut n = 0u64;
408414

415+
// Label the starting directory itself; the walk API only visits children.
409416
let metadata = root.symlink_metadata(path_for_read)?;
410417
match ensure_labeled(root, path, &metadata, policy)? {
418+
SELinuxLabelState::Unsupported => return Ok(()),
411419
SELinuxLabelState::Unlabeled => {
412420
n += 1;
413421
}
414-
SELinuxLabelState::Unsupported => return Ok(()),
415422
SELinuxLabelState::Labeled => {}
416423
}
417-
418-
for ent in root.read_dir(path_for_read)? {
419-
let ent = ent?;
420-
let metadata = ent.metadata()?;
421-
if let Some((skip_dev, skip_ino)) = skip.as_ref().copied() {
422-
if (metadata.dev(), metadata.ino()) == (skip_dev, skip_ino) {
423-
tracing::debug!("Skipping dev={skip_dev} inode={skip_ino}");
424-
continue;
424+
let config = WalkConfiguration::default()
425+
.noxdev()
426+
.path_base(path_for_read.as_std_path());
427+
428+
root.open_dir(path_for_read)?
429+
.walk::<_, anyhow::Error>(&config, |component| {
430+
let metadata = component.entry.metadata()?;
431+
432+
// Check if this entry should be skipped
433+
if let Some((skip_dev, skip_ino)) = skip {
434+
if (metadata.dev(), metadata.ino()) == (skip_dev, skip_ino) {
435+
tracing::debug!("Skipping dev={skip_dev} inode={skip_ino}");
436+
// For directories, Break skips traversal into the directory
437+
// but continues with the next sibling. For non-directories,
438+
// Break would skip all remaining siblings, so use Continue
439+
// to skip only this entry.
440+
if component.file_type.is_dir() {
441+
return Ok(ControlFlow::Break(()));
442+
} else {
443+
return Ok(ControlFlow::Continue(()));
444+
}
445+
}
425446
}
426-
}
427-
let name = ent.file_name();
428-
let name = name
429-
.to_str()
430-
.ok_or_else(|| anyhow::anyhow!("Invalid non-UTF-8 filename: {name:?}"))?;
431-
path.push(name);
432447

433-
if metadata.is_dir() {
434-
ensure_dir_labeled_recurse(root, path, policy, skip)?;
435-
} else {
448+
let path = Utf8Path::from_path(component.path)
449+
.ok_or_else(|| anyhow::anyhow!("Invalid non-UTF-8 path: {:?}", component.path))?;
450+
436451
match ensure_labeled(root, path, &metadata, policy)? {
437452
SELinuxLabelState::Unlabeled => {
438453
n += 1;
439454
}
440-
SELinuxLabelState::Unsupported => break,
455+
// We check for Unsupported on the starting directory above,
456+
// and the walk uses noxdev to stay on the same filesystem,
457+
// so hitting Unsupported here would be unexpected.
458+
SELinuxLabelState::Unsupported => {
459+
anyhow::bail!(
460+
"Unexpected SELinuxLabelState::Unsupported during walk at {path}"
461+
);
462+
}
441463
SELinuxLabelState::Labeled => {}
442464
}
443-
}
444-
path.pop();
445-
}
465+
466+
Ok(ControlFlow::Continue(()))
467+
})?;
446468

447469
if n > 0 {
448470
tracing::debug!("Relabeled {n} objects in {path}");

0 commit comments

Comments
 (0)