skip to changelog

whyno changelog

linux permission debugger — answers "why can't this user do this to this file?"

march 2026

  • feature

    metadata permission operations

    Operation::Chmod, ChownUid, ChownGid, SetXattr variants. CoreLayer::Metadata — 6th check layer implementing setattr_prepare semantics. check_metadata() sub-checks: chmod → owner or CAP_FOWNER; chown-uid → CAP_CHOWN; chown-gid → CAP_CHOWN or owner-in-target-group; setxattr → namespace-dependent (user/posix_acl → owner/CAP_FOWNER, trusted/security → CAP_SYS_ADMIN). DAC and ACL layers early-return Pass for metadata operations.

  • feature

    XattrNamespace and MetadataParams

    XattrNamespace enum (User, Trusted, Security, SystemPosixAcl) for setxattr namespace classification. MetadataParams struct carries caller intent (new_mode, new_uid, new_gid) through check and fix pipelines. Implements Default (all None) — None for the active operation returns Degraded, not Fail.

  • breaking

    run_checks and generate_fixes take &MetadataParams

    breaking: both entry points now take &MetadataParams as a second argument. only the metadata layer reads it; all other layers ignore it. pass MetadataParams::default() for non-metadata operations.

  • feature

    GrantCap fix action

    FixAction::GrantCap { path, capability } variant. apply_grant_cap() maps capability name strings (cap_fowner, cap_chown, cap_sys_admin, cap_fsetid) to bitmask bits and merges into state.subject.capabilities — promotes Unknown/Inaccessible to Known(bit). GrantCap with path: None renders as a descriptive recommendation rather than a concrete setcap command.

  • improvement

    apply_chown cascade

    apply_chown() mirrors kernel notify_change(): clears S_ISUID on owner change, clears S_ISGID on group change only when S_IXGRP is set (ATTR_KILL_SUID/ATTR_KILL_SGID semantics).

  • feature

    CAP_FSETID and CAP_SYS_ADMIN constants

    CAP_FSETID (bit 4) and CAP_SYS_ADMIN (bit 21) added to the capability constants module.

  • improvement

    operation_to_selinux_perm consolidated

    consolidated to a single canonical copy and promoted to pub, re-exported from whyno-core. metadata ops map to ("file", "setattr").

  • improvement

    AppArmor metadata-aware degraded message

    metadata ops in enforce mode return Degraded with a capability-rules hint (chown/fowner/sys_admin) rather than the generic libapparmor rebuild hint.

  • feature

    new CLI flags for metadata operations

    --new-mode <MODE> (octal) for chmod, --new-uid <UID> for chown-uid, --new-gid <GID> for chown-gid, --xattr-key <KEY> for setxattr. resolve_operation() routes setxattr through parse_xattr_namespace(); build_metadata_params() constructs MetadataParams from flags.

  • improvement

    metadata operation integration tests

    Docker and Proxmox VM integration tests for chmod, chown-uid, chown-gid, setxattr operations.

  • fix

    clippy pedantic fixes

    resolved clippy pedantic warnings across test files.

  • breaking

    MAC state architecture

    MacState, SeLinuxState, SeLinuxMode, AppArmorState types in state/mac.rs. SystemState.mac_state field (breaking: all constructors now require MacState). gather_mac_state() pre-computes SELinux AVC decisions and AppArmor profile labels before the check pipeline. check_selinux and check_apparmor refactored to pure functions — read exclusively from state.mac_state, no syscalls at check time. degraded messages changed to "SELinux state not gathered" / "AppArmor state not gathered".

  • feature

    build & distribution — armv7 and riscv64

    armv7-unknown-linux-musleabihf and riscv64gc-unknown-linux-musl build targets. .cargo/config.toml with +crt-static rustflags for all 4 musl targets. justfile with release, per-arch build, and per-arch test recipes. /dist added to .gitignore.

  • feature

    live kernel test infrastructure

    ci/proxmox/ — Proxmox VM templates for Fedora 40 (SELinux enforcing) and Ubuntu 24.04 (AppArmor enforcing) with cloud-init configs, provision scripts, and SSH config stub. justfile recipes: proxmox-setup, test-selinux, test-apparmor, test-live.

  • feature

    MAC check unit tests

    selinux_tests.rs (6 tests) and apparmor_tests.rs (6 tests) — unit tests for check_selinux and check_apparmor across all Probe variants and enforcement modes. ext4-tests and acl-tests feature flags in whyno-gather for filesystem-specific assertions.

  • improvement

    test file splits

    cli_tests.rs, dac.rs, dac_tests.rs, acl_tests.rs, traversal_tests.rs, generators_tests.rs, apply_tests.rs each split into two focused files. caps_modify extracted from dac.rs. test_helpers/mac.rs extracted from mod.rs. no logic changes.

  • fix

    st_nlink portability

    st_nlink widened to u64 via u64::from() in whyno-gather/src/stat.rs — st_nlink is u32 on aarch64/armv7, u64 on x86_64.

