mirror of
https://github.com/containers/bootc.git
synced 2026-02-05 06:45:13 +01:00
Soft reboot: Detect SELinux policy deltas
Add check to prevent soft reboot when SELinux policies differ between booted and target deployments, since policy is not reloaded across soft reboots. Assisted-by: Cursor (Auto) Signed-off-by: gursewak1997 <gursmangat@gmail.com>
This commit is contained in:
committed by
Colin Walters
parent
f49a6bac30
commit
3ad82d0ee6
@@ -93,7 +93,45 @@ impl From<ImageReference> for OstreeImageReference {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if SELinux policies are compatible between booted and target deployments.
|
||||
/// Returns false if SELinux is enabled and the policies differ or have mismatched presence.
|
||||
fn check_selinux_policy_compatible(
|
||||
sysroot: &SysrootLock,
|
||||
booted_deployment: &ostree::Deployment,
|
||||
target_deployment: &ostree::Deployment,
|
||||
) -> Result<bool> {
|
||||
// Only check if SELinux is enabled
|
||||
if !crate::lsm::selinux_enabled()? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let booted_fd = crate::utils::deployment_fd(sysroot, booted_deployment)
|
||||
.context("Failed to get file descriptor for booted deployment")?;
|
||||
let booted_policy = crate::lsm::new_sepolicy_at(&booted_fd)
|
||||
.context("Failed to load SELinux policy from booted deployment")?;
|
||||
let target_fd = crate::utils::deployment_fd(sysroot, target_deployment)
|
||||
.context("Failed to get file descriptor for target deployment")?;
|
||||
let target_policy = crate::lsm::new_sepolicy_at(&target_fd)
|
||||
.context("Failed to load SELinux policy from target deployment")?;
|
||||
|
||||
let booted_csum = booted_policy.and_then(|p| p.csum());
|
||||
let target_csum = target_policy.and_then(|p| p.csum());
|
||||
|
||||
match (booted_csum, target_csum) {
|
||||
(None, None) => Ok(true), // Both absent, compatible
|
||||
(Some(_), None) | (None, Some(_)) => {
|
||||
// Incompatible: one has policy, other doesn't
|
||||
Ok(false)
|
||||
}
|
||||
(Some(booted_csum), Some(target_csum)) => {
|
||||
// Both have policies, checksums must match
|
||||
Ok(booted_csum == target_csum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a deployment has soft reboot capability
|
||||
// TODO: Lower SELinux policy check into ostree's deployment_can_soft_reboot API
|
||||
fn has_soft_reboot_capability(sysroot: &SysrootLock, deployment: &ostree::Deployment) -> bool {
|
||||
if !ostree_ext::systemd_has_soft_reboot() {
|
||||
return false;
|
||||
@@ -113,7 +151,22 @@ fn has_soft_reboot_capability(sysroot: &SysrootLock, deployment: &ostree::Deploy
|
||||
return false;
|
||||
}
|
||||
|
||||
sysroot.deployment_can_soft_reboot(deployment)
|
||||
if !sysroot.deployment_can_soft_reboot(deployment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check SELinux policy compatibility with booted deployment
|
||||
// Block soft reboot if SELinux policies differ, as policy is not reloaded across soft reboots
|
||||
if let Some(booted_deployment) = sysroot.booted_deployment() {
|
||||
// deployment_fd should not fail for valid deployments
|
||||
if !check_selinux_policy_compatible(sysroot, &booted_deployment, deployment)
|
||||
.expect("deployment_fd should not fail for valid deployments")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Parse an ostree origin file (a keyfile) and extract the targeted
|
||||
|
||||
@@ -119,3 +119,14 @@ execute:
|
||||
how: fmf
|
||||
test:
|
||||
- /tmt/tests/test-28-factory-reset
|
||||
|
||||
/test-29-soft-reboot-selinux-policy:
|
||||
summary: Test soft reboot with SELinux policy changes
|
||||
discover:
|
||||
how: fmf
|
||||
test:
|
||||
- /tmt/tests/test-29-soft-reboot-selinux-policy
|
||||
adjust:
|
||||
- when: running_env != image_mode
|
||||
enabled: false
|
||||
because: tmt-reboot does not work with systemd reboot in testing farm environment (see bug-soft-reboot.md)
|
||||
|
||||
104
tmt/tests/booted/test-soft-reboot-selinux-policy.nu
Normal file
104
tmt/tests/booted/test-soft-reboot-selinux-policy.nu
Normal file
@@ -0,0 +1,104 @@
|
||||
# Verify that soft reboot is blocked when SELinux policies differ
|
||||
use std assert
|
||||
use tap.nu
|
||||
|
||||
let soft_reboot_capable = "/usr/lib/systemd/system/soft-reboot.target" | path exists
|
||||
if not $soft_reboot_capable {
|
||||
echo "Skipping, system is not soft reboot capable"
|
||||
return
|
||||
}
|
||||
|
||||
# Check if SELinux is enabled
|
||||
let selinux_enabled = "/sys/fs/selinux/enforce" | path exists
|
||||
if not $selinux_enabled {
|
||||
echo "Skipping, SELinux is not enabled"
|
||||
return
|
||||
}
|
||||
|
||||
# This code runs on *each* boot.
|
||||
bootc status
|
||||
|
||||
# Run on the first boot
|
||||
def initial_build [] {
|
||||
tap begin "Build base image and test soft reboot with SELinux policy change"
|
||||
|
||||
let td = mktemp -d
|
||||
cd $td
|
||||
|
||||
bootc image copy-to-storage
|
||||
|
||||
# Create a derived container that installs a custom SELinux policy module
|
||||
# Installing a policy module will change the compiled policy checksum
|
||||
# Following Colin's suggestion and the composefs-rs example
|
||||
# We create a minimal policy module and install it
|
||||
"FROM localhost/bootc
|
||||
# Install tools needed to build and install SELinux policy modules
|
||||
RUN dnf install -y selinux-policy-devel checkpolicy policycoreutils
|
||||
|
||||
# Create a minimal SELinux policy module that will change the policy checksum
|
||||
# We install it to ensure it's part of the deployment filesystem
|
||||
RUN <<EORUN
|
||||
set -eux
|
||||
mkdir -p /tmp/bootc-test-policy
|
||||
cd /tmp/bootc-test-policy
|
||||
echo 'module bootc_test_policy 1.0;' > bootc_test_policy.te
|
||||
echo 'require {' >> bootc_test_policy.te
|
||||
echo ' type unconfined_t;' >> bootc_test_policy.te
|
||||
echo ' class file { read write };' >> bootc_test_policy.te
|
||||
echo '}' >> bootc_test_policy.te
|
||||
echo 'type bootc_test_t;' >> bootc_test_policy.te
|
||||
checkmodule -M -m -o bootc_test_policy.mod bootc_test_policy.te
|
||||
semodule_package -o bootc_test_policy.pp -m bootc_test_policy.mod
|
||||
semodule -i bootc_test_policy.pp
|
||||
rm -rf /tmp/bootc-test-policy
|
||||
# Clean up dnf cache and logs, and SELinux policy generation artifacts to satisfy lint checks
|
||||
dnf clean all
|
||||
rm -rf /var/log/dnf* /var/log/hawkey.log /var/log/rhsm
|
||||
rm -rf /var/cache/dnf /var/lib/dnf
|
||||
rm -rf /var/lib/sepolgen /var/lib/rhsm /var/cache/ldconfig
|
||||
EORUN
|
||||
" | save Dockerfile
|
||||
|
||||
# Build the derived image
|
||||
podman build --quiet -t localhost/bootc-derived-policy .
|
||||
|
||||
# Verify soft reboot preparation hasn't happened yet
|
||||
assert (not ("/run/nextroot" | path exists))
|
||||
|
||||
# Try to soft reboot - this should fail because policies differ
|
||||
bootc switch --soft-reboot=auto --transport containers-storage localhost/bootc-derived-policy
|
||||
let st = bootc status --json | from json
|
||||
|
||||
# Verify staged deployment exists
|
||||
assert ($st.status.staged != null) "Expected staged deployment to exist"
|
||||
|
||||
# The staged deployment should NOT be soft-reboot capable because policies differ
|
||||
assert (not $st.status.staged.softRebootCapable) "Expected soft reboot to be blocked due to SELinux policy difference, but softRebootCapable is true"
|
||||
|
||||
# Verify soft reboot preparation didn't happen
|
||||
assert (not ("/run/nextroot" | path exists)) "Soft reboot should not be prepared when policies differ"
|
||||
|
||||
# Do a full reboot
|
||||
tmt-reboot
|
||||
}
|
||||
|
||||
# The second boot; verify we're in the derived image
|
||||
def second_boot [] {
|
||||
tap begin "Verify deployment with different SELinux policy"
|
||||
|
||||
# Verify we're in the new deployment
|
||||
let st = bootc status --json | from json
|
||||
let booted = $st.status.booted.image
|
||||
assert ($booted.image.image | str contains "bootc-derived-policy") $"Expected booted image to contain 'bootc-derived-policy', got: ($booted.image.image)"
|
||||
|
||||
tap ok
|
||||
}
|
||||
|
||||
def main [] {
|
||||
# See https://tmt.readthedocs.io/en/stable/stories/features.html#reboot-during-test
|
||||
match $env.TMT_REBOOT_COUNT? {
|
||||
null | "0" => initial_build,
|
||||
"1" => second_boot,
|
||||
$o => { error make { msg: $"Invalid TMT_REBOOT_COUNT ($o)" } },
|
||||
}
|
||||
}
|
||||
3
tmt/tests/test-29-soft-reboot-selinux-policy.fmf
Normal file
3
tmt/tests/test-29-soft-reboot-selinux-policy.fmf
Normal file
@@ -0,0 +1,3 @@
|
||||
summary: Test soft reboot with SELinux policy changes
|
||||
test: nu booted/test-soft-reboot-selinux-policy.nu
|
||||
duration: 30m
|
||||
Reference in New Issue
Block a user