**Permission check pipeline, fix engine, and state types for Linux filesystem permissions.**
`whyno-core` is the pure-logic engine behind the [whyno](https://gnu.foo/projects/whyno) permission debugger. It takes a pre-gathered snapshot of filesystem state and deterministically evaluates whether an operation is allowed — with zero I/O at check time. When a check fails, it generates least-privilege fix suggestions ranked by impact.
## Overview
`whyno-core` answers one question: **"Why can't this user do this to that file?"**
Given a `SystemState` (subject identity, path walk, mount table, MAC state, and the requested operation), the check pipeline evaluates seven layers of Linux permission enforcement:
1. **Mount** — read-only, `noexec`, `nosuid` filesystem options
2. **FsFlags** — inode flags (`immutable`, `append-only`)
3. **Traversal** — `+x` on every ancestor directory
4. **DAC** — discretionary access control (uid/gid/mode bits + capability overrides)
5. **ACL** — POSIX.1e access control lists with mask evaluation
6. **SELinux** — mandatory access control (feature-gated)
7. **AppArmor** — mandatory access control (feature-gated)
Each layer returns `Pass`, `Fail`, or `Degraded` (state unavailable). The fix engine then generates repair suggestions for every failed layer, simulates their cascading effects, and prunes redundant fixes.
## Features
- **Pure logic, zero I/O** — all filesystem state is gathered externally and passed in via `SystemState`
- **Seven-layer check pipeline** — mount options, fs flags, traversal, DAC, ACL, SELinux, AppArmor
- **Capability-aware DAC** — models `CAP_DAC_OVERRIDE`, `CAP_DAC_READ_SEARCH`, `CAP_FOWNER`, and more
- **POSIX.1e ACL evaluation** — full mask interaction, named user/group entries, effective permission computation
- **Fix engine** — generates `chmod`, `chown`, `setfacl`, `mount -o remount`, and `chattr` suggestions
- **Impact scoring** — fixes ranked 1 (least privilege) to 6 (broadest impact)
- **Cascade simulation** — applies fixes to cloned state, prunes cross-layer redundancies
- **Serializable** — all types derive `Serialize` for JSON output
- **JSON Schema** — all public types derive `JsonSchema` via `schemars`
## Installation
```toml
[dependencies]
whyno-core = "0.3"
```
## Quick Start
### Build a SystemState
The check pipeline operates on a `SystemState` struct built from gathered OS data. The pipeline performs no I/O.
```rust
use whyno_core::state::{
SystemState, Probe,
subject::ResolvedSubject,
path::{PathComponent, StatResult, FileType},
mount::{MountTable, MountEntry, MountOptions},
mac::MacState,
};
use whyno_core::operation::Operation;
let subject = ResolvedSubject {
uid: 1000,
gid: 1000,
groups: vec![33, 44],
capabilities: Probe::Unknown,
};
let walk = vec![
PathComponent {
path: "/".into(),
stat: Probe::Known(StatResult {
mode: 0o755, uid: 0, gid: 0,
dev: 1, nlink: 2, file_type: FileType::Directory,
}),
acl: Probe::Unknown,
flags: Probe::Unknown,
mount: Some(0),
},
PathComponent {
path: "/var/log/app.log".into(),
stat: Probe::Known(StatResult {
mode: 0o640, uid: 0, gid: 44,
dev: 1, nlink: 1, file_type: FileType::Regular,
}),
acl: Probe::Unknown,
flags: Probe::Unknown,
mount: Some(0),
},
];
let mounts = MountTable(vec![MountEntry {
mount_id: 1,
device: 1,
mountpoint: "/".into(),
fs_type: "ext4".into(),
options: MountOptions { read_only: false, noexec: false, nosuid: false },
}]);
let state = SystemState {
subject,
walk,
mounts,
operation: Operation::Read,
mac_state: MacState::default(),
};
```
### Run Checks
```rust
use whyno_core::checks::run_checks;
let report = run_checks(&state);
if report.is_allowed() {
println!("Access allowed");
} else {
for layer in report.failed_layers() {
println!("Blocked by: {layer:?}");
}
}
```
### Generate Fixes
```rust
use whyno_core::fix::generate_fixes;
let plan = generate_fixes(&report, &state);
for fix in &plan.fixes {
println!("[impact {}] {:?} — {}", fix.impact, fix.action, fix.description);
}
for warning in &plan.warnings {
println!("⚠ {warning}");
}
```
## Key Types
### State Types
| `SystemState` | `state` | Complete gathered state — subject, path walk, mounts, operation, MAC state |
| `Probe<T>` | `state` | Tri-state: `Known(T)`, `Unknown`, or `Inaccessible` — never produces false-green results |
| `ResolvedSubject` | `state::subject` | UID, GID, supplementary groups, and effective capability bitmask |
| `PathComponent` | `state::path` | Single path segment with stat, ACL, fs-flags, and mount reference |
| `MountTable` | `state::mount` | Collection of mount entries with device-based lookup |
| `PosixAcl` | `state::acl` | POSIX.1e ACL with mask application and effective permission helpers |
| `MacState` | `state::mac` | SELinux and AppArmor probe results |
| `Operation` | `operation` | Read, Write, Execute, Delete, Create, or Stat |
### Check Types
| `CheckReport` | Aggregated results — `is_allowed()` for quick verdict, `failed_layers()` for diagnostics |
| `LayerResult` | `Pass` (with optional warnings), `Fail` (with detail), or `Degraded` (state unavailable) |
| `CoreLayer` | Mount, FsFlags, Traversal, Dac, Acl |
| `MacLayer` | SeLinux, AppArmor |
### Fix Types
| `FixPlan` | Ordered fix list with warnings for high-impact changes |
| `Fix` | Target layer, structured action, impact score (1–6), and description |
| `FixAction` | `Chmod`, `Chown`, `SetAcl`, `Remount`, or `Chattr` — renderable to shell commands |
## Check Pipeline
`run_checks()` evaluates layers in a fixed order. All layers always run — no short-circuiting.
| 1 | Mount | `ro` for writes, `noexec` for execute, `nosuid` advisory |
| 2 | FsFlags | `immutable` blocks all writes, `append-only` blocks non-append writes |
| 3 | Traversal | `+x` on every ancestor directory |
| 4 | DAC | uid/gid/mode bits + capability overrides |
| 5 | ACL | POSIX.1e named entries with mask application |
| 6 | SELinux | AVC decision over pre-gathered `mac_state` (`selinux` feature) |
| 7 | AppArmor | Profile mode over pre-gathered `mac_state` (`apparmor` feature) |
## Feature Flags
| `selinux` | off | Enables SELinux check layer — without it, SELinux results are always `Degraded` |
| `apparmor` | off | Enables AppArmor check layer — without it, AppArmor results are always `Degraded` |
| `test-helpers` | off | Exposes `test_helpers` module with fluent builders for constructing `SystemState` in tests |
## Dependencies
- `serde` + `serde_json` — serialization for all public types
- `schemars` — `JsonSchema` derive for schema generation
- `thiserror` — structured error types
- `enum-map` — fixed-size enum-keyed maps for layer results
## Minimum Supported Rust Version
**Rust 1.78.** Pure Rust, no platform-specific code — compiles on any target `rustc` supports.