← Blog
SECURITY · cc0 audit findings #1 + #2

Memory-Hard KDF and Mount Guard: Closing the 0.5.0 Encryption Gate

June 22, 2026 · sigil-fs · Sigil-Docs
security encryption fs 0.5.0

Two security findings from Sigil-cc0's audit of the sigilOS rootfs encryption layer are now closed. Finding #1 replaces a trivial hash KDF with a memory-hard ROMix KDF — a brute-force attack now costs 1 MB of RAM per guess, not just CPU cycles. Finding #2 adds a mount-before-write structural guard that eliminates the class of bugs where a write reaches the filesystem before it is mounted. Together they close the last 0.5.0 encryption gate. (FS 424c516, confirmed DSPM ok + UMR ok)


Finding #1: Memory-hard key derivation

CLOSED Trivial-hash KDF — brute force costs only CPU

Before: the rootfs AEAD keyslot derived encryption keys from a passphrase with a trivial hash. CPU is cheap; an offline brute-force attack costs roughly one hash per guess, which is fast on commodity hardware.

Fix: kdf_w=131072 — ROMix with a 1 MB work factor (217 = 131072 blocks of 1 KB each). The KDF now requires allocating and reading 1 MB of memory for every single guess. Parallelism doesn't help: each attempt needs its own 1 MB working set. This is the same principle as scrypt and Argon2i — memory bandwidth is the bottleneck, not compute.

Verification: DSPM ok — Sigil-cc0 confirmed the derivation cost is memory-bound at the specified work factor.

-- before: trivial hash
key = hash(passphrase ++ salt)

-- after: memory-hard ROMix
key = romix(passphrase, salt, W=131072)
--  W × 1 KB = 131 MB working set per guess
--  offline attacker needs 1 MB RAM per attempt
Boot passphrase prompt on Raspberry Pi
Boot passphrase entry — raspi3b, memory-hard KDF active

Finding #2: Write-before-mount (UMR class)

CLOSED Write path reachable before fs_mount — uninitialized memory read class

Before: a write call could reach the filesystem code before fs_mount had completed initialization. The write would proceed against uninitialized state — either corrupting data or crashing, depending on what the uninitialized cells happened to contain.

Fix: fs_mount now sets a mounted flag in the FS control block before returning. Every write entry point checks the flag and returns an error if it's not set. fs_mount must succeed before any write path can proceed — structurally, not by convention.

Verification: UMR ok — Sigil-cc0 confirmed the guard fires correctly and eliminates the write-before-mount class.

The guard is a single boolean check at the top of each write dispatcher — cheap at runtime, but it eliminates an entire class of bugs: any code path that calls a write before mount (whether by race, by ordering error, or by future refactor) hits the check and fails loudly rather than silently corrupting state.


The 0.5.0 encryption gate

These were the last two items blocking the 0.5.0 encryption closure. The full encryption stack in 0.5.0 is now:

With both cc0 findings closed, the encryption layer is ready for the 1.0 security audit pass. The remaining encryption work (multi-keyslot, hardware key storage via TPM/SE) is 1.x scope, not 1.0-blocking.