@@ -763,6 +763,64 @@ fn has_soft_reboot_capability(deployment: Option<&crate::spec::BootEntry>) -> bo
763763 deployment. map ( |d| d. soft_reboot_capable ) . unwrap_or ( false )
764764}
765765
766+ /// Check if SELinux policy differs between booted and target deployments.
767+ /// Returns an error if SELinux is enabled and the policies differ.
768+ #[ context( "Checking SELinux policy compatibility with soft reboot" ) ]
769+ fn check_selinux_policy_for_soft_reboot (
770+ sysroot : & SysrootLock ,
771+ booted_deployment : & ostree:: Deployment ,
772+ target_deployment : & ostree:: Deployment ,
773+ ) -> Result < ( ) > {
774+ // Only check if SELinux is enabled
775+ if !crate :: lsm:: selinux_enabled ( ) ? {
776+ return Ok ( ( ) ) ;
777+ }
778+
779+ // Load SELinux policy from booted deployment
780+ let booted_fd = crate :: utils:: deployment_fd ( sysroot, booted_deployment) ?;
781+ let booted_policy = crate :: lsm:: new_sepolicy_at ( & booted_fd) ?;
782+
783+ // Load SELinux policy from target deployment
784+ let target_fd = crate :: utils:: deployment_fd ( sysroot, target_deployment) ?;
785+ let target_policy = crate :: lsm:: new_sepolicy_at ( & target_fd) ?;
786+
787+ // Ensure both deployments have the same policy presence state
788+ // If one has a policy and the other doesn't, soft reboot is unsafe
789+ if booted_policy. is_some ( ) != target_policy. is_some ( ) {
790+ anyhow:: bail!(
791+ "SELinux policy presence differs between booted ({}) and target ({}) deployments. \
792+ Soft reboot cannot be used because SELinux policy is not reloaded across soft reboots. \
793+ Please use a full reboot instead.",
794+ if booted_policy. is_some( ) { "present" } else { "absent" } ,
795+ if target_policy. is_some( ) { "present" } else { "absent" }
796+ ) ;
797+ }
798+
799+ // If both deployments have policies, they must match
800+ if let ( Some ( booted_policy) , Some ( target_policy) ) = ( booted_policy, target_policy) {
801+ // Compare policy checksums
802+ // SAFETY: new_sepolicy_at filters out policies without checksums, so these should always be Some
803+ let booted_csum = booted_policy
804+ . csum ( )
805+ . expect ( "booted policy should have checksum" ) ;
806+ let target_csum = target_policy
807+ . csum ( )
808+ . expect ( "target policy should have checksum" ) ;
809+
810+ if booted_csum != target_csum {
811+ anyhow:: bail!(
812+ "SELinux policy differs between booted and target deployments (booted: {:?}, target: {:?}). \
813+ Soft reboot cannot be used because SELinux policy is not reloaded across soft reboots. \
814+ Please use a full reboot instead.",
815+ booted_csum,
816+ target_csum
817+ ) ;
818+ }
819+ }
820+
821+ Ok ( ( ) )
822+ }
823+
766824/// Prepare a soft reboot for the given deployment
767825#[ context( "Preparing soft reboot" ) ]
768826fn prepare_soft_reboot ( sysroot : & SysrootLock , deployment : & ostree:: Deployment ) -> Result < ( ) > {
@@ -835,6 +893,11 @@ fn soft_reboot_staged(sysroot: &SysrootLock) -> Result<()> {
835893 . find ( |d| d. is_staged ( ) )
836894 . ok_or_else ( || anyhow:: anyhow!( "Failed to find staged deployment" ) ) ?;
837895
896+ // Check SELinux policy compatibility before allowing soft reboot
897+ if let Some ( booted_deployment) = sysroot. booted_deployment ( ) {
898+ check_selinux_policy_for_soft_reboot ( sysroot, & booted_deployment, staged_deployment) ?;
899+ }
900+
838901 prepare_soft_reboot ( sysroot, staged_deployment) ?;
839902 Ok ( ( ) )
840903}
@@ -849,6 +912,13 @@ fn soft_reboot_rollback(booted_ostree: &BootedOstree<'_>) -> Result<()> {
849912 . first ( )
850913 . ok_or_else ( || anyhow:: anyhow!( "No rollback deployment found!" ) ) ?;
851914
915+ // Check SELinux policy compatibility before allowing soft reboot
916+ check_selinux_policy_for_soft_reboot (
917+ booted_ostree. sysroot ,
918+ & booted_ostree. deployment ,
919+ target_deployment,
920+ ) ?;
921+
852922 prepare_soft_reboot ( booted_ostree. sysroot , target_deployment)
853923}
854924
0 commit comments