use super::*;
use crate::checks::capability_modify;
use crate::checks::caps::CAP_DAC_OVERRIDE;
use crate::operation::Operation;
use crate::state::mac::MacState;
use crate::test_helpers::StateBuilder;
#[test]
fn root_execute_on_0744_passes_owner_has_x() {
let state = StateBuilder::new()
.subject(0, 0, vec![])
.operation(Operation::Execute)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file("/script.sh", 1000, 1000, 0o744)
.build();
assert!(capability_modify(check_dac(&state), &state).is_pass());
}
#[test]
fn root_execute_on_0111_passes_all_have_x() {
let state = StateBuilder::new()
.subject(0, 0, vec![])
.operation(Operation::Execute)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file("/bin/tool", 1000, 1000, 0o111)
.build();
assert!(capability_modify(check_dac(&state), &state).is_pass());
}
#[test]
fn capability_modify_preserves_pass() {
let state = StateBuilder::new()
.subject(0, 0, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file("/file.txt", 0, 0, 0o644)
.build();
let dac = check_dac(&state);
assert!(dac.is_pass());
assert!(capability_modify(dac, &state).is_pass());
}
#[test]
fn capability_modify_preserves_degraded() {
let state = StateBuilder::new()
.subject(0, 0, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_unknown("/mystery")
.build();
let dac = check_dac(&state);
assert!(dac.is_degraded());
assert!(capability_modify(dac, &state).is_degraded());
}
#[test]
fn root_delete_sticky_parent_mentions_sticky_in_override() {
let state = StateBuilder::new()
.subject(0, 0, vec![])
.operation(Operation::Delete)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component("/restricted", 1000, 1000, 0o1700)
.component_file("/restricted/file.txt", 1000, 1000, 0o644)
.build();
let dac = check_dac(&state);
assert!(dac.is_fail(), "expected DAC fail for root as other on 1700");
let result = capability_modify(dac, &state);
assert!(result.is_pass());
if let LayerResult::Pass { detail, .. } = &result {
assert!(
detail.contains("sticky"),
"expected sticky bit context: {detail}"
);
assert!(
detail.contains("CAP_DAC_OVERRIDE"),
"expected CAP_DAC_OVERRIDE: {detail}"
);
}
}
#[test]
fn root_delete_no_sticky_generic_override() {
let state = StateBuilder::new()
.subject(0, 0, vec![])
.operation(Operation::Delete)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component("/restricted", 1000, 1000, 0o700)
.component_file("/restricted/file.txt", 1000, 1000, 0o644)
.build();
let dac = check_dac(&state);
assert!(dac.is_fail());
let result = capability_modify(dac, &state);
assert!(result.is_pass());
if let LayerResult::Pass { detail, .. } = &result {
assert!(
!detail.contains("sticky"),
"should not mention sticky: {detail}"
);
}
}
#[test]
fn capability_modify_known_cap_overrides_fail() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.with_capabilities(CAP_DAC_OVERRIDE)
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file("/secret.txt", 0, 0, 0o000)
.build();
let result = capability_modify(check_dac(&state), &state);
assert!(result.is_pass(), "expected Pass, got {result:?}");
if let LayerResult::Pass { detail, .. } = &result {
assert!(detail.contains("CAP_DAC_OVERRIDE"));
}
}
#[test]
fn capability_modify_known_no_cap_leaves_fail() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.with_capabilities(0u64)
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file("/secret.txt", 0, 0, 0o000)
.build();
let result = capability_modify(check_dac(&state), &state);
assert!(result.is_fail(), "expected Fail when cap bitmask is 0");
}
#[test]
fn capability_modify_unknown_root_overrides() {
let state = StateBuilder::new()
.subject(0, 0, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file("/secret.txt", 1000, 1000, 0o000)
.build();
let result = capability_modify(check_dac(&state), &state);
assert!(result.is_pass(), "uid=0 + Unknown should override DAC fail");
}
#[test]
fn capability_modify_unknown_nonroot_no_override() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file("/secret.txt", 0, 0, 0o000)
.build();
let result = capability_modify(check_dac(&state), &state);
assert!(
result.is_fail(),
"uid=1000 + Unknown should not override DAC fail"
);
}
#[test]
fn capability_modify_inaccessible_leaves_result() {
use crate::state::subject::ResolvedSubject;
use crate::state::Probe;
use crate::state::SystemState;
let base = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file("/secret.txt", 0, 0, 0o000)
.build();
let state = SystemState {
subject: ResolvedSubject {
uid: base.subject.uid,
gid: base.subject.gid,
groups: base.subject.groups.clone(),
capabilities: Probe::Inaccessible,
},
walk: base.walk,
mounts: base.mounts,
operation: base.operation,
mac_state: MacState::default(),
};
let dac = check_dac(&state);
assert!(dac.is_fail());
let result = capability_modify(dac.clone(), &state);
assert_eq!(
result, dac,
"Inaccessible must return original result unchanged"
);
}
#[test]
fn delete_single_component_returns_degraded() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Delete)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.build();
let result = check_dac(&state);
assert!(result.is_degraded());
}
#[test]
fn delete_parent_with_inaccessible_stat_returns_degraded() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Delete)
.component_inaccessible("/locked_dir")
.component_file("/locked_dir/file.txt", 1000, 1000, 0o644)
.build();
let result = check_dac(&state);
assert!(result.is_degraded());
}