Skip to content

Commit e8208b5

Browse files
server: Handle Partial/Full caps separately, similar to C
1 parent aadb643 commit e8208b5

1 file changed

Lines changed: 134 additions & 31 deletions

File tree

server/src/capability.rs

Lines changed: 134 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ pub enum Error {
88
DropGroups(nix::Error),
99
SetGid(nix::Error),
1010
SetUid(nix::Error),
11-
InsufficientCapabilities,
1211
}
1312

1413
impl std::fmt::Display for Error {
@@ -19,45 +18,149 @@ impl std::fmt::Display for Error {
1918
Self::DropGroups(e) => write!(f, "Failed to drop supplementary groups: {}", e),
2019
Self::SetGid(e) => write!(f, "Failed to setgid: {}", e),
2120
Self::SetUid(e) => write!(f, "Failed to setuid: {}", e),
22-
Self::InsufficientCapabilities => write!(
23-
f,
24-
"Insufficient process capabilities, insecure memory might get used"
25-
),
2621
}
2722
}
2823
}
2924

3025
impl std::error::Error for Error {}
3126

32-
pub fn drop_unnecessary_capabilities() -> Result<(), Error> {
33-
// Load current process capabilities
34-
let permitted_caps = caps::read(None, CapSet::Permitted).map_err(Error::CapsRead)?;
27+
#[derive(Debug, PartialEq)]
28+
enum CapabilityState {
29+
// We are either setuid root or the root user
30+
Full,
31+
// File system based capabilities
32+
Partial,
33+
None,
34+
}
35+
36+
fn handle_full_capabilities() -> Result<(), Error> {
37+
// First, prepare the capability sets we want to end up with
38+
let mut ipc_lock_caps = CapsHashSet::new();
39+
ipc_lock_caps.insert(Capability::CAP_IPC_LOCK);
40+
41+
// Clear all capabilities first, but DON'T touch bounding set yet
42+
let empty_caps = CapsHashSet::new();
43+
caps::set(None, CapSet::Effective, &empty_caps).map_err(Error::CapsUpdate)?;
44+
caps::set(None, CapSet::Permitted, &empty_caps).map_err(Error::CapsUpdate)?;
45+
46+
// Set only CAP_IPC_LOCK in permitted and effective (before identity change)
47+
caps::set(None, CapSet::Permitted, &ipc_lock_caps).map_err(Error::CapsUpdate)?;
48+
caps::set(None, CapSet::Effective, &ipc_lock_caps).map_err(Error::CapsUpdate)?;
49+
50+
// Drop supplementary groups first
51+
setgroups(&[]).map_err(Error::DropGroups)?;
52+
53+
// Change to real GID
54+
setgid(getgid()).map_err(Error::SetGid)?;
55+
56+
// Change to real UID (this should be done last)
57+
setuid(getuid()).map_err(Error::SetUid)?;
58+
59+
// NOW we can safely clear the bounding set (after identity change)
60+
if let Err(err) = caps::set(None, CapSet::Bounding, &ipc_lock_caps) {
61+
tracing::debug!(
62+
"Could not clear bounding set (may not be supported): {}",
63+
err
64+
);
65+
}
66+
67+
Ok(())
68+
}
69+
70+
fn handle_partial_capabilities() -> Result<(), Error> {
71+
let effective_caps = caps::read(None, CapSet::Effective).map_err(Error::CapsRead)?;
72+
73+
// Check if we have CAP_IPC_LOCK in effective set
74+
if !effective_caps.contains(&Capability::CAP_IPC_LOCK) {
75+
tracing::warn!("Insufficient process capabilities, insecure memory might get used");
76+
}
77+
78+
// Check if we have CAP_SETPCAP for bounding set manipulation
79+
let has_setpcap =
80+
caps::has_cap(None, CapSet::Effective, Capability::CAP_SETPCAP).map_err(Error::CapsRead)?;
81+
82+
// Clear all capabilities first
83+
let empty_caps = CapsHashSet::new();
84+
caps::set(None, CapSet::Effective, &empty_caps).map_err(Error::CapsUpdate)?;
85+
caps::set(None, CapSet::Permitted, &empty_caps).map_err(Error::CapsUpdate)?;
86+
87+
// Only clear bounding set if we have CAP_SETPCAP
88+
if has_setpcap {
89+
if let Err(err) = caps::set(None, CapSet::Bounding, &empty_caps) {
90+
tracing::warn!("Failed to clear bounding set: {}", err);
91+
}
92+
}
3593

36-
if permitted_caps.contains(&Capability::CAP_IPC_LOCK) {
37-
// Check if CAP_SETPCAP is available (needed to drop bounding set and groups)
38-
let has_setpcap = caps::has_cap(None, CapSet::Permitted, Capability::CAP_SETPCAP)
39-
.map_err(Error::CapsRead)?;
40-
41-
let mut drop_caps = CapsHashSet::new();
42-
drop_caps.insert(Capability::CAP_IPC_LOCK);
43-
44-
// Clear other capabilities and apply only CAP_IPC_LOCK
45-
caps::set(None, CapSet::Effective, &drop_caps).map_err(Error::CapsUpdate)?;
46-
caps::set(None, CapSet::Permitted, &drop_caps).map_err(Error::CapsUpdate)?;
47-
48-
// Drop supplementary groups and switch to real UID/GID.
49-
if has_setpcap {
50-
setgroups(&[]).map_err(Error::DropGroups)?;
51-
setgid(getgid()).map_err(Error::SetGid)?;
52-
setuid(getuid()).map_err(Error::SetUid)?;
53-
} else {
54-
return Err(Error::InsufficientCapabilities);
94+
// Add only CAP_IPC_LOCK to effective and permitted sets
95+
let mut ipc_lock_caps = CapsHashSet::new();
96+
ipc_lock_caps.insert(Capability::CAP_IPC_LOCK);
97+
98+
caps::set(None, CapSet::Effective, &ipc_lock_caps).map_err(Error::CapsUpdate)?;
99+
caps::set(None, CapSet::Permitted, &ipc_lock_caps).map_err(Error::CapsUpdate)?;
100+
101+
// Only set bounding set if we have CAP_SETPCAP and cleared it successfully
102+
if has_setpcap {
103+
if let Err(err) = caps::set(None, CapSet::Bounding, &ipc_lock_caps) {
104+
tracing::warn!("Failed to set bounding set: {}", err);
55105
}
56-
} else if permitted_caps.is_empty() {
57-
tracing::warn!("No process capabilities, insecure memory might get used");
58-
return Ok(());
106+
}
107+
108+
Ok(())
109+
}
110+
111+
/// Determines the current capability state of the process
112+
/// This mirrors the logic from libcap-ng's capng_have_capabilities()
113+
fn determine_capability_state() -> Result<CapabilityState, Error> {
114+
let effective_caps = caps::read(None, CapSet::Effective).map_err(Error::CapsRead)?;
115+
let permitted_caps = caps::read(None, CapSet::Permitted).map_err(Error::CapsRead)?;
116+
let bounding_caps = caps::read(None, CapSet::Bounding).map_err(Error::CapsRead)?;
117+
118+
// Check if we have no capabilities at all
119+
if permitted_caps.is_empty() && effective_caps.is_empty() {
120+
return Ok(CapabilityState::None);
121+
}
122+
123+
// To match libcap-ng logic more closely, check if we have "most" capabilities
124+
// This is a heuristic - if we have a substantial number of capabilities,
125+
// we're likely in FULL state (setuid root or running as root)
126+
127+
// Count total unique capabilities across all sets
128+
let mut all_caps = permitted_caps.clone();
129+
all_caps.extend(&effective_caps);
130+
all_caps.extend(&bounding_caps);
131+
132+
// If we have 10+ capabilities total, likely FULL state
133+
// This matches libcap-ng's heuristic more closely than checking specific caps
134+
if all_caps.len() >= 10 {
135+
Ok(CapabilityState::Full)
59136
} else {
60-
return Err(Error::InsufficientCapabilities);
137+
// Otherwise, we have partial/filesystem-based capabilities
138+
Ok(CapabilityState::Partial)
139+
}
140+
}
141+
142+
pub fn drop_unnecessary_capabilities() -> Result<(), Error> {
143+
// First, verify we can read capabilities at all (equivalent to CAPNG_FAIL
144+
// check)
145+
if let Err(e) = caps::read(None, CapSet::Effective) {
146+
// Critical error - cannot proceed safely, should abort like C version
147+
tracing::error!("Error getting process capabilities: {:?}, aborting", e);
148+
std::process::exit(1);
149+
}
150+
151+
match determine_capability_state()? {
152+
CapabilityState::Full => {
153+
// We are either setuid root or the root user
154+
handle_full_capabilities()?;
155+
}
156+
CapabilityState::None => {
157+
tracing::warn!("No process capabilities, insecure memory might get used");
158+
return Ok(());
159+
}
160+
CapabilityState::Partial => {
161+
// File system based capabilities
162+
handle_partial_capabilities()?;
163+
}
61164
}
62165

63166
Ok(())

0 commit comments

Comments
 (0)