Skip to content

Initial reimplementation of composefs-c#225

Draft
cgwalters wants to merge 11 commits intocomposefs:mainfrom
cgwalters:composefs-c-compat
Draft

Initial reimplementation of composefs-c#225
cgwalters wants to merge 11 commits intocomposefs:mainfrom
cgwalters:composefs-c-compat

Conversation

@cgwalters
Copy link
Collaborator

Basically starting on composefs/composefs#423

3 key goals:

  • Compatible CLI interfaces
  • Compatible EROFS output format (this is a big deal!)
  • Next: Compatible C shared library (ugly and messy)

Assisted-by: OpenCode (Claude Sonnet 4)

@cgwalters
Copy link
Collaborator Author

There's definitely some sub-tasks to this and pieces that we need to break out. One that I'm realizing is that the dumpfile format is hardcoded to sha256-12. I guess we can just auto-detect from length (like we're doing in other places) but the more I think about this the more I feel we need to formalize it (as is argued in #224 )

So how about a magic comment in the dumpfile like

# format: sha512-12

or so?

@cgwalters
Copy link
Collaborator Author

Let's make the format layout a choice to avoid breaking sealed UKIs as is today

@cgwalters cgwalters force-pushed the composefs-c-compat branch 3 times, most recently from 8a5c48d to 9cb1923 Compare March 11, 2026 01:41
@cgwalters cgwalters force-pushed the composefs-c-compat branch 2 times, most recently from 89ec8a3 to 1af8fee Compare March 11, 2026 18:42
Move Debug impls for format types and EROFS structures to the top
of the file (before ImageVisitor), extract hexdump helper, and
add missing_debug_implementations allows. Pure reorganization,
no functional changes.

Assisted-by: OpenCode (Claude Opus 4)
Signed-off-by: Colin Walters <walters@verbum.org>
Convert the assert_eq! in ImageVisitor::note() to return an error
instead of panicking when a corrupt image has the same offset visited
as two different segment types. Found by the debug_image fuzz target.

Assisted-by: OpenCode (Claude Opus 4)
Signed-off-by: Colin Walters <walters@verbum.org>
Fix arithmetic operations that could overflow, underflow, or cause
resource exhaustion when processing malformed EROFS images:

- Use checked_mul instead of unchecked << for block address
  calculations in debug.rs
- Use checked_add for block range end computation in reader.rs to
  prevent u64 overflow
- Use usize::BITS instead of hardcoded 64 for blkszbits validation
  (correct on 32-bit platforms)
- Use usize::try_from instead of 'as usize' casts for inode size,
  inode ID, and block ID to avoid silent truncation on 32-bit
- Cap Vec allocation against image length to prevent OOM from crafted
  size fields
- Use saturating_sub for debug display calculations

Assisted-by: OpenCode (Claude Opus 4)
Signed-off-by: Colin Walters <walters@verbum.org>
Replace direct slice indexing with .get() where the bounds come from
image content: XAttr::suffix/value/padding, Inode::inline, and
debug_img's unassigned-region slicing. This prevents panics on
malformed images where field values are inconsistent with actual data
lengths.

Assisted-by: OpenCode (Claude Opus 4)
Signed-off-by: Colin Walters <walters@verbum.org>
…pers

Change XAttr::suffix(), value(), and padding() to return
Result<&[u8], ErofsReaderError> instead of silently returning empty
slices on out-of-bounds access. This ensures corrupt xattr data
is properly reported rather than silently swallowed.

Also deduplicate is_whiteout() (moved to InodeHeader trait method)
and find_child_nid() (moved to Image method), and remove the
redundant entry_nid() test helper in favor of DirectoryEntry::nid().

Assisted-by: OpenCode (Claude Opus 4)
Signed-off-by: Colin Walters <walters@verbum.org>
Add fuzz testing infrastructure under crates/composefs/fuzz/ with two
targets: read_image (exercises the full reader API surface including
inode traversal, xattr parsing, and object collection) and debug_image
(runs the debug_img dump on arbitrary input). Includes a seed corpus
generator that creates valid EROFS images exercising various code paths.

Assisted-by: OpenCode (Claude Opus 4)
Signed-off-by: Colin Walters <walters@verbum.org>
…verflow

A crafted EROFS image with directory cycles can cause unbounded recursion
in populate_directory(), leading to a stack overflow. Add a depth parameter
and enforce a maximum of PATH_MAX / 2 (2048) levels, matching the
theoretical limit for valid filesystem paths.

Found by cargo-fuzz.

Assisted-by: OpenCode (Claude Opus 4)
Signed-off-by: Colin Walters <walters@verbum.org>
The cargo-fuzz targets found multiple panics within seconds of fuzzing.
Convert all remaining .unwrap() calls and assert!() macros in non-test
reader code to return Result, and propagate errors at all call sites.

Key changes:
- data_layout() returns Result instead of unwrapping TryInto
- XAttr::from_prefix(), xattrs(), shared(), local() return Result
- DirectoryBlock::n_entries/entries/get_entry_header return Result
- DirectoryEntries iterator yields Result<DirectoryEntry>
- XAttrIter yields Result<&XAttr>
- All callers in reader.rs, debug.rs, and fuzz targets updated

Assisted-by: OpenCode (Claude Opus 4)
Signed-off-by: Colin Walters <walters@verbum.org>
This got introduced in a CI refactoring and wasn't
intentional. Our fuzzing had way too short of a timeout.
If CI job is actually stuck we'll figure that out when
it happens.

Signed-off-by: Colin Walters <walters@verbum.org>
Add a FormatVersion enum (V1/V2) that controls the EROFS image format:

V1 produces byte-identical output to C mkcomposefs. It sets
composefs_version=0 in the superblock, uses compact inodes where
possible, BFS inode ordering, C-compatible xattr sorting, and
includes overlay whiteout character device entries in the root
directory. The build_time is set to the minimum mtime across all
inodes, matching the C implementation.

V2 remains the default (composefs_version=2). It uses extended
inodes, DFS ordering, and the composefs-rs native xattr layout.

Key V1 writer differences from V2:
- BFS (breadth-first) inode ordering vs DFS (depth-first)
- Compact inodes when uid/gid fit in u16 and mtime == build_time
- Xattr sorting by full key name for C compatibility
- Overlay whiteout char devices (00-ff) added to root directory
- trusted.overlay.opaque=y xattr on root directory

Tests cover both format versions: insta snapshots, proptest
round-trips, fsck validation, and byte-identical comparison against
the C mkcomposefs tool. The fuzz corpus generator also produces
both V1 and V2 seed images.

Assisted-by: OpenCode (Claude Opus 4)
Make cfsctl a multi-call binary that dispatches based on argv[0]:
when invoked as "mkcomposefs" or "composefs-info" (via symlink or
hardlink), it runs the corresponding tool directly. This avoids
separate binary crates while providing drop-in compatibility with
the C composefs tools.

mkcomposefs reads composefs-dump(5) format or directory trees and
produces compatible EROFS images with v1.0/v1.1 format support.

composefs-info inspects composefs/EROFS images: dumps filesystem
trees, lists objects, and displays detailed inode/xattr info.

The integration tests create a symlink from cfsctl to mkcomposefs
for the multi-call dispatch rather than looking for a separate
mkcomposefs binary.

Assisted-by: OpenCode (Claude Opus 4)
@cgwalters cgwalters force-pushed the composefs-c-compat branch from 1af8fee to 6eda766 Compare March 12, 2026 15:50
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.

1 participant