use crate::operation::Operation;
use crate::state::mount::MountEntry;
use crate::state::SystemState;
use super::LayerResult;
#[must_use]
pub fn check_mount(state: &SystemState) -> LayerResult {
let Some(target_idx) = state.operation.target_component(state.walk.len()) else {
return LayerResult::Degraded {
reason: "path walk too short to determine target component".into(),
};
};
let component = &state.walk[target_idx];
let Some(mount_idx) = component.mount else {
return LayerResult::Degraded {
reason: format!("no mount information for {}", component.path.display()),
};
};
let Some(entry) = state.mounts.0.get(mount_idx) else {
return LayerResult::Degraded {
reason: format!("mount index {mount_idx} out of range"),
};
};
let suid_bits = target_suid_bits(state, target_idx);
check_operation_against_mount(state.operation, entry, target_idx, suid_bits)
}
#[derive(Clone, Copy)]
struct SuidBits {
setuid: bool,
setgid: bool,
}
fn target_suid_bits(state: &SystemState, target_idx: usize) -> SuidBits {
let mode = state.walk[target_idx].stat.known().map_or(0, |s| s.mode);
SuidBits {
setuid: mode & 0o4000 != 0,
setgid: mode & 0o2000 != 0,
}
}
fn nosuid_warnings(entry: &MountEntry, suid: SuidBits) -> Vec<String> {
if !entry.options.nosuid || (!suid.setuid && !suid.setgid) {
return vec![];
}
vec!["nosuid: setuid/setgid bits on target will be ignored at exec".into()]
}
fn check_operation_against_mount(
op: Operation,
entry: &MountEntry,
component_idx: usize,
suid: SuidBits,
) -> LayerResult {
let mp = entry.mountpoint.display();
let fs = &entry.fs_type;
match op {
Operation::Read | Operation::Stat => LayerResult::Pass {
detail: format!("mount at {mp} ({fs}) allows {op:?}"),
warnings: vec![],
},
Operation::Write
| Operation::Delete
| Operation::Create
| Operation::Chmod
| Operation::ChownUid
| Operation::ChownGid
| Operation::SetXattr { .. } => {
if entry.options.read_only {
LayerResult::Fail {
detail: format!("filesystem mounted read-only at {mp} ({fs})"),
component_index: Some(component_idx),
}
} else {
LayerResult::Pass {
detail: format!("mount at {mp} ({fs}) is read-write"),
warnings: vec![],
}
}
}
Operation::Execute => {
if entry.options.noexec {
LayerResult::Fail {
detail: format!("filesystem mounted noexec at {mp} ({fs})"),
component_index: Some(component_idx),
}
} else {
let warnings = nosuid_warnings(entry, suid);
LayerResult::Pass {
detail: format!("mount at {mp} ({fs}) allows execution"),
warnings,
}
}
}
}
}
#[cfg(test)]
#[path = "mount_tests.rs"]
mod tests;