xfs-forensic¶
A from-scratch XFS reader and a graded anomaly auditor — walk the superblock, allocation-group headers, inodes, extents (inline and bmap-B+tree), and the five directory formats of an XFS image over any byte source, then turn its residue into evidence: v5 CRC-mismatched metadata, secondary-superblock divergence, AGI-unlinked orphaned inodes, and deleted inodes still carvable from their surviving residual extent records.
Two crates, one workspace:
xfs-core— the reader (imported asxfs): superblock + geometry, AGF / AGI / AGFL headers, inode cores (v2 100-byte / v3 176-byte, bigtime timestamps), inline and bmap-B+tree extents, short-form / block / data / leaf directories, and v5 CRC32c verification, over any byte slice. Nounsafe, no C bindings.xfs-forensic— the auditor: turns parsed XFS structures into severity-gradedforensicnomicon::report::Findings, and recovers deleted inodes, so an XFS volume's anomalies aggregate uniformly with the partition and container layers.
Audit an XFS image in 30 seconds¶
use xfs_forensic::audit_findings;
// Feed it the raw image bytes; get back graded findings.
for finding in audit_findings(&image_bytes, "xfs") {
println!("[{:?}] {} — {}", finding.severity, finding.code, finding.note);
// e.g. [Some(High)] XFS-SB-MIRROR-DIVERGENCE — AG 2 secondary superblock: agcount …
}
audit_findings parses the superblock, AG headers, and inode residue in place and grades what it finds. A structurally invalid image yields no findings (corruption is surfaced as its own finding, never a panic).
The anomaly codes¶
Each finding is an observation ("consistent with …"); the examiner draws the conclusions. Codes are a stable, published contract.
| Code | Severity | What it observes |
|---|---|---|
XFS-CRC-MISMATCH |
High | A v5 self-describing metadata block whose stored crc32c does not verify — consistent with corruption or post-write tampering |
XFS-SB-MIRROR-DIVERGENCE |
High | A secondary (per-AG) superblock field that differs from the AG-0 primary — consistent with a spliced or edited image |
XFS-IMPOSSIBLE-GEOMETRY |
High | A geometry field beyond what the image can hold — an allocation-bomb / corruption guard |
XFS-ORPHANED-INODE |
Medium | An AGI unlinked[64] bucket pointing at a live inode — unlinked while still open (orphaned-but-live), a recovery lead |
Deleted-inode recovery is separate: recover_deleted(&image, &sb) scans for freed (di_mode == 0) inodes whose data fork still holds residual extent records, decodes them, and carves each DeletedInode's bytes from the readable blocks.
The reader: navigate an image¶
xfs-core (imported as xfs) reads an XFS image over any byte slice:
use xfs::{Superblock, read_by_path};
let sb = Superblock::parse(&image[0..512])?;
let bytes = read_by_path(&image, &sb, "etc/hostname")?;
# Ok::<(), xfs::XfsError>(())
The bare crate name xfs on crates.io is an abandoned 2016 perf-data parser unrelated to the filesystem, so this on-disk reader publishes as xfs-core and imports as xfs.
Trust but verify¶
#![forbid(unsafe_code)]in both crates — nounsafe, no C bindings.- Panic-free — every integer/length/offset field is read through bounds-checked big-endian helpers; a malformed image degrades to an empty/typed result, never a panic.
- Fuzzed — one
cargo-fuzztarget per parsed structure (superblock, agheaders, inode, extent, btree, dir, crc) plus afuzz_forensictarget driving the fullaudit_image/recover_deletedpipeline. See Validation. - Tier-1 validated — the reader is checked against a real third-party XFS image (log2timeline/dfvfs
xfs.raw, Apache-2.0), with ground truth fromxfs_db, the Linux kernel mount, andlibfsxfs— implementations wholly separate from ours. See Validation.
Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd.