diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99894b250..b28cf722e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,4 +143,4 @@ jobs: sudo podman run --rm -ti --privileged --env BOOTC_SKIP_SELINUX_HOST_CHECK=1 --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \ quay.io/centos-bootc/fedora-bootc-dev:eln bootc install to-filesystem \ --replace=alongside /target - sudo ls -ldZ / /ostree/deploy/default/deploy/* /ostree/deploy/default/deploy/*/etc + sudo ls -ldZ / /boot /ostree/deploy/default /ostree/repo/objects /ostree/deploy/default/deploy/* /ostree/deploy/default/deploy/*/etc diff --git a/lib/src/install.rs b/lib/src/install.rs index 6f929625f..4947ff582 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -38,6 +38,7 @@ use serde::{Deserialize, Serialize}; use self::baseline::InstallBlockDeviceOpts; use crate::containerenv::ContainerExecutionInfo; +use crate::lsm::selinux_label_recurse; use crate::task::Task; use crate::utils::sigpolicy_from_opts; @@ -263,21 +264,6 @@ pub(crate) struct State { pub(crate) install_config: config::InstallConfiguration, } -impl State { - // Wraps core lsm labeling functionality, conditionalizing based on source state - pub(crate) fn lsm_label( - &self, - target: &Utf8Path, - as_path: &Utf8Path, - recurse: bool, - ) -> Result<()> { - if !self.source.selinux { - return Ok(()); - } - crate::lsm::lsm_label(target, as_path, recurse) - } -} - /// Path to initially deployed version information const BOOTC_ALEPH_PATH: &str = ".bootc-aleph.json"; @@ -466,9 +452,15 @@ async fn initialize_ostree_root_from_self( let rootfs = root_setup.rootfs.as_path(); let cancellable = gio::Cancellable::NONE; - // Ensure that the physical root is labeled. - // Another implementation: https://github.com/coreos/coreos-assembler/blob/3cd3307904593b3a131b81567b13a4d0b6fe7c90/src/create_disk.sh#L295 - state.lsm_label(rootfs, "/".into(), false)?; + let sepolicy = if state.override_disable_selinux { + None + } else { + let root = ostree::gio::File::for_path("/"); + Some(ostree::SePolicy::new( + &root, + ostree::gio::Cancellable::NONE, + )?) + }; // TODO: make configurable? let stateroot = STATEROOT_DEFAULT; @@ -478,12 +470,6 @@ async fn initialize_ostree_root_from_self( ["admin", "init-fs", "--modern", rootfs.as_str()], )?; - // And also label /boot AKA xbootldr, if it exists - let bootdir = rootfs.join("boot"); - if bootdir.try_exists()? { - state.lsm_label(&bootdir, "/boot".into(), false)?; - } - // Default to avoiding grub2-mkconfig etc., but we need to use zipl on s390x. // TODO: Lower this logic into ostree proper. let bootloader = if cfg!(target_arch = "s390x") { @@ -509,8 +495,10 @@ async fn initialize_ostree_root_from_self( .cwd(rootfs_dir)? .run()?; - // Ensure everything in the ostree repo is labeled - state.lsm_label(&rootfs.join("ostree"), "/usr".into(), true)?; + if let Some(policy) = sepolicy.as_ref() { + selinux_label_recurse(policy, &root_setup.rootfs_fd, "./".into()) + .context("Labeling physical root")?; + } let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(rootfs))); sysroot.load(cancellable)?; diff --git a/lib/src/install/baseline.rs b/lib/src/install/baseline.rs index 182d361fa..ccac585f4 100644 --- a/lib/src/install/baseline.rs +++ b/lib/src/install/baseline.rs @@ -364,15 +364,10 @@ pub(crate) fn install_create_rootfs( .collect::>(); mount::mount(&rootdev, &rootfs)?; - state.lsm_label(&rootfs, "/".into(), false)?; let rootfs_fd = Dir::open_ambient_dir(&rootfs, cap_std::ambient_authority())?; let bootfs = rootfs.join("boot"); std::fs::create_dir(&bootfs).context("Creating /boot")?; - // The underlying directory on the root should be labeled - state.lsm_label(&bootfs, "/boot".into(), false)?; mount::mount(bootdev, &bootfs)?; - // And we want to label the root mount of /boot - state.lsm_label(&bootfs, "/boot".into(), false)?; // Create the EFI system partition, if applicable if let Some(esp_partno) = esp_partno { diff --git a/lib/src/lsm.rs b/lib/src/lsm.rs index 74c9feb90..9b10a7965 100644 --- a/lib/src/lsm.rs +++ b/lib/src/lsm.rs @@ -6,11 +6,13 @@ use std::process::Command; use anyhow::{Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; +use cap_std_ext::cap_std::fs::{Dir, MetadataExt}; use fn_error_context::context; #[cfg(feature = "install")] use gvariant::{aligned_bytes::TryAsAligned, Marker, Structure}; #[cfg(feature = "install")] use ostree_ext::ostree; +use rustix::fd::AsRawFd; use crate::task::Task; @@ -168,6 +170,48 @@ fn selinux_label_for_path(target: &str) -> Result { Ok(label.trim().to_string()) } +fn selinux_set_one_label( + policy: &ostree::SePolicy, + root: &Dir, + path: &Utf8Path, + mode: u32, +) -> Result<()> { + let label = policy.label(path.as_str(), mode, ostree::gio::Cancellable::NONE)?; + if let Some(label) = label { + let selfpath = format!("/proc/self/fd/{}", root.as_raw_fd()); + rustix::fs::lsetxattr( + &selfpath, + SELINUX_XATTR, + label.as_bytes(), + rustix::fs::XattrFlags::empty(), + )?; + } + Ok(()) +} + +pub(crate) fn selinux_label_recurse( + policy: &ostree::SePolicy, + root: &Dir, + path: &Utf8Path, +) -> Result<()> { + let meta = root.symlink_metadata(path)?; + selinux_set_one_label(policy, root, path, meta.mode())?; + if meta.is_dir() { + for ent in root.read_dir(path)? { + let ent = ent?; + let name = ent.file_name(); + let name = if let Some(name) = name.to_str() { + name + } else { + anyhow::bail!("Invalid filename: {name:?}"); + }; + let path = path.join(name); + selinux_label_recurse(policy, root, &path)?; + } + } + Ok(()) +} + // Write filesystem labels (currently just for SELinux) #[context("Labeling {as_path}")] pub(crate) fn lsm_label(target: &Utf8Path, as_path: &Utf8Path, recurse: bool) -> Result<()> {