commits
Top-level [match] joined to a follow-up [match] doesn't parse, and
[Printf.eprintf] / [Format.printf] mixed with the rest. Wrap as
[read_image data = ...], use [Fmt.pr] / [Fmt.epr], add [fmt] to the
mdx libraries.
Pure formatting: dune fmt outdents by 2 spaces and pulls the
multi-line add_file binding into its own block.
Run mdx on lib/squashfs_writer.mli so the {[ ... ]} odoc blocks now
type-check.
The Example used absolute paths ("/", "/bin", "/bin/hello"), but the
writer's own security-considerations note says it "Rejects absolute
paths and paths containing ..". The image build would have raised at
runtime. Switched to relative paths, which is also what the test
suite uses, and added an `assert (String.length image > 0)` so the
example actually documents that finalize produces bytes.
Used Squashfs.Writer.* (the canonical re-export from squashfs.mli)
instead of Squashfs_writer.* throughout, so the example reads as a
user would write it.
The Compression block called `finalize` on a Zstd-configured writer.
The library only implements Gzip -- finalize on Zstd raises
"only gzip compression is currently supported". Wrapped the Zstd
example in `let make_zstd () = ...` so the example shows the API
shape (Squashfs.Writer.Zstd is a real constructor) without invoking
the unsupported codec at mdx test time.
The READMEs all share the standard install/overlay snippet, but the
sh blocks lacked the "<!-- $MDX skip -->" directive. `dune test`
would shell out to `opam install` against the live switch, which
either prompts interactively or fails with a package conflict —
either way diffing as a test failure.
Bulk-add skip directives in front of every install/overlay block.
Also collapse the doubled "non-deterministic + skip" stack on three
READMEs (memtrace, ocaml-dpop, ocaml-pid1, ocaml-yaml, merlint) where
`skip` already implies the runtime is bypassed.
Pure formatting changes from `dune fmt`: doc comment placement moves
from above the binding to below it for `type`s, multi-line `match`
expressions collapse onto one line where they fit, and infix operator
applications pick up spaces (`Soup.($?)` -> `Soup.( $? )`). No
semantic changes.
Object combinators: [Object.mem] -> [Object.member], [Object.opt_mem]
-> [Object.opt_member], [Object.case_mem] -> [Object.case_member]. The
sibling submodules [Object.Mem] / [Object.Mems] become
[Object.Member] / [Object.Members]. RFC 8259 §4 calls these
"name/value pairs, referred to as the members", so mirror the spec
name rather than the shortened [mem].
[Object.finish] -> [Object.seal]. "Seal" reads as "close the map, no
more members added", which is what the operation does.
Value constructors/queries: [Value.mem] (function) -> [Value.member];
[Value.mem_find] -> [Value.member_key]; [Value.mem_names] ->
[Value.member_names]; [Value.mem_keys] -> [Value.member_keys].
[type mem = ...] -> [type member = ...]; [type object'] still points
at [member list].
Downstream (~80 files across slack, sbom, stripe, sigstore, requests,
claude, irmin, freebox) updated via perl-pie. dune build clean,
dune test ocaml-json clean.
Follow up to the module rename: update the remaining callers that
still referenced [Err] (library [claude.ml{,i}], [client.ml], the test
driver [test.ml]), and fix one stray [^ e] string concatenation in
hermest's CLI that needed [Json.Error.to_string e] now that
[Json.of_string] yields a structured error.
Warning 69 (unused-field, mutable-never-assigned). Four independent
record fields were flagged as mutable but the code only mutates their
referents in place, never rebinds the record slot itself:
- ocaml-wal/lib/wal.ml: [t.file] (the Eio file resource; methods call
Eio.File.pwrite_all etc., the slot is set once at open time).
- ocaml-block/lib/block.ml: [Memory.state.data] (the backing bytes,
written via Bytes.blit_string; [Bytes.t] is already mutable).
- ocaml-sse/lib/sse.ml: [Parser.t.data_buf] (a Buffer.t, written via
Buffer.add_*; the slot never changes).
- ocaml-zephyr/lib/zephyr.ml: drop [mode : Read | Write] entirely —
set at open-time, read nowhere. The open_read / open_write
constructors already distinguish the two call shapes, so mode
tracking was redundant.
Previously the eight git-x subcommands sat flat at top level (split,
check, fix, verify, filter-paths, split-commit, drop-commit, reword),
with 'split' ambiguous between the subtree split and the per-directory
commit split.
Regrouped into two namespaces that mirror the object they act on:
git-x tree
split (was: git-x split)
add (new — inject a standalone history under a prefix)
drop (was: git-x filter-paths)
check (was: git-x check)
fix (was: git-x fix)
verify (was: git-x verify)
git-x commit
split (was: git-x split-commit)
drop (was: git-x drop-commit)
reword (was: git-x reword)
Each subcommand lives in cmd_<group>_<verb>.{ml,mli}; cmd_tree.ml and
cmd_commit.ml are the Cmd.group wrappers. git_x.ml registers just the
two groups.
'tree add' is a thin wrapper over Git.Subtree.add, which already
existed in the library but had no CLI exposure. It accepts a ref (e.g.
FETCH_HEAD after 'git fetch URL REF') and a --prefix, then builds a
subtree-merge commit with the current user's git config identity.
Log source names are updated to match (git-x.tree.split,
git-x.tree.fix). The cram test under test/cram/tree_split.t is
updated to use the new 'git-x tree split' invocation throughout.
Wire_3d now generates ExternalTypedefs.h, ExternalAPI.h, Wrapper,
and Fields files for schemas using WireCtx. Bitfield padding is
fixed so .3d structs match the OCaml codec wire_size. Test.c passes
NULL for the WIRECTX parameter and links per-schema Field stubs.
Affected: ax25, cfdp, fsr, ltp, mbr, pid1, pus, rpmsg, sdls,
spacefibre, squashfs, tcpcl, udpcl, spacewire, and others via @gen.
Generate .opam.template files with x-quality-* fields based on
detected package features:
- x-quality-build: has lib/ with .ml files
- x-quality-test: has test/ with .ml files
- x-quality-fuzz: has fuzz/ with .ml files
- x-quality-interop: has test/interop/ directory
- x-quality-cram: has test/*.t/ directories
These fields are picked up by dune's opam generation and will be
checked by merlint E910 for consistency.
Also: add fmt dep to ocaml-sse/lib/dune (Fmt.pf used without dep).
monopam quality — scans packages for quality features, caches by
git commit hash. 166 packages: build=163, test=162, fuzz=94,
interop=39, doc=42.
Standard vocabulary based on crates.io categories, erratique/opam
conventions, and monorepo domain coverage:
Org: org:blacksun
Domain: aerospace, codec, crypto, network, storage, git, merkle
Purpose: cli, test, bench, format, log, system
Protocol: ccsds, uslp, cop1, sdls, sle, atproto, tls, http, json, binary
Cross-cutting: eio, simulation, math, compression
Tags placed in dune-project (package ...) stanzas via (tags ...).
Propagated to .opam files by dune's opam generation.
Replace failwith (Fmt.str ...) with Fmt.failwith ... in spacedata,
squashfs, sdls, and sigstore.
Creates c/gen.ml + c/dune + generated .3d files for every package
that defines Wire codecs. Each gen.ml calls Wire_3d.main to project
the OCaml Wire codec definition to EverParse 3D format.
Packages: FSR, PUS, GPT, MBR, PID1, LTP, TCPCL (8 codecs), UDPCL,
AX.25, CFDP (3 fixed-size codecs), SDLS (EP header + MC status),
SpaceWire, RPMsg, SquashFS (5 codecs), SpaceFibre.
Codecs with Wire.Param.input (variable-size) are excluded from 3D
generation: CFDP header/eof/metadata/keep-alive, SDLS security
header/trailer, PUS tm_header/hk_param.
Proximity-1 and space-wire excluded: proximity-1 has only variable-
size codecs, space-wire has its own C generation approach.
Fixes E900 (17→2) and E905 (remaining MBR Partition.struct_).
- Update .ocamlformat to 0.29.0 across all 591 files
- csvt: reuse single Buffer.t for field reads (no alloc per field)
- sexpt: Obj members decoded from stream into Dict, typed Variant GADT
- Reformat all source files for 0.29.0
Migrate all consumers to the new wire API:
- wire.c library → wire.3d (Wire_c → Wire_3d)
- Wire.struct_/module_ → Wire.Everparse.struct_/module_
- Wire.Codec: record/|+/seal → Codec.v with Field.v and $
- Wire.bf_uint* → Wire.U8/U16/U16be/U32/U32be
- Wire.UInt32 → Wire.Private.UInt32
- Wire.cases → Wire.lookup, Wire.map now uses labeled args
- Wire.Codec.decode now returns result
- Add wire pin to root.opam.template
160 new tests exercising security-critical code paths identified by
mapping known CVEs from C/reference implementations to our OCaml code:
- ocaml-sqlite (9): cyclic pages, oversized varints, record overflow,
wrong page kind, truncated WAL, out-of-bounds root, garbage files
- ocaml-cbort (12): deep nesting (CVE-2025-24302), indefinite-length
DoS, integer overflow in lengths, truncated input, invalid types
- ocaml-tar (10): path traversal (CVE-2021-32803), symlink escape
(CVE-2025-45582), oversized octal, truncated headers, checksum
- ocaml-http (14): CRLF header injection (CWE-113), null bytes,
Content-Length overflow, empty/duplicate headers
Also hardens validate_header_name_str to reject null bytes/empty names
- ocaml-jsonwt (21): "none" algorithm bypass (CVE-2015-9235) case
variations, algorithm confusion (CVE-2016-10555), malformed headers,
empty segments, extra dots, large payloads
- ocaml-cose (8): algorithm substitution, missing algorithm header,
malformed CBOR, wrong types, label overlap (RFC 9052)
- ocaml-git (18): tree path traversal, null bytes, symlink mode,
malformed tree data, pack delta attacks, pack format validation
- ocaml-tomlt (25): duplicate keys, integer overflow, malformed dates
(invalid month/day/hour/minute), deep nesting, long strings
- ocaml-squashfs (20): symlink traversal edge cases, fragment table
bounds, inode self-reference, compression bomb limits, bad superblock
- ocaml-cpio (23): symlink target validation, null bytes in filenames,
oversized filesize, truncated archives, invalid magic numbers
import used List.find_opt on raw map pairs, so a duplicate
"version" or "entries" key would shadow later occurrences.
Now checks for byte-equal duplicate keys before field extraction,
consistent with the receipt parser's cbor_check_unique_keys.
Ensure all 67 fuzz/dune files include gen_corpus.exe in the (alias fuzz)
rule deps for AFL corpus generation. Adds both missing runtest and fuzz
rules to ocaml-cose which had neither.
Fix invalid odoc markup in 54 files: convert {\!Module} to {!Module}
in fuzz .mli files, replace inline {v ... v} with [...] code spans,
fix "paragraph should begin on its own line" warnings, escape bare
brackets, and resolve ambiguous docstring placement (warning 50).
- Remove vendored crowbar/ directory
- Replace all Crowbar references with Alcobar across 176 .ml files
- Update all fuzz dune files: crowbar → alcobar in libraries
- Remove 77 gen_corpus.ml files (alcobar handles corpus internally)
- Update dune-project files: crowbar → alcobar in dependencies
- Update merlint rules (e705, e726): Crowbar → Alcobar in checks,
docs, and examples
- Update merlint generated docs (index.html)
428 files changed, ~1200 lines removed net.
Adds 108 missing dependency declarations across 52 packages.
Most common missing dep was fmt (38 packages), followed by wire,
eio, and bytesrw. Also improves lint output with tty tables and
better subtree filtering display.
- squashfs.ml: make_superblock → superblock
- squashfs_writer.ml: make_superblock → superblock, make_inode_buf → inode_buf
- test_squashfs.ml: make_image → image
- test_streaming_aead.ml: make_key → key
Rename make_superblock → superblock, make_inode_buf → inode_buf,
make_image → image, make_key → key in squashfs and streaming-aead.
Add doc comments for fuzz suite values and squashfs extended codec
values.
The linter re-created test_vectors.ml with proper RFC-sourced vectors
(NIST SP 800-38D, RFC 5116, RFC 5869). Accept the linter's version and
wire it into the test suite.
- Replace Printf.sprintf/printf with Fmt.str/pr in fuzz_squashfs, gen_corpus
- Replace failwith (Fmt.str ...) with Fmt.failwith in squashfs.ml (E215)
- Replace invalid_arg (Fmt.str ...) with Fmt.invalid_arg in squashfs_writer, tc (E216)
- Add fmt to gen_corpus dune deps for squashfs, srp, streaming-aead, tar, tc
- squashfs_writer.ml: extract 8 helpers from finalize (356→~50 lines)
- squashfs.ml: extract per-type parsers from parse_inode (80→~20 lines)
- streaming_aead.ml: extract encrypt_segment, decrypt_segments helpers
- tar.ml: extract decode_next_longlink and decode_active helpers
Rename GlobalExtendedHeader → Global_extended_header,
PerFileExtendedHeader → Per_file_extended_header, LongLink → Long_link,
LongName → Long_name, OldGNU → Old_GNU across tar lib, mli, and fuzz.
E600: Add missing .mli files for test modules and flatten test suites
to single pairs. E410: Add trailing periods to @param/@raise doc lines,
fix [name] format in tar and tc docs. Also add pp functions to squashfs
types and handle linter-triggered renames (create→v, make_header→header).
Add module doc comments to tar_gz.mli and test_vectors.mli, and value
doc comments across tar, tc, streaming-aead, squashfs, and srp.
- squashfs_writer.mli: add periods to @param/@raise/@item tags
- streaming_aead.mli: add periods to @return tags
- tar_eio.mli: fix value/append_file/header_of_file doc style
- tar.mli: fix fold [name args] format
- tc.mli: add periods, fix make_header [name args] format
- Change `run` signature to `string -> (string * test_case list) list -> unit`
matching Alcotest's grouping convention
- Fix `_name` bug: pass the name through to Alcotest.run_with_args
- Each fuzz module now exports `let suite = ("name", [test_case ...])`
- Entry points (fuzz.ml) collect suites: `Crowbar.run "pkg" [Fuzz_X.suite]`
- Remove stale `add_test`/`suite` API, keep only `test_case`/`run`
- Remove `let run () = ()` from fuzz_common.ml files
- Update merlint E725 rule to match new `let suite = ("name", ...)` pattern
- Update E725 test fixtures and expected output
- Restore cursor on exit via at_exit in Tty.Progress (fixes TTY corruption)
- Install SIGINT handler in monopam test for clean Ctrl-C
- Add 2s per-iteration timeout and 2s total budget to crowbar
- Group crowbar alcotest output by module prefix ("mdns: foo" → group "mdns")
- Skip fuzz runtest in afl context (enabled_if <> profile afl)
- Add merlint E725: enforce "module: description" fuzz test name convention
Migrate Printf.sprintf to Fmt.str, Format.fprintf to Fmt.pf, and
Format.pp_print_string to Fmt.string across bundle, gpt, hap, homebrew,
jsonwt, matter, mbr, meross, paseto, precommit, publicsuffix, qemu,
retry, sdnv, slack, sle, space-packet, spake2, sqlite, squashfs, tar,
tc, tcf, tcpcl, tm, tomlt, tty, uslp, vlog, wal, wire, yamlrw, yamlt,
osrelease, space, xdge, and crypto test runner.
Replace manual binary helpers (get_u8/get_u16_le/get_u32_le/get_i32_le/
get_u64_le in reader; set_u8/set_u16_le/set_u32_le/set_u64_le in writer)
with Wire.Codec definitions for all multi-field on-disk structures:
superblock (96 bytes), inode header (16 bytes), inode bodies (directory,
file, device, IPC, symlink), directory header (12 bytes), and directory
entry header (8 bytes).
Keep minimal get_u16_le/get_u32_le/get_u64_le in reader and set_u16_le
in writer for single-field reads at dynamic offsets (metadata block
headers, ID table entries).
- License -> Licence
- color -> colour (in prose, not API/code)
- behavior -> behaviour
- analyze -> analyse
- organized -> organised
- Remove marketing buzzwords (leveraging)
- Remove emojis from prose
Convert all packages from:
(source (uri https://tangled.org/handle/repo))
to:
(source (tangled handle/repo))
This uses dune 3.21's native tangled support for cleaner source
declarations. Also removes redundant homepage/bug_reports fields
that are auto-generated from tangled sources.
Seeds added for disk/archive format parsers:
- ocaml-gpt: signature (8B), header_only (100B)
- ocaml-mbr: header_only (512B), zeroes (512B valid MBR)
- ocaml-cpio: header (110B), valid_archive (248B)
- ocaml-squashfs: magic (4B), superblock (96B)
- ocaml-tar: header (512B), valid_entry (1.5KB)
All seeds kept small (<1KB where possible) for efficient AFL mutation.
Fixes:
- cpio: Validate namesize > 0 before String.sub (crash on malformed input)
- squashfs: Fix compression test to check roundtrip validity
Add Squashfs.Writer module for creating SquashFS compressed filesystem
images:
- Full entry type support: files, directories, symlinks, devices, fifos,
sockets
- Gzip compression via bytesrw.zlib
- Automatic parent directory creation
- Path validation (rejects absolute paths and traversal)
- Block size configuration (4KB-1MB, power of 2)
- Statistics tracking (file/dir/symlink/device counts)
- Streaming output via bytesrw writer
Also fixes superblock field offsets in reader to match squashfs spec:
- offset 48: id_table_start
- offset 64: inode_table_start
- offset 72: directory_table_start
Includes comprehensive unit tests and fuzz tests for the writer.
Security fixes based on CVE research in C squashfs-tools:
- CVE-2015-4645: Integer overflow in fragment table
- CVE-2015-4646: DoS via crafted input
- CVE-2012-4025: Integer overflow via crafted block_log
- CVE-2021-40153: Directory traversal via symbolic link
Mitigations added:
- Block size validation (max 1MB per SquashFS spec)
- File size limits for read_file (default 100MB)
- Bounds checking for all metadata reads
- Symlink path traversal detection (is_path_traversal, safe_read_link)
- ID table bounds validation
- Device node detection helper (is_device)
Security documentation added to .mli with extraction guidelines.
CVE regression tests added to test suite.
Fuzz tests expanded with crafted superblock values.
References:
- https://www.cvedetails.com/vulnerability-list/vendor_id-16355/Squashfs-Project.html
- Fix decompress library dependency (use decompress.de decompress.zl)
- Rewrite decompression to use correct Zl.Inf API
- Rename inode.data field to inode.inode_data to avoid shadowing
Run mdx on lib/squashfs_writer.mli so the {[ ... ]} odoc blocks now
type-check.
The Example used absolute paths ("/", "/bin", "/bin/hello"), but the
writer's own security-considerations note says it "Rejects absolute
paths and paths containing ..". The image build would have raised at
runtime. Switched to relative paths, which is also what the test
suite uses, and added an `assert (String.length image > 0)` so the
example actually documents that finalize produces bytes.
Used Squashfs.Writer.* (the canonical re-export from squashfs.mli)
instead of Squashfs_writer.* throughout, so the example reads as a
user would write it.
The Compression block called `finalize` on a Zstd-configured writer.
The library only implements Gzip -- finalize on Zstd raises
"only gzip compression is currently supported". Wrapped the Zstd
example in `let make_zstd () = ...` so the example shows the API
shape (Squashfs.Writer.Zstd is a real constructor) without invoking
the unsupported codec at mdx test time.
The READMEs all share the standard install/overlay snippet, but the
sh blocks lacked the "<!-- $MDX skip -->" directive. `dune test`
would shell out to `opam install` against the live switch, which
either prompts interactively or fails with a package conflict —
either way diffing as a test failure.
Bulk-add skip directives in front of every install/overlay block.
Also collapse the doubled "non-deterministic + skip" stack on three
READMEs (memtrace, ocaml-dpop, ocaml-pid1, ocaml-yaml, merlint) where
`skip` already implies the runtime is bypassed.
Object combinators: [Object.mem] -> [Object.member], [Object.opt_mem]
-> [Object.opt_member], [Object.case_mem] -> [Object.case_member]. The
sibling submodules [Object.Mem] / [Object.Mems] become
[Object.Member] / [Object.Members]. RFC 8259 §4 calls these
"name/value pairs, referred to as the members", so mirror the spec
name rather than the shortened [mem].
[Object.finish] -> [Object.seal]. "Seal" reads as "close the map, no
more members added", which is what the operation does.
Value constructors/queries: [Value.mem] (function) -> [Value.member];
[Value.mem_find] -> [Value.member_key]; [Value.mem_names] ->
[Value.member_names]; [Value.mem_keys] -> [Value.member_keys].
[type mem = ...] -> [type member = ...]; [type object'] still points
at [member list].
Downstream (~80 files across slack, sbom, stripe, sigstore, requests,
claude, irmin, freebox) updated via perl-pie. dune build clean,
dune test ocaml-json clean.
Follow up to the module rename: update the remaining callers that
still referenced [Err] (library [claude.ml{,i}], [client.ml], the test
driver [test.ml]), and fix one stray [^ e] string concatenation in
hermest's CLI that needed [Json.Error.to_string e] now that
[Json.of_string] yields a structured error.
Warning 69 (unused-field, mutable-never-assigned). Four independent
record fields were flagged as mutable but the code only mutates their
referents in place, never rebinds the record slot itself:
- ocaml-wal/lib/wal.ml: [t.file] (the Eio file resource; methods call
Eio.File.pwrite_all etc., the slot is set once at open time).
- ocaml-block/lib/block.ml: [Memory.state.data] (the backing bytes,
written via Bytes.blit_string; [Bytes.t] is already mutable).
- ocaml-sse/lib/sse.ml: [Parser.t.data_buf] (a Buffer.t, written via
Buffer.add_*; the slot never changes).
- ocaml-zephyr/lib/zephyr.ml: drop [mode : Read | Write] entirely —
set at open-time, read nowhere. The open_read / open_write
constructors already distinguish the two call shapes, so mode
tracking was redundant.
Previously the eight git-x subcommands sat flat at top level (split,
check, fix, verify, filter-paths, split-commit, drop-commit, reword),
with 'split' ambiguous between the subtree split and the per-directory
commit split.
Regrouped into two namespaces that mirror the object they act on:
git-x tree
split (was: git-x split)
add (new — inject a standalone history under a prefix)
drop (was: git-x filter-paths)
check (was: git-x check)
fix (was: git-x fix)
verify (was: git-x verify)
git-x commit
split (was: git-x split-commit)
drop (was: git-x drop-commit)
reword (was: git-x reword)
Each subcommand lives in cmd_<group>_<verb>.{ml,mli}; cmd_tree.ml and
cmd_commit.ml are the Cmd.group wrappers. git_x.ml registers just the
two groups.
'tree add' is a thin wrapper over Git.Subtree.add, which already
existed in the library but had no CLI exposure. It accepts a ref (e.g.
FETCH_HEAD after 'git fetch URL REF') and a --prefix, then builds a
subtree-merge commit with the current user's git config identity.
Log source names are updated to match (git-x.tree.split,
git-x.tree.fix). The cram test under test/cram/tree_split.t is
updated to use the new 'git-x tree split' invocation throughout.
Wire_3d now generates ExternalTypedefs.h, ExternalAPI.h, Wrapper,
and Fields files for schemas using WireCtx. Bitfield padding is
fixed so .3d structs match the OCaml codec wire_size. Test.c passes
NULL for the WIRECTX parameter and links per-schema Field stubs.
Affected: ax25, cfdp, fsr, ltp, mbr, pid1, pus, rpmsg, sdls,
spacefibre, squashfs, tcpcl, udpcl, spacewire, and others via @gen.
Generate .opam.template files with x-quality-* fields based on
detected package features:
- x-quality-build: has lib/ with .ml files
- x-quality-test: has test/ with .ml files
- x-quality-fuzz: has fuzz/ with .ml files
- x-quality-interop: has test/interop/ directory
- x-quality-cram: has test/*.t/ directories
These fields are picked up by dune's opam generation and will be
checked by merlint E910 for consistency.
Also: add fmt dep to ocaml-sse/lib/dune (Fmt.pf used without dep).
Standard vocabulary based on crates.io categories, erratique/opam
conventions, and monorepo domain coverage:
Org: org:blacksun
Domain: aerospace, codec, crypto, network, storage, git, merkle
Purpose: cli, test, bench, format, log, system
Protocol: ccsds, uslp, cop1, sdls, sle, atproto, tls, http, json, binary
Cross-cutting: eio, simulation, math, compression
Tags placed in dune-project (package ...) stanzas via (tags ...).
Propagated to .opam files by dune's opam generation.
Creates c/gen.ml + c/dune + generated .3d files for every package
that defines Wire codecs. Each gen.ml calls Wire_3d.main to project
the OCaml Wire codec definition to EverParse 3D format.
Packages: FSR, PUS, GPT, MBR, PID1, LTP, TCPCL (8 codecs), UDPCL,
AX.25, CFDP (3 fixed-size codecs), SDLS (EP header + MC status),
SpaceWire, RPMsg, SquashFS (5 codecs), SpaceFibre.
Codecs with Wire.Param.input (variable-size) are excluded from 3D
generation: CFDP header/eof/metadata/keep-alive, SDLS security
header/trailer, PUS tm_header/hk_param.
Proximity-1 and space-wire excluded: proximity-1 has only variable-
size codecs, space-wire has its own C generation approach.
Fixes E900 (17→2) and E905 (remaining MBR Partition.struct_).
Migrate all consumers to the new wire API:
- wire.c library → wire.3d (Wire_c → Wire_3d)
- Wire.struct_/module_ → Wire.Everparse.struct_/module_
- Wire.Codec: record/|+/seal → Codec.v with Field.v and $
- Wire.bf_uint* → Wire.U8/U16/U16be/U32/U32be
- Wire.UInt32 → Wire.Private.UInt32
- Wire.cases → Wire.lookup, Wire.map now uses labeled args
- Wire.Codec.decode now returns result
- Add wire pin to root.opam.template
160 new tests exercising security-critical code paths identified by
mapping known CVEs from C/reference implementations to our OCaml code:
- ocaml-sqlite (9): cyclic pages, oversized varints, record overflow,
wrong page kind, truncated WAL, out-of-bounds root, garbage files
- ocaml-cbort (12): deep nesting (CVE-2025-24302), indefinite-length
DoS, integer overflow in lengths, truncated input, invalid types
- ocaml-tar (10): path traversal (CVE-2021-32803), symlink escape
(CVE-2025-45582), oversized octal, truncated headers, checksum
- ocaml-http (14): CRLF header injection (CWE-113), null bytes,
Content-Length overflow, empty/duplicate headers
Also hardens validate_header_name_str to reject null bytes/empty names
- ocaml-jsonwt (21): "none" algorithm bypass (CVE-2015-9235) case
variations, algorithm confusion (CVE-2016-10555), malformed headers,
empty segments, extra dots, large payloads
- ocaml-cose (8): algorithm substitution, missing algorithm header,
malformed CBOR, wrong types, label overlap (RFC 9052)
- ocaml-git (18): tree path traversal, null bytes, symlink mode,
malformed tree data, pack delta attacks, pack format validation
- ocaml-tomlt (25): duplicate keys, integer overflow, malformed dates
(invalid month/day/hour/minute), deep nesting, long strings
- ocaml-squashfs (20): symlink traversal edge cases, fragment table
bounds, inode self-reference, compression bomb limits, bad superblock
- ocaml-cpio (23): symlink target validation, null bytes in filenames,
oversized filesize, truncated archives, invalid magic numbers
- Remove vendored crowbar/ directory
- Replace all Crowbar references with Alcobar across 176 .ml files
- Update all fuzz dune files: crowbar → alcobar in libraries
- Remove 77 gen_corpus.ml files (alcobar handles corpus internally)
- Update dune-project files: crowbar → alcobar in dependencies
- Update merlint rules (e705, e726): Crowbar → Alcobar in checks,
docs, and examples
- Update merlint generated docs (index.html)
428 files changed, ~1200 lines removed net.
- Replace Printf.sprintf/printf with Fmt.str/pr in fuzz_squashfs, gen_corpus
- Replace failwith (Fmt.str ...) with Fmt.failwith in squashfs.ml (E215)
- Replace invalid_arg (Fmt.str ...) with Fmt.invalid_arg in squashfs_writer, tc (E216)
- Add fmt to gen_corpus dune deps for squashfs, srp, streaming-aead, tar, tc
- Change `run` signature to `string -> (string * test_case list) list -> unit`
matching Alcotest's grouping convention
- Fix `_name` bug: pass the name through to Alcotest.run_with_args
- Each fuzz module now exports `let suite = ("name", [test_case ...])`
- Entry points (fuzz.ml) collect suites: `Crowbar.run "pkg" [Fuzz_X.suite]`
- Remove stale `add_test`/`suite` API, keep only `test_case`/`run`
- Remove `let run () = ()` from fuzz_common.ml files
- Update merlint E725 rule to match new `let suite = ("name", ...)` pattern
- Update E725 test fixtures and expected output
- Restore cursor on exit via at_exit in Tty.Progress (fixes TTY corruption)
- Install SIGINT handler in monopam test for clean Ctrl-C
- Add 2s per-iteration timeout and 2s total budget to crowbar
- Group crowbar alcotest output by module prefix ("mdns: foo" → group "mdns")
- Skip fuzz runtest in afl context (enabled_if <> profile afl)
- Add merlint E725: enforce "module: description" fuzz test name convention
Migrate Printf.sprintf to Fmt.str, Format.fprintf to Fmt.pf, and
Format.pp_print_string to Fmt.string across bundle, gpt, hap, homebrew,
jsonwt, matter, mbr, meross, paseto, precommit, publicsuffix, qemu,
retry, sdnv, slack, sle, space-packet, spake2, sqlite, squashfs, tar,
tc, tcf, tcpcl, tm, tomlt, tty, uslp, vlog, wal, wire, yamlrw, yamlt,
osrelease, space, xdge, and crypto test runner.
Replace manual binary helpers (get_u8/get_u16_le/get_u32_le/get_i32_le/
get_u64_le in reader; set_u8/set_u16_le/set_u32_le/set_u64_le in writer)
with Wire.Codec definitions for all multi-field on-disk structures:
superblock (96 bytes), inode header (16 bytes), inode bodies (directory,
file, device, IPC, symlink), directory header (12 bytes), and directory
entry header (8 bytes).
Keep minimal get_u16_le/get_u32_le/get_u64_le in reader and set_u16_le
in writer for single-field reads at dynamic offsets (metadata block
headers, ID table entries).
Seeds added for disk/archive format parsers:
- ocaml-gpt: signature (8B), header_only (100B)
- ocaml-mbr: header_only (512B), zeroes (512B valid MBR)
- ocaml-cpio: header (110B), valid_archive (248B)
- ocaml-squashfs: magic (4B), superblock (96B)
- ocaml-tar: header (512B), valid_entry (1.5KB)
All seeds kept small (<1KB where possible) for efficient AFL mutation.
Fixes:
- cpio: Validate namesize > 0 before String.sub (crash on malformed input)
- squashfs: Fix compression test to check roundtrip validity
Add Squashfs.Writer module for creating SquashFS compressed filesystem
images:
- Full entry type support: files, directories, symlinks, devices, fifos,
sockets
- Gzip compression via bytesrw.zlib
- Automatic parent directory creation
- Path validation (rejects absolute paths and traversal)
- Block size configuration (4KB-1MB, power of 2)
- Statistics tracking (file/dir/symlink/device counts)
- Streaming output via bytesrw writer
Also fixes superblock field offsets in reader to match squashfs spec:
- offset 48: id_table_start
- offset 64: inode_table_start
- offset 72: directory_table_start
Includes comprehensive unit tests and fuzz tests for the writer.
Security fixes based on CVE research in C squashfs-tools:
- CVE-2015-4645: Integer overflow in fragment table
- CVE-2015-4646: DoS via crafted input
- CVE-2012-4025: Integer overflow via crafted block_log
- CVE-2021-40153: Directory traversal via symbolic link
Mitigations added:
- Block size validation (max 1MB per SquashFS spec)
- File size limits for read_file (default 100MB)
- Bounds checking for all metadata reads
- Symlink path traversal detection (is_path_traversal, safe_read_link)
- ID table bounds validation
- Device node detection helper (is_device)
Security documentation added to .mli with extraction guidelines.
CVE regression tests added to test suite.
Fuzz tests expanded with crafted superblock values.
References:
- https://www.cvedetails.com/vulnerability-list/vendor_id-16355/Squashfs-Project.html