Skip to content

reiserfs: extract 3.5/3.6 volumes (closes #63) - #66

Merged
nmatt0 merged 1 commit into
masterfrom
feat/reiserfs-extract
Oct 17, 2026
Merged

nmatt0 merged 1 commit into
masterfrom
feat/reiserfs-extract

Conversation

@nmatt0

@nmatt0 nmatt0 commented Oct 17, 2026

Copy link
Copy Markdown
Owner

Summary

Adds read-only extraction for ReiserFS (3.5 / 3.6). Identification landed with the legacy-block-filesystem batch (#24); until now -e on a ReiserFS image printed the summary table and wrote nothing, because extractor_for() had no reiserfs case. Closes #63.

What it does

src/extract/reiserfs.{cpp,hpp}, registered in extractor_for():

  • Reads the superblock (root block, block size, block count), recovers the partition base for both the 64 KiB (3.6) and 8 KiB (3.5) superblock placements, and validates the root block before walking.
  • Walks the S+ tree from the root block, collecting every leaf item. Internal nodes are traversed by child pointer only; internal-node keys are never interpreted, so the v1/v2 key-format ambiguity only has to be resolved at leaf items (per item, from ih_version).
  • Rebuilds the tree under the existing SafeRoot: regular files (direct tails plus indirect block lists, with holes honored), directories, and symlinks. Objects are grouped by (dir_id, objectid); each child's stat-data gives its mode, and files are reconstructed from their direct/indirect items in key order.

Per-item key handling matters in practice: a real 3.6 image mixes a v2 stat-data item with a v1 directory item in the same object, which this handles.

Safety

  • Every read goes through the bounds-checked Reader; a truncated or corrupt image yields a partial result, never an out-of-bounds read.
  • Guardrails on node/item/file/entry counts and recursion depth, plus cycle guards on both tree blocks and the directory recursion.
  • A declared file size larger than the image is clamped, so a crafted size field cannot force a giant zero-filled allocation.
  • No journal replay, no write support, no mount, no sudo (unlike mount / debugreiserfs).

Tests

tests/test_reiserfs_extract.py (wired into run.sh and ctest):

  1. Synthetic, always-on: hand-built 3.6 (two-level tree exercising internal-node traversal, v2 stat-data + v1 directory items, indirect items, a tail, a leading hole, a symlink, a nested dir) and 3.5 (single leaf, all-v1, 8 KiB superblock) images, extracted and compared byte-for-byte. The layouts are modeled on real mkreiserfs output verified with debugreiserfs.
  2. Real empty image: mkreiserfs builds an empty filesystem (no mount needed) and moria must identify and extract it cleanly. Self-skips if reiserfsprogs is absent. This checks the parser against genuine on-disk bytes.
  3. Corruption robustness: mangled superblock/tree inputs must not crash and must report a sane status (the ASan build is the OOB oracle).

A populated real round-trip is not possible unprivileged: mkreiserfs has no populate-from-directory mode, and the kernel driver was removed in 6.13, so neither a loop mount nor a userspace populate is available. The synthetic layer (modeled on verified real bytes) plus the real empty-image check cover every code path, and all format constants come from the kernel fs/reiserfs/reiserfs.h header.

fuzz/fuzz_reiserfs.cpp fuzzes the parse-only tree/item/directory/content walk (a fixed valid superblock, fuzzer-controlled tree region, both placements), plus the scan pipeline on the raw bytes.

Docs

ReiserFS added to the extractable-filesystem lists in README.md and moria.1.

moria identified ReiserFS (issue #24) but had no extractor, so -e wrote
nothing. Add src/extract/reiserfs.{cpp,hpp} and register it in extractor_for().

The extractor walks the S+ tree from the superblock's root block, collects
every leaf item, and rebuilds the tree under a SafeRoot: regular files (direct
tails plus indirect block lists, holes honored), directories, and symlinks.
Item key format (v1 "old" / v2 "new") is read per item from ih_version, so a
3.6 image that mixes a v2 stat-data item with a v1 directory item in one object
is handled. Tree traversal follows child pointers only and never interprets an
internal-node key, so the key-format ambiguity only arises at leaf items where
ih_version resolves it. The partition base is recovered for both the 64 KiB
(3.6) and 8 KiB (3.5) superblock placements. All reads go through the
bounds-checked Reader, so a truncated or corrupt image yields a partial result
rather than a crash, and a declared file size larger than the image is clamped
to avoid honoring a crafted size with a giant allocation.

Tests (tests/test_reiserfs_extract.py): synthetic 3.6 (two-level tree, v2
stat-data plus v1 directory items, indirect items, a tail, a leading hole, a
symlink, a nested dir) and 3.5 (single leaf, all-v1, 8 KiB superblock) images
round-trip byte-for-byte; a real empty mkreiserfs image is identified and
extracted (self-skips without reiserfsprogs); mangled superblock and tree
inputs never crash. The hand-built layouts are modeled on real mkreiserfs
output, verified with debugreiserfs. Adds fuzz/fuzz_reiserfs.cpp over the
parse-only tree/item/directory/content walk.
@nmatt0
nmatt0 merged commit 7169193 into master Oct 17, 2026
4 checks passed
@nmatt0
nmatt0 deleted the feat/reiserfs-extract branch October 17, 2026 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ReiserFS: extract the identified volume (identify-only since #24)

1 participant