@@ -644,20 +644,7 @@ impl Wrappable for CreateRequest {
644644 fs:: write ( "/proc/self/oom_score_adj" , score. to_string ( ) ) ?;
645645 }
646646
647- // We need to toggle SECBIT before we change UID/GID,
648- // or else changing UID/GID may cause us to lose the capabilities
649- // we need to explicitly drop capabilities later on.
650- set_keep_caps ( ) ?;
651- // Set these *first*, before we exec. Otherwise
652- // we may not be able to switch after dropping caps.
653- apply_gid_uid (
654- self . exec . gid ,
655- self . exec . uid ,
656- self . exec . supplemental_gids . as_ref ( ) ,
657- ) ?;
658- // Now, we can synchronize effective/inherited/permitted caps
659- // as a final step.
660- apply_capabilities ( self . capabilities . as_ref ( ) ) ?;
647+ preexec_prep ( & self . exec , self . capabilities . as_ref ( ) ) ?;
661648
662649 debug ! ( "ready to launch workload" ) ;
663650 self . exec . execute ( )
@@ -844,8 +831,6 @@ impl Wrappable for AttachRequest {
844831 warn ! ( "unable to set process limits" ) ;
845832 }
846833
847- apply_capabilities ( self . capabilities . as_ref ( ) ) ?;
848-
849834 // Ensure the process receives the desired out-of-memory score adjustment.
850835 if let Some ( score) = self . exec . oom_score_adj {
851836 fs:: write ( "/proc/self/oom_score_adj" , score. to_string ( ) ) ?;
@@ -854,11 +839,7 @@ impl Wrappable for AttachRequest {
854839 debug ! ( "all namespaces joined -- forking child" ) ;
855840 fork_and_wait ( ) ?;
856841
857- apply_gid_uid (
858- self . exec . gid ,
859- self . exec . uid ,
860- self . exec . supplemental_gids . as_ref ( ) ,
861- ) ?;
842+ preexec_prep ( & self . exec , self . capabilities . as_ref ( ) ) ?;
862843
863844 self . exec . execute ( )
864845 }
@@ -989,14 +970,42 @@ fn apply_capabilities(capabilities: Option<&Capabilities>) -> Result<()> {
989970 Ok ( ( ) )
990971}
991972
973+ /// The ordered final prep that runs after namespaces are set up and right
974+ /// before execve(2). The sequence MUST be:
975+ ///
976+ /// 1. `set_keep_caps` (SECBIT_NO_SETUID_FIXUP) so the kernel does not clear
977+ /// the permitted/effective cap sets on a uid 0 <-> non-zero transition.
978+ /// 2. `apply_gid_uid` to drop primary GID, supplemental GIDs, and UID.
979+ /// 3. `apply_capabilities` to apply the workload's final cap raises/drops.
980+ ///
981+ /// Both `CreateRequest::wrap` and `AttachRequest::wrap` must run this
982+ /// sequence.
983+ fn preexec_prep ( exec : & ExecutableSpec , capabilities : Option < & Capabilities > ) -> Result < ( ) > {
984+ // We need to toggle SECBIT before we change UID/GID,
985+ // or else changing UID/GID may cause us to lose the capabilities
986+ // we need to explicitly drop capabilities later on.
987+ set_keep_caps ( ) ?;
988+ // Set these *first*, before we exec. Otherwise
989+ // we may not be able to switch after dropping caps.
990+ apply_gid_uid ( exec. gid , exec. uid , exec. supplemental_gids . as_ref ( ) ) ?;
991+ // Now, we can synchronize effective/inherited/permitted caps
992+ // as a final step.
993+ apply_capabilities ( capabilities) ?;
994+ Ok ( ( ) )
995+ }
996+
992997#[ cfg( test) ]
993998mod tests {
994- use crate :: config:: CreateRequest ;
999+ use super :: { apply_capabilities, apply_gid_uid, preexec_prep} ;
1000+ use crate :: caps:: { CapabilityBit , get_caps} ;
1001+ use crate :: config:: { Capabilities , CreateRequest , ExecutableSpec } ;
9951002 use crate :: namespace:: Namespace ;
9961003 use crate :: unshare:: unshare;
9971004 use nix:: sys:: wait:: { WaitStatus , waitpid} ;
9981005 use nix:: unistd:: { ForkResult , fork, geteuid} ;
9991006
1007+ const NOBODY_UID : u32 = 65534 ;
1008+
10001009 /// Run a closure in a forked child. Returns true if the child exits 0.
10011010 /// Uses _exit() to skip Rust destructors in the child.
10021011 unsafe fn in_child < F : FnOnce ( ) -> i32 > ( f : F ) -> bool {
@@ -1151,4 +1160,76 @@ mod tests {
11511160 } )
11521161 } ) ;
11531162 }
1163+
1164+ #[ test]
1165+ fn root_only_apply_exec_prep_preserves_raised_cap_across_uid_change ( ) {
1166+ if !is_root ( ) {
1167+ return ;
1168+ }
1169+ assert ! ( unsafe {
1170+ in_child( || {
1171+ let exec = ExecutableSpec {
1172+ uid: Some ( NOBODY_UID ) ,
1173+ gid: Some ( NOBODY_UID ) ,
1174+ ..Default :: default ( )
1175+ } ;
1176+ let caps = Capabilities {
1177+ raise: Some ( vec![ "CAP_NET_RAW" . to_string( ) ] ) ,
1178+ raise_ambient: None ,
1179+ drop: None ,
1180+ } ;
1181+ if preexec_prep( & exec, Some ( & caps) ) . is_err( ) {
1182+ return 1 ;
1183+ }
1184+ let Ok ( now) = get_caps( ) else {
1185+ return 2 ;
1186+ } ;
1187+ if !CapabilityBit :: NetRaw . get_from( now. permitted) {
1188+ return 3 ;
1189+ }
1190+ if !CapabilityBit :: NetRaw . get_from( now. effective) {
1191+ return 4 ;
1192+ }
1193+ 0
1194+ } )
1195+ } ) ;
1196+ }
1197+
1198+ #[ test]
1199+ fn root_only_raise_then_setuid_without_keep_caps_drops_cap ( ) {
1200+ if !is_root ( ) {
1201+ return ;
1202+ }
1203+ assert ! ( unsafe {
1204+ in_child( || {
1205+ let caps = Capabilities {
1206+ raise: Some ( vec![ "CAP_NET_RAW" . to_string( ) ] ) ,
1207+ raise_ambient: None ,
1208+ drop: None ,
1209+ } ;
1210+ if apply_capabilities( Some ( & caps) ) . is_err( ) {
1211+ return 1 ;
1212+ }
1213+ let Ok ( before) = get_caps( ) else {
1214+ return 2 ;
1215+ } ;
1216+ if !CapabilityBit :: NetRaw . get_from( before. effective) {
1217+ return 3 ;
1218+ }
1219+ if apply_gid_uid( Some ( NOBODY_UID ) , Some ( NOBODY_UID ) , None ) . is_err( ) {
1220+ return 4 ;
1221+ }
1222+ let Ok ( after) = get_caps( ) else {
1223+ return 5 ;
1224+ } ;
1225+ if CapabilityBit :: NetRaw . get_from( after. permitted) {
1226+ return 6 ;
1227+ }
1228+ if CapabilityBit :: NetRaw . get_from( after. effective) {
1229+ return 7 ;
1230+ }
1231+ 0
1232+ } )
1233+ } ) ;
1234+ }
11541235}
0 commit comments