Skip to content

Commit 24f2716

Browse files
committed
Fix ordering for AttachRequest as well
1 parent c9086d2 commit 24f2716

2 files changed

Lines changed: 104 additions & 23 deletions

File tree

rust-toolchain.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[toolchain]
2-
channel = "1.89.0"
2+
channel = "1.95.0"
33
components = ["rustfmt", "rust-std", "clippy"]

src/wrap.rs

Lines changed: 103 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
993998
mod 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

Comments
 (0)