From 9607e46aaf42d5bf00df8b5ca79618456244bd8c Mon Sep 17 00:00:00 2001 From: Andrew Dunn Date: Mon, 16 Mar 2026 09:40:42 -0400 Subject: [PATCH] initramfs: Inherit SELinux label on transient root tmpfs When root.transient is enabled, the tmpfs used as the overlay upper layer has no SELinux context set, so its root directory gets the default tmpfs_t label. This propagates to the overlay mountpoint at /, causing services like systemd-networkd to refuse to start because they expect root_t. Fix this by reading the SELinux label from the composefs lower layer via fgetxattr(security.selinux) and setting it as rootcontext on the tmpfs before creating the overlay. This ensures the overlay root inherits the correct label from the base filesystem. The rootcontext mount option sets only the root inode's label, preserving per-file labeling from SELinux policy via type_transition rules. This is preferable to context= which would force a uniform label on all inodes. When SELinux is not enabled, fgetxattr returns an error and no context is set, preserving the existing behavior. Note: the ostree backend (otcore-prepare-root.c in ostreedev/ostree) has the same issue but uses /run as the tmpfs backing store rather than creating its own tmpfs. That fix would need to go through libcomposefs mount options or the ostree repo directly. Closes: #1992 AI-Assisted: yes AI-Tools: GitLab Duo, OpenCode Signed-off-by: Andrew Dunn --- crates/initramfs/Cargo.toml | 1 + crates/initramfs/src/lib.rs | 29 ++++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/crates/initramfs/Cargo.toml b/crates/initramfs/Cargo.toml index c82b8def0..32dbac6a0 100644 --- a/crates/initramfs/Cargo.toml +++ b/crates/initramfs/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] anyhow.workspace = true +cap-std-ext.workspace = true clap = { workspace = true, features = ["std", "help", "usage", "derive"] } libc.workspace = true rustix.workspace = true diff --git a/crates/initramfs/src/lib.rs b/crates/initramfs/src/lib.rs index e21b9ddb6..704eea3ca 100644 --- a/crates/initramfs/src/lib.rs +++ b/crates/initramfs/src/lib.rs @@ -1,7 +1,7 @@ //! Mount helpers for bootc-initramfs use std::{ - ffi::OsString, + ffi::{CString, OsString}, fmt::Debug, io::ErrorKind, os::fd::{AsFd, AsRawFd, OwnedFd}, @@ -9,6 +9,8 @@ use std::{ }; use anyhow::{Context, Result}; +use cap_std_ext::cap_std::fs::Dir; +use cap_std_ext::dirext::CapStdExtDirExt; use clap::Parser; use rustix::{ fs::{CWD, Mode, OFlags, major, minor, mkdirat, openat, stat, symlink}, @@ -19,6 +21,7 @@ use rustix::{ }, path, }; + use serde::Deserialize; use cfsctl::composefs; @@ -202,9 +205,21 @@ fn bind_mount(fd: impl AsFd, path: &str) -> Result { Ok(res?) } -#[context("Mounting tmpfs")] -fn mount_tmpfs() -> Result { +/// Mount a tmpfs, inheriting the SELinux label from the base filesystem +/// if provided. See . +#[context("Mounting tmpfs for overlay")] +fn mount_tmpfs_for_overlay(base: Option) -> Result { let tmpfs = FsHandle::open("tmpfs")?; + + if let Some(base_fd) = base { + let base_dir = Dir::reopen_dir(&base_fd.as_fd())?; + if let Some(label) = base_dir.getxattr(".", "security.selinux")? { + if let Ok(cstr) = CString::new(label) { + fsconfig_set_string(tmpfs.as_fd(), "rootcontext", &cstr)?; + } + } + } + fsconfig_create(tmpfs.as_fd())?; Ok(fsmount( tmpfs.as_fd(), @@ -239,16 +254,20 @@ fn overlay_state( mount_at_wrapper(fs, base, ".").context("Moving mount") } -/// Mounts a transient overlayfs with passed in fd as the lowerdir +/// Mounts a transient overlayfs with passed in fd as the lowerdir. +/// +/// The tmpfs used for the overlay upper layer inherits the SELinux label +/// from the base filesystem to prevent label mismatches (see #1992). #[context("Mounting transient overlayfs")] pub fn overlay_transient( base: impl AsFd, mode: Option, mount_attr_flags: Option, ) -> Result<()> { + let tmpfs = mount_tmpfs_for_overlay(Some(&base))?; overlay_state( base, - prepare_mount(mount_tmpfs()?)?, + prepare_mount(tmpfs)?, "transient", mode, mount_attr_flags,