february 2026

  • improvement

    capability-aware DAC

    capability_modify() replaces root_modify() — dispatches on Probe::Known(CapEff) bitmask rather than uid==0 heuristic. non-root subjects with CAP_DAC_OVERRIDE now correctly bypass DAC.

  • feature

    linux capability constants

    CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_FOWNER, CAP_CHOWN, CAP_LINUX_IMMUTABLE constants with has_cap(bitmask, cap) helper. CapEff gathered from /proc/<pid>/status for pid:N and svc: subjects.

  • feature

    user namespace detection

    PIDs in non-initial user namespaces (/proc/<pid>/uid_map non-identity) annotated as Probe::Inaccessible — CapEff bits are namespace-relative and cannot be interpreted as host-level privileges.

  • feature

    selinux check layer

    behind --features selinux. queries in-kernel AVC via selinux_check_access(). permissive mode reported as [WARN] not [PASS]. selinux = "0.3" workspace dependency.

  • feature

    apparmor check layer

    behind --features apparmor. reads profile mode from securityfs. unconfined subjects pass. enforce-mode profiles report [DEGRADED] without libapparmor policy query support.

  • feature

    --with-cap flag

    repeatable flag — injects a known capability bitmask for hypothetical queries. e.g. --with-cap CAP_DAC_OVERRIDE. overrides gathered CapEff for the duration of the check.

  • feature

    mac layer infrastructure

    MacLayer enum (SeLinux, AppArmor) and CheckReport.mac_results: Vec<(MacLayer, LayerResult)>. MAC layers always present in output — [SKIP] with rebuild hint when not compiled in.

  • breaking

    CoreLayer rename and CheckReport restructure

    Layer enum renamed to CoreLayer. CheckReport.results renamed to CheckReport.core_results (EnumMap<CoreLayer, LayerResult>). root_modify re-exported as capability_modify.

  • breaking

    subject header format change

    checklist output drops the username/service prefix. format changed from "Subject: nginx (uid=33, ...)" to "Subject: uid=33, ...". caps=0x... appended when capabilities are Probe::Known.

  • breaking

    json and explain output expansion

    --json layers array grows from 5 to 7 entries (selinux and apparmor always present). --explain === Layer Results === section includes MAC entries. degraded entries carry rebuild hint.

  • improvement

    statvfs mount flags

    mount flag gathering now uses statvfs() instead of parsing /proc/self/mountinfo option strings — one syscall, no text parsing, no elevated capabilities.

  • improvement

    mountinfo scope reduction

    /proc/self/mountinfo parsing scoped to metadata only (fs_type, mountpoint, device). parsed options retained as fallback when statvfs() fails.

  • feature

    nosuid diagnostic

    layerresult::pass now carries warnings — warns when execute targets suid/sgid binaries on a nosuid mount.

  • feature

    json schema subcommand

    whyno schema prints auto-generated json schema for --json output via schemars.

  • feature

    self-test crosscheck

    --self-test flag cross-checks whyno answer against kernel faccessat2(AT_EACCESS) on real systems.

  • fix

    cascade cycle detection

    plan hashing with hashset<u64> breaks oscillating fix loops with a diagnostic warning.

  • improvement

    enummap check report

    checkreport named fields replaced with enummap<layer, layerresult> — adding layers is now a one-line enum variant, compiler enforces exhaustiveness.

  • improvement

    probe must_use

    #[must_use] added to probe<t> — silently dropping a probed value now triggers a compiler warning.

  • fix

    fsflags delete check

    check_delete_create_flags now checks append_only on parent for delete operations — matches kernel may_delete() behavior.

  • fix

    acl large entry parsing

    read_acl_xattr rewritten with two-call getxattr pattern — acls with 30+ entries no longer silently degrade to probe::unknown.

  • fix

    root sticky bit diagnostic

    apply_root_override detects sticky bit on parent for delete and enriches the diagnostic message.

  • improvement

    musl ioctl compat

    FS_IOC_GETFLAGS uses cfg(target_env) for correct ioctl request type per platform — musl (c_int) vs glibc (c_ulong).

  • improvement

    crate metadata and docs

    doc comments rewritten to lowercase terse style. #![deny(missing_docs)] added. crate descriptions, keywords, categories, rust-version = "1.78". strip = true in release profile.

  • feature

    five-layer check pipeline

    five permission layers checked unconditionally — mount, fsflags, traversal, dac, acl. probe<t> tri-state for graceful degradation. systemstate as pure-function input over immutable snapshot.

  • feature

    subject resolution

    four input formats — bare username, uid:N, pid:N, svc:<name>. auto-detection for bare values. username lookup via /etc/passwd + /etc/group, pid from /proc/<pid>/status, service via systemctl.

  • feature

    six operations

    read, write, execute, delete, create, stat. delete and create redirect to parent directory. full ancestor walk from / to target.

  • feature

    fix engine

    least-privilege fix suggestions ranked 1–6. cascade simulation re-runs checks after each hypothetical fix to prune redundant suggestions. chmod 777 never suggested.

  • feature

    json and explain output

    three output modes — default checklist with pass/fail/skip, --json with versioned schema for ci, --explain with full debug trace. json to stdout, warnings to stderr.

  • feature

    capability management

    whyno caps install/uninstall/check — writes VFS cap v2 xattr via raw setxattr(). grants CAP_DAC_READ_SEARCH without per-invocation sudo. no libcap dependency.

  • feature

    static musl binary

    three-crate workspace — whyno-core (pure logic), whyno-gather (os state), whyno-cli (binary). primary target x86_64-unknown-linux-musl, secondary aarch64. cross-compilation via cross.

  • feature

    mac detection warnings

    prints stderr warning when selinux or apparmor is detected as active but not checked. one-time privilege hint when checks are skipped.