whyno-gather 0.3.0

Linux OS state gathering — stat, ACL, mount, fsflags, and capabilities
Documentation

whyno-gather is the OS-level state-gathering crate in the whyno project. It collects Linux file-permission metadata — stat(), POSIX ACLs, filesystem flags, mount options, MAC labels (SELinux/AppArmor), and process capabilities — and packages everything into a single SystemState struct for the check pipeline.

What It Does

Given an absolute file path, a resolved subject (user/group identity), and an operation, whyno-gather walks the entire ancestor chain from / to the target and collects:

  • File stat — ownership (uid/gid), mode bits, file type, device ID
  • POSIX ACLs — parsed directly from system.posix_acl_access xattr bytes
  • Filesystem flags — immutable and append-only flags via ioctl(FS_IOC_GETFLAGS)
  • Mount info — filesystem type, device, mountpoint from /proc/self/mountinfo
  • Mount optionsread_only, noexec, nosuid from statvfs() (authoritative, no text parsing)
  • MAC labels — SELinux file/process contexts and AppArmor profiles
  • Process info — effective capabilities, UID/GID from /proc/<pid>/status

Features

  • Single entry point — call gather_state() and get a complete SystemState
  • Ancestor walk — every directory from / to the target is stat'd, ACL-read, and flag-checked
  • Authoritative mount flagsstatvfs() is the source of truth for ro/noexec/nosuid; mountinfo text options are fallback only
  • Subject resolution — resolve by username, UID, PID, or systemd service name
  • Graceful degradation — each probe returns Probe::Known(T), Probe::Unknown, or Probe::Inaccessible instead of failing the entire gather
  • Feature-gated MAC — SELinux and AppArmor support are opt-in via Cargo features

Installation

[dependencies]
whyno-gather = "0.3"

Feature Flags

Flag What it enables Default
selinux SELinux context gathering via the selinux crate off
apparmor AppArmor profile detection off
ext4-tests Integration tests that require an ext4 filesystem off
acl-tests Integration tests that require POSIX ACL support off

Quick Start

Gather full state for a file

use whyno_gather::{gather_state, subject::resolve_username};
use whyno_core::operation::Operation;
use std::path::Path;

let subject = resolve_username("deploy")?;
let state = gather_state(&subject, Operation::Read, Path::new("/var/log/app.log"))?;

// state.walk — stat, ACL, and flags for every ancestor from / to target
// state.mounts — mount table with statvfs-verified options
// state.mac_state — SELinux/AppArmor labels (if features enabled)

Resolve a subject by UID

use whyno_gather::subject::resolve_uid;

let subject = resolve_uid(33)?; // www-data
println!("uid={} gid={} groups={:?}", subject.uid, subject.gid, subject.groups);

Resolve a subject by PID

use whyno_gather::subject::resolve_pid;

let subject = resolve_pid(1234)?;
// Reads /proc/<pid>/status for UID, GID, supplementary groups, and capabilities

Resolve a systemd service

use whyno_gather::subject::resolve_service;

let subject = resolve_service("nginx.service")?;
// Looks up MainPID via systemctl, then resolves via /proc

Modules

Module Purpose
acl POSIX ACL reading — parses raw xattr bytes into typed ACL entries
stat File stat gathering — ownership, mode, file type
statvfs Mount option probing via statvfs() syscall
mountinfo Mount table parsing from /proc/self/mountinfo
proc Process info — reads /proc/<pid>/status for UIDs, GIDs, capabilities
subject Subject resolution — username, UID, PID, or service → ResolvedSubject
fsflags Filesystem flags — immutable/append-only via ioctl(FS_IOC_GETFLAGS)
mac MAC label gathering — SELinux contexts and AppArmor profiles
error GatherError enum covering all failure modes

Error Handling

All fallible operations return Result<…, GatherError>. Main variants:

  • RelativePath — path must be absolute
  • MountInfoUnreadable — could not read /proc/self/mountinfo
  • StatFailedstat() call failed for a path
  • UserNotFound / UidNotFound — subject resolution failed
  • ProcUnreadable — could not read /proc/<pid>/status
  • ServiceResolveFailed — systemd service PID lookup failed
  • ParseError — malformed /proc or passwd content

Individual per-component probes (ACLs, flags, MAC labels) use Probe::Unknown rather than hard errors — a single inaccessible attribute does not abort the entire gather.

Dependencies

  • whyno-core = "0.3" — shared types (SystemState, Operation, Probe, etc.)
  • nix — safe Rust bindings for stat(), ioctl, user/group resolution
  • libc — raw syscall constants and types
  • thiserror — structured error types
  • selinux (optional) — SELinux context API (selinux feature only)

Requirements

  • Linux only — relies on /proc, xattrs, ioctl, and statvfs
  • Rust 1.78+ (workspace MSRV)

Integration Pattern

use whyno_gather::{gather_state, subject::resolve_username};
use whyno_core::{checks::run_checks, fix::generate_fixes, operation::Operation};
use std::path::Path;

let subject = resolve_username("deploy")?;
let state = gather_state(&subject, Operation::Write, Path::new("/var/log/app.log"))?;
let report = run_checks(&state);

if !report.is_allowed() {
    let plan = generate_fixes(&report, &state);
    for fix in &plan.fixes {
        eprintln!("[impact {}] {:?}", fix.impact, fix.action);
    }
}