use super::*;
use crate::operation::Operation;
use crate::state::acl::{AclEntry, AclPerms, AclTag};
use crate::test_helpers::StateBuilder;
fn acl_entry(tag: AclTag, qualifier: Option<u32>, r: bool, w: bool, x: bool) -> AclEntry {
AclEntry {
tag,
qualifier,
perms: AclPerms {
read: r,
write: w,
execute: x,
},
}
}
#[test]
fn acl_group_obj_match_grants_execute() {
let acl_entries = vec![
acl_entry(AclTag::UserObj, None, true, true, false),
acl_entry(AclTag::User, Some(9999), false, false, false),
acl_entry(AclTag::GroupObj, None, false, false, true),
acl_entry(AclTag::Mask, None, true, true, true),
acl_entry(AclTag::Other, None, false, false, false),
];
let state = StateBuilder::new()
.subject(1000, 50, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_with_acl("/grouponly", 0, 50, 0o700, acl_entries)
.component_file("/grouponly/file.txt", 0, 0, 0o644)
.build();
let result = check_traversal(&state);
assert!(result.is_pass(), "expected pass, got: {result:?}");
}
#[test]
fn root_subject_always_passes_traversal() {
let state = StateBuilder::new()
.subject(0, 0, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o000)
.component("/locked", 0, 0, 0o000)
.component_file("/locked/file.txt", 0, 0, 0o000)
.build();
let result = check_traversal(&state);
assert!(result.is_pass());
}
#[test]
fn inaccessible_ancestor_returns_degraded() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_inaccessible("/secret")
.component_file("/secret/file.txt", 0, 0, 0o644)
.build();
let result = check_traversal(&state);
assert!(result.is_degraded());
if let LayerResult::Degraded { reason } = &result {
assert!(reason.contains("/secret"));
}
}
#[test]
fn unknown_ancestor_stat_returns_degraded() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_unknown("/mystery")
.component_file("/mystery/file.txt", 0, 0, 0o644)
.build();
let result = check_traversal(&state);
assert!(result.is_degraded());
if let LayerResult::Degraded { reason } = &result {
assert!(reason.contains("/mystery"));
}
}
#[test]
fn single_component_walk_passes() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.build();
let result = check_traversal(&state);
assert!(result.is_pass());
if let LayerResult::Pass { detail, .. } = &result {
assert!(detail.contains("single component"));
}
}
#[test]
fn acl_probe_inaccessible_returns_degraded() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_inaccessible("/locked_dir")
.component_file("/locked_dir/file.txt", 0, 0, 0o644)
.build();
let result = check_traversal(&state);
assert!(result.is_degraded());
}