use super::*;
use crate::operation::Operation;
use crate::state::acl::{AclEntry, AclPerms, AclTag};
use crate::state::mac::MacState;
use crate::test_helpers::StateBuilder;
fn 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 delete_operation_checks_parent_acl() {
let parent_acl = vec![
entry(AclTag::UserObj, None, true, true, true),
entry(AclTag::User, Some(2000), false, false, false),
entry(AclTag::GroupObj, None, false, false, false),
entry(AclTag::Mask, None, true, false, false),
entry(AclTag::Other, None, false, false, false),
];
let state = StateBuilder::new()
.subject(2000, 2000, vec![])
.operation(Operation::Delete)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_with_acl("/dir", 0, 0, 0o755, parent_acl)
.component_file("/dir/file.txt", 2000, 2000, 0o644)
.build();
let result = check_acl(&state);
assert!(result.is_fail());
if let LayerResult::Fail { detail, .. } = &result {
assert!(detail.contains("named user"));
}
}
#[test]
fn create_operation_checks_parent_acl_grants() {
let parent_acl = vec![
entry(AclTag::UserObj, None, true, true, true),
entry(AclTag::User, Some(2000), true, true, true),
entry(AclTag::GroupObj, None, false, false, false),
entry(AclTag::Mask, None, true, true, true),
entry(AclTag::Other, None, false, false, false),
];
let state = StateBuilder::new()
.subject(2000, 2000, vec![])
.operation(Operation::Create)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_with_acl("/dir", 0, 0, 0o755, parent_acl)
.component_file("/dir/newfile.txt", 2000, 2000, 0o644)
.build();
let result = check_acl(&state);
assert!(result.is_pass());
if let LayerResult::Pass { detail, .. } = &result {
assert!(detail.contains("named user"));
}
}
#[test]
fn named_user_acl_blocked_by_mask() {
let acl = vec![
entry(AclTag::UserObj, None, true, true, true),
entry(AclTag::User, Some(2000), true, true, true),
entry(AclTag::GroupObj, None, true, false, false),
entry(AclTag::Mask, None, true, false, false),
entry(AclTag::Other, None, false, false, false),
];
let state = StateBuilder::new()
.subject(2000, 2000, vec![])
.operation(Operation::Write)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file_with_acl("/file.txt", 1000, 1000, 0o640, acl)
.build();
let result = check_acl(&state);
assert!(result.is_fail());
if let LayerResult::Fail { detail, .. } = &result {
assert!(detail.contains("named user"));
assert!(detail.contains("write"));
}
}
#[test]
fn file_owner_uses_user_obj_not_affected_by_mask() {
let acl = vec![
entry(AclTag::UserObj, None, true, true, true),
entry(AclTag::User, Some(9999), false, false, false),
entry(AclTag::GroupObj, None, false, false, false),
entry(AclTag::Mask, None, false, false, false),
entry(AclTag::Other, None, false, false, false),
];
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Write)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file_with_acl("/file.txt", 1000, 1000, 0o700, acl)
.build();
let result = check_acl(&state);
assert!(result.is_pass());
if let LayerResult::Pass { detail, .. } = &result {
assert!(detail.contains("UserObj"));
}
}
#[test]
fn other_entry_not_affected_by_mask() {
let acl = vec![
entry(AclTag::UserObj, None, true, true, false),
entry(AclTag::User, Some(9999), false, false, false),
entry(AclTag::GroupObj, None, false, false, false),
entry(AclTag::Mask, None, false, false, false),
entry(AclTag::Other, None, true, false, false),
];
let state = StateBuilder::new()
.subject(3000, 3000, vec![])
.operation(Operation::Read)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file_with_acl("/file.txt", 1000, 1000, 0o604, acl)
.build();
let result = check_acl(&state);
assert!(result.is_pass());
if let LayerResult::Pass { detail, .. } = &result {
assert!(detail.contains("Other"));
}
}
#[test]
fn supplementary_group_match_mask_restricts() {
let acl = vec![
entry(AclTag::UserObj, None, true, true, false),
entry(AclTag::Group, Some(50), true, true, false),
entry(AclTag::GroupObj, None, false, false, false),
entry(AclTag::Mask, None, true, false, false),
entry(AclTag::Other, None, false, false, false),
];
let state = StateBuilder::new()
.subject(2000, 2000, vec![50])
.operation(Operation::Write)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.component_file_with_acl("/file.txt", 1000, 1000, 0o600, acl)
.build();
let result = check_acl(&state);
assert!(result.is_fail());
}
#[test]
fn stat_unavailable_for_acl_evaluation_returns_degraded() {
use crate::state::acl::PosixAcl;
use crate::state::mount::MountTable;
use crate::state::path::PathComponent;
use crate::state::subject::ResolvedSubject;
use crate::state::{Probe, SystemState};
use std::path::PathBuf;
let acl_entries = vec![
entry(AclTag::UserObj, None, true, true, false),
entry(AclTag::User, Some(2000), true, false, false),
entry(AclTag::GroupObj, None, false, false, false),
entry(AclTag::Mask, None, true, false, false),
entry(AclTag::Other, None, false, false, false),
];
let comp = PathComponent {
path: PathBuf::from("/file.txt"),
stat: Probe::Unknown,
acl: Probe::Known(PosixAcl(acl_entries)),
flags: Probe::Unknown,
mount: None,
};
let state = SystemState {
subject: ResolvedSubject {
uid: 2000,
gid: 2000,
groups: vec![],
capabilities: Probe::Unknown,
},
walk: vec![comp],
mounts: MountTable(vec![]),
operation: Operation::Read,
mac_state: MacState::default(),
};
let result = check_acl(&state);
assert!(result.is_degraded());
if let LayerResult::Degraded { reason } = &result {
assert!(reason.contains("stat unavailable"));
}
}
#[test]
fn check_acl_target_walk_too_short_returns_degraded() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Delete)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.build();
let result = check_acl(&state);
assert!(result.is_degraded());
if let LayerResult::Degraded { reason } = &result {
assert!(reason.contains("parent"));
}
}
#[test]
fn check_acl_create_walk_too_short_returns_degraded() {
let state = StateBuilder::new()
.subject(1000, 1000, vec![])
.operation(Operation::Create)
.mount("/", "ext4", "rw")
.component("/", 0, 0, 0o755)
.build();
let result = check_acl(&state);
assert!(result.is_degraded());
}