Repository navigation
bcachefs: the engine's reader and mkfs replace upstream/ (storage B2) - #1032
Conversation
…hat compose them bcachefs hashes directory names with SipHash-2-4 and builds its own construction from raw ChaCha20 at a chosen block counter and Poly1305 keyed from a separate nonce domain, which is not RFC 8439's AEAD. B2's engine needs all three. - SipHash-2-4 moves here from toyos-net-wire, which re-exports it, so the tree keeps one copy. It gains the paper's published vector and a differential test against std's SipHasher at every length to three words. - `chacha::xor` is the existing RFC 8439 keystream, now public. - `poly1305::tag` and `poly1305::verify` are new and public. `verify` takes a 1- to 16-byte prefix of the tag, compared in constant time, because bcachefs truncates data MACs to 80 bits. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q
`lib/bcachefs/src/engine` implements bcachefs metadata version 1.39 as bcachefs-tools v1.39.4 defines it, on one device. It is written clean-room from the storage design's analyst specification and black-box results, and replaces `src/upstream/`, which was written from upstream's GPL headers; that module and its test are deleted whole. - The reader opens a clean volume: the superblock copy chosen as the format says, every field validated where the bytes become types; an encrypted volume's key unwrapped from its passphrase under scrypt limits the caller sets; btrees walked from `clean`'s roots with every bset's crc32c or ChaCha20/Poly1305 MAC checked and decrypted; inodes, directory lookup by SipHash slot and probing, listing, symlinks and file reads over inline data and crc- or MAC-checked extents. Anything outside that is refused by name: an unclean volume (replay is B5), compression, reflink, more than one device or snapshot, other hash types, nodes past 256 KiB, a poisoned extent. On an encrypted volume a node or extent without a MAC is refused rather than read. - mkfs writes the storage design's minimal complete volume, unencrypted (ROOT's) or encrypted with 128-bit MACs and the key wrapped under a passphrase (DATA's): three superblock copies and the layout copy, an empty journal and `clean` at sequence 0, the root subvolume, snapshot, snapshot tree and root directory without lost+found, the inode cursor, and alloc, freespace, fragmentation LRU, backpointer and accounting keys for exactly the nodes it writes. Every btree a v1 volume writes gets a root, so the node count only grows with the keys its own nodes add, and the layout is that count's least fixed point. Every identifier and key comes from the caller's `Random` and every time from the caller, so its bytes are a function of both, which B4's reproducible ROOT relies on. - `fsck` is ToyOS's check v0: the design's superblock rules, identical superblock copies, the root world as mkfs writes it, every node in the member's btree bitmap, and no bset or inode stamped with a journal sequence past `clean`'s. The engine's tests read the committed upstream volumes: `crc32c.img.gz`'s tree, and both encrypted fixtures, new here, unlocked and read whole with every extent's 80- or 128-bit MAC verified; a flipped byte in an extent or a node then fails its MAC. mkfs's volumes read back and check clean at 8 MiB, 32 MiB and 1 GiB, and fsck finds a bset or an inode stamped past `clean`. Code and records cite only published sources; the analyst's facts are stated where they bind. One gap is recorded, not filled: the specification does not give data checksum type 5's seed or final XOR, so the reader refuses type-5 extents by name and cannot yet read an unencrypted upstream volume's extent data (`issues/the-bcachefs-reader-refuses-crc32c-data-extents.md`). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q
Review of #1032 (storage B2), round 1, head 62873ceArchitectureThe ownership fits the approved design (rev 13 §3.6, §5 row B2, §6). On clean-room provenance I found no sign of contamination. Names, structure and error style differ from the deleted module. Spot-checked identifiers ( Spec conformance (checked, no finding)
FindingsBLOCKER 1. Hostile images panic the reader or fsck. The crate claims that external input is refused at its boundary.
Each needs a module test that feeds the malformed field or key and expects the named refusal. That is the cheapest sufficient test under root's policy, and today the engine has no malformed-input test beyond the checksum and MAC flips. BLOCKER 2. Snapshots are not refused, and the PR says they are.
SUGGESTION 1. SUGGESTION 2. SUGGESTION 3. Evidence
Follow-up notedThe coordinator reports that A11-gaps answers type-5 crc32c, per-btree key types and interior pointer size, and that the implementer is adding a commit. That commit should close SEND BACK: Blockers 1 and 2 remain. |
…sed, never a panic The facts B2 lacked are now established clean-room, and review round 1 of #1032 found inputs that panicked the reader or fsck. Data checksum type 5 is CRC-32C's register with no initial or final inversion (`crc32c_zero`), over the whole encoded extent, stored zero-extended in the entry's checksum bytes. The reader verifies it, so `crc32c.img.gz`'s `seq.txt` reads whole, and a test checks the fixture's stored 0x7080C241 and A11's check values. This closes `issues/the-bcachefs-reader-refuses-crc32c-data-extents.md`. Each btree's allowed key types: - An interior node holds only deleted, btree_ptr and btree_ptr_v2 keys, of size 0; a leaf holds its btree's own types. - The writer asserts the table for every key it encodes. - The reader enforces it for strict types and passes the non-strict ones (deleted, whiteout, error, cookie, set) in a leaf, as upstream's read does. - ToyOS fsck finds any key outside the table. - A key type at or past 39, which v1.39.4 does not define, is refused by name. - `Btree` gains ids 21 to 28, and a `clean` root pointer must have size 0. Refused at the boundary, each with a module test: - a members field shorter than its header; - journal bucket counts that overflow, now validated before they are summed; - a link count past 2^32; - a dirent at the last offset; - a symlink longer than 4 KiB, refused before its allocation; - a superblock copy past the device, which fsck reports instead of reading; - a root subvolume that has been snapshotted, whose keys live in ancestor snapshots this reader does not walk. Smaller refusals: - A log2 N of 0 or 64 and up is reported as corrupt. - A MAC-typed node on an unencrypted volume is corrupt. - mkfs refuses a passphrase ending in a newline, which upstream would strip. A structured fuzz test mutates `crc32c.img.gz` and mkfs's unencrypted and encrypted volumes and runs the reader and fsck over each, which must never panic. It sweeps every superblock header word and each field's first 128 bytes with boundary values in all copies at once, and gives nodes seeded random bytes and words. Checksums are resealed, so each mutation reaches the decoders behind them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q
Review of #1032 (storage B2), round 2, head e51e660This round reviews e51e660, which follows 62873ce, against round 1's findings and Round 1 blockers: resolvedBlocker 1 (hostile images panicked). All six sites are fixed:
Each site has a module test that builds the malformed field or key and expects the named refusal. I checked by reading the code that, before the fix, each of these tests panics or opens the volume (the dev profile keeps overflow checks), so each holds its fix. The seeded fuzz test ( Blocker 2 (snapshots not refused). Fixed at Suggestions 1–3 (round 1). All three are taken:
New code against A11
FindingsBLOCKER 1.
That describes the five types for which SUGGESTION 1. Evidence
SEND BACK: one blocker remains, a one-line comment fix. Everything else is ready to land. |
… is refused by name Review round 2 of #1032. The comment on `kind::strict` described the types it returns false for. A v1 `btree_ptr`, which the type table admits in an interior node, is valid bcachefs this reader does not walk, so it is refused as unsupported by that name rather than as no pointer at all. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q
Storage stage B2 (design rev 13 §5): the bcachefs engine's reader and mkfs, which replace
lib/bcachefs/src/upstream/.Draft. B1 (#1025) and K1 (#1029), which this builds on, have landed; the branch is rebased onto
main: two commits, then round 2's one.Problem
lib/bcachefs/src/upstream/was a read side for clean volumes written from upstream's GPL headers, by its own statement. The storage design replaces it with an engine written clean-room, and needs a ToyOS writer of a volume upstream's own fsck accepts before any later stage can be judged.What it does
lib/bcachefs/src/engineimplements metadata version 1.39 as bcachefs-tools v1.39.4 defines it, on one device. It was written only from the analyst's specification (A1–A10), the design and the black-box results, with btree ids and key-type numbers from the published Principles of Operation's lists (§11.3, §11.5), as the specification directs. No upstream source, provenance file orupstream/code was read; the deleted module was opened only to find its one caller,tests/upstream_fixture.rs, deleted with it. Round 2 adds A11, the analyst's answers to the gaps B2 found.Volume): opens a clean volume; the superblock copy is chosen as A1.11 says and validated once. An encrypted volume's master key is unwrapped from its passphrase through K1's scrypt, at a cost the caller bounds. Btrees are walked fromclean's roots with every bset's crc32c or ChaCha20/Poly1305 MAC checked before it is decrypted. On top: inodes, lookup by SipHash slot with probing, readdir by hash cursor, readlink, and reads over inline data and checked extents. Data checksums are type 5 (crc32c without inversion), type 1, or the 80- and 128-bit MACs. Every key is checked against its btree's allowed types: strict types on read, as upstream reads, and the full table in the writer and fsck.Refused by name:
wide_macs, the key wrapped under a passphrase with the given scrypt cost):cleanat sequence 0;fsck): §3.6's superblock rules, identical copies but for offset and checksum, the root world as mkfs writes it, every node inside the bitmap, every key of a type its btree holds, and no bset or inode stamped with a journal sequence pastclean's.toyos-cryptogains SipHash, moved fromtoyos-net-wire(which re-exports it), and publicchacha::xor,poly1305::tagandpoly1305::verify. The engine composes A10's construction from these; it is not RFC 8439's AEAD.Decisions
clean's sequence is 0, and every stamp is 0. BB-A1-1 asked whether that is accepted on B2's image: it is (below), and upstream resumes the journal from it.Random, and every time from the caller. Its bytes are a function of both, and a test asserts it. B4's reproducible ROOT relies on this.Checks
cargo run -- --ci hostat 62873ce, which runs every test below and clippy with the adopted lintscargo run -- --build-onlyat 62873ceHost tests, reading upstream's own bytes:
crc32c.img.gz: its whole tree, inline data, the symlink and directory link counts, andseq.txtread whole through its fourteen type-5 extents. The fixture's stored 0x7080C241 and A11's check values hold, and standard CRC-32C would give 0xE4E4C9C4 instead.encrypted.img.gz(80-bit MACs, crc64) andencrypted-wide-macs.img.gz(128-bit, crc128): unlocked and read whole,seq.txtbyte for byte with every extent's MAC verified. A wrong passphrase, a locked open and scrypt past the caller's memory are each refused. A flipped byte in an extent, or in a node, fails its MAC.clean. A node without a MAC on an encrypted volume is refused.Negative controls (patch applied, built, run, restored): extent MAC verification skipped; the bset stamp comparison dropped; the inode stamp comparison dropped; the encrypted-volume MAC requirement on nodes dropped. Each turns its test red.
The upstream judge: bcachefs-tools v1.39.4 (commit d997ad76e906, clean tree, built from its tag) under QEMU/HVF on Ubuntu 26.04.1 arm64, kernel 7.0.0-38. It always ran
fsck -n -K < /dev/null, judged by §3.4's rule: exit 0 and no failure marker.bcachefs unlock -k session -f passin fsck's session keyring. Each fresh volume's run isrecovering from clean shutdown, journal seq 0through every pass, with nothing else printed.ls, then one that writesseq 1 40000and a file, then a read-back; on DATA's, the write and the read-back. Each passes the judge after, and every read-back iscontents-ok. Upstream resumed the journal from 0. Sequence 0 with no stamps above it is settled.Error: incorrect passphrase, exit 1; with no key fsck exits 1 (error reading passphrase).btree node read errorandchecksum erroralone. bad5's first run did not apply its damage, because my position parse missed, so kvdb refused and the image stayed clean. Rerun with the parse fixed, it fails.kill_btree_node -e keysand-e csumon the inodes node (the latter exit 0, markers only), a flipped key byte, an alloc counter and a deleted accounting key. On the encrypted volume, a broken MAC and a flipped byte fail too. Both untouched volumes pass.Evidence:
b2/onpr-1032, commit 6263267d8161825dd9c3faf207abac029f79abb4: every command's stdout, stderr and exit status, the guest's build, the generator of the judged volumes and their hashes, which mkfs at 62873ce reproduces byte for byte, and the control patches.No guest test runs here: B2 changes no booted behaviour. The loader and the file server still use the interim format, and the SipHash net-wire uses is the same function, moved.
Round 2 (A11 and review r1), head e51e660
issues/the-bcachefs-reader-refuses-crc32c-data-extents.mdis closed.crc32c.img.gzand mkfs's unencrypted and encrypted volumes, about 5 s in all. It sweeps every superblock header word and each field's first 128 bytes with boundary values in all copies, gives nodes seeded random bytes and words, reseals checksums, and runs the reader and fsck over each mutation, which must never panic. The encrypted fixtures are left out because their default scrypt costs too much per mutation.cargo run -- --ci hostat e51e660: exit 0, 101 of 101 steps green.cargo run -- --build-only: exit 0. Evidence:b2-r2/onpr-1032, commit 19a01c971e8d130ea00567d7219204e0963101c6.Unresolved
fsckv0 does not yet derive alloc, backpointers or accounting from the nodes (B3 grows it). Upstream'scheck_allocationscompared them on every judged volume.🤖 Generated with Claude Code
https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q