whyno-core 0.5.0

Permission check pipeline, fix engine, and state types
Documentation
//! Tests for `metadata_fixes()` generator — chmod, chown, setxattr.

use super::generators;
use crate::fix::FixAction;
use crate::operation::{MetadataParams, Operation, XattrNamespace};
use crate::test_helpers::StateBuilder;

// --- chmod failures ---

#[test]
fn chmod_fail_produces_chown_fix_impact_4() {
    let state = StateBuilder::new()
        .subject(1000, 1000, vec![])
        .operation(Operation::Chmod)
        .mount("/", "ext4", "rw")
        .component("/", 0, 0, 0o755)
        .component_file("/file.txt", 0, 0, 0o644)
        .build();
    let params = MetadataParams::default();

    let fixes = generators::metadata_fixes(&state, &params);
    let chown = fixes
        .iter()
        .find(|f| matches!(f.action, FixAction::Chown { .. }));
    assert!(chown.is_some(), "expected Chown fix for chmod failure");
    assert_eq!(chown.unwrap().impact, 4);
}

#[test]
fn chmod_fail_produces_grant_cap_fowner_impact_5() {
    let state = StateBuilder::new()
        .subject(1000, 1000, vec![])
        .operation(Operation::Chmod)
        .mount("/", "ext4", "rw")
        .component("/", 0, 0, 0o755)
        .component_file("/file.txt", 0, 0, 0o644)
        .build();
    let params = MetadataParams::default();

    let fixes = generators::metadata_fixes(&state, &params);
    let grant = fixes.iter().find(|f| {
        matches!(&f.action, FixAction::GrantCap { capability, .. } if capability == "cap_fowner")
    });
    assert!(grant.is_some(), "expected GrantCap cap_fowner fix");
    assert_eq!(grant.unwrap().impact, 5);
}

// --- chown_uid failures ---

#[test]
fn chown_uid_fail_produces_grant_cap_chown_impact_5() {
    let state = StateBuilder::new()
        .subject(1000, 1000, vec![])
        .with_capabilities(0)
        .operation(Operation::ChownUid)
        .mount("/", "ext4", "rw")
        .component("/", 0, 0, 0o755)
        .component_file("/file.txt", 0, 0, 0o644)
        .build();
    let params = MetadataParams::default();

    let fixes = generators::metadata_fixes(&state, &params);
    let grant = fixes.iter().find(|f| {
        matches!(&f.action, FixAction::GrantCap { capability, .. } if capability == "cap_chown")
    });
    assert!(
        grant.is_some(),
        "expected GrantCap cap_chown fix for chown_uid"
    );
    assert_eq!(grant.unwrap().impact, 5);
}

// --- chown_gid failures ---

#[test]
fn chown_gid_fail_produces_grant_cap_chown_impact_5() {
    let state = StateBuilder::new()
        .subject(2000, 2000, vec![])
        .with_capabilities(0)
        .operation(Operation::ChownGid)
        .mount("/", "ext4", "rw")
        .component("/", 0, 0, 0o755)
        .component_file("/file.txt", 0, 0, 0o644)
        .build();
    let params = MetadataParams {
        new_gid: Some(999),
        ..MetadataParams::default()
    };

    let fixes = generators::metadata_fixes(&state, &params);
    let grant = fixes.iter().find(|f| {
        matches!(&f.action, FixAction::GrantCap { capability, .. } if capability == "cap_chown")
    });
    assert!(
        grant.is_some(),
        "expected GrantCap cap_chown fix for chown_gid"
    );
}

// --- setxattr failures ---

#[test]
fn setxattr_user_fail_produces_chown_fix() {
    let state = StateBuilder::new()
        .subject(1000, 1000, vec![])
        .with_capabilities(0)
        .operation(Operation::SetXattr {
            namespace: XattrNamespace::User,
        })
        .mount("/", "ext4", "rw")
        .component("/", 0, 0, 0o755)
        .component_file("/file.txt", 0, 0, 0o644)
        .build();
    let params = MetadataParams::default();

    let fixes = generators::metadata_fixes(&state, &params);
    let chown = fixes
        .iter()
        .find(|f| matches!(f.action, FixAction::Chown { .. }));
    assert!(chown.is_some(), "expected Chown fix for user xattr");
    assert_eq!(chown.unwrap().impact, 4);
}

#[test]
fn setxattr_trusted_fail_produces_grant_cap_sys_admin_impact_6() {
    let state = StateBuilder::new()
        .subject(1000, 1000, vec![])
        .with_capabilities(0)
        .operation(Operation::SetXattr {
            namespace: XattrNamespace::Trusted,
        })
        .mount("/", "ext4", "rw")
        .component("/", 0, 0, 0o755)
        .component_file("/file.txt", 0, 0, 0o644)
        .build();
    let params = MetadataParams::default();

    let fixes = generators::metadata_fixes(&state, &params);
    let grant = fixes.iter().find(|f| {
        matches!(&f.action, FixAction::GrantCap { capability, .. } if capability == "cap_sys_admin")
    });
    assert!(
        grant.is_some(),
        "expected GrantCap cap_sys_admin for trusted xattr"
    );
    assert_eq!(grant.unwrap().impact, 6);
}

#[test]
fn setxattr_security_fail_produces_grant_cap_sys_admin_impact_6() {
    let state = StateBuilder::new()
        .subject(1000, 1000, vec![])
        .with_capabilities(0)
        .operation(Operation::SetXattr {
            namespace: XattrNamespace::Security,
        })
        .mount("/", "ext4", "rw")
        .component("/", 0, 0, 0o755)
        .component_file("/file.txt", 0, 0, 0o644)
        .build();
    let params = MetadataParams::default();

    let fixes = generators::metadata_fixes(&state, &params);
    let grant = fixes.iter().find(|f| {
        matches!(&f.action, FixAction::GrantCap { capability, .. } if capability == "cap_sys_admin")
    });
    assert!(
        grant.is_some(),
        "expected GrantCap cap_sys_admin for security xattr"
    );
    assert_eq!(grant.unwrap().impact, 6);
}

#[test]
fn setxattr_posix_acl_fail_produces_chown_and_grant_cap_fowner() {
    let state = StateBuilder::new()
        .subject(1000, 1000, vec![])
        .with_capabilities(0)
        .operation(Operation::SetXattr {
            namespace: XattrNamespace::SystemPosixAcl,
        })
        .mount("/", "ext4", "rw")
        .component("/", 0, 0, 0o755)
        .component_file("/file.txt", 0, 0, 0o644)
        .build();
    let params = MetadataParams::default();

    let fixes = generators::metadata_fixes(&state, &params);
    let chown = fixes
        .iter()
        .find(|f| matches!(f.action, FixAction::Chown { .. }));
    let grant = fixes.iter().find(|f| {
        matches!(&f.action, FixAction::GrantCap { capability, .. } if capability == "cap_fowner")
    });
    assert!(
        chown.is_some(),
        "expected Chown fix for system posix acl xattr"
    );
    assert_eq!(chown.unwrap().impact, 4);
    assert!(
        grant.is_some(),
        "expected GrantCap cap_fowner for system posix acl xattr"
    );
    assert_eq!(grant.unwrap().impact, 5);
}