#!/bin/sh set -eu script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) fixture_dir=${FIXTURE_DIR:-"$script_dir/fixture"} artifact_dir=${ARTIFACT_DIR:-"$script_dir/artifacts"} for tool in mkfs.erofs fsck.erofs dump.erofs python3 sha256sum; do command -v "$tool" >/dev/null 2>&1 || { echo "missing tool: $tool" >&2 exit 1 } done test "$(id -u)" -eq 0 || { echo "root is required to create device-node fixtures" >&2 exit 1 } test ! -e "$fixture_dir" || { echo "fixture directory already exists: $fixture_dir" >&2 exit 1 } test ! -e "$artifact_dir" || { echo "artifact directory already exists: $artifact_dir" >&2 exit 1 } mkdir -p \ "$fixture_dir/inline" \ "$fixture_dir/special" \ "$fixture_dir/namei/alpha/bravo/charlie" \ "$fixture_dir/namei/alpha/sibling" \ "$fixture_dir/namei/wide" \ "$fixture_dir/pager" \ "$fixture_dir/nfs/basic/subdir" \ "$fixture_dir/nlink/subdir" \ "$artifact_dir" printf 'inline-tail-payload-0123456789\n' > "$fixture_dir/inline/inline.txt" printf 'special target\n' > "$fixture_dir/special/target" ln -s target "$fixture_dir/special/link" mknod "$fixture_dir/special/char-large" c 2748 344865 mknod "$fixture_dir/special/block-large" b 2748 344865 mkfifo "$fixture_dir/special/fifo" printf 'cold nested lookup payload\n' > \ "$fixture_dir/namei/alpha/bravo/charlie/payload.txt" printf 'repeat lookup payload\n' > \ "$fixture_dir/namei/alpha/bravo/repeat.txt" printf 'sibling marker\n' > "$fixture_dir/namei/alpha/sibling/marker.txt" index=0 while [ "$index" -lt 320 ]; do name=$(printf 'entry-%03d-abcdefghijklmnopqrstuvwxyz.txt' "$index") printf 'wide entry %03d\n' "$index" > "$fixture_dir/namei/wide/$name" index=$((index + 1)) done python3 - "$fixture_dir" <<'PY' from pathlib import Path import sys root = Path(sys.argv[1]) data = bytes(((index * 131 + 17) ^ (index >> 3)) & 0xff for index in range(5 * 4096 + 731)) (root / "pager" / "pager.bin").write_bytes(data) (root / "nfs" / "basic" / "regular.txt").write_text( "stable file handle payload\n", encoding="ascii") (root / "nfs" / "basic" / "subdir" / "child.txt").write_text( "child\n", encoding="ascii") PY ln -s regular.txt "$fixture_dir/nfs/basic/link-to-regular" mkfifo "$fixture_dir/nfs/basic/test.fifo" printf 'single\n' > "$fixture_dir/nlink/single.txt" printf 'hardlinked\n' > "$fixture_dir/nlink/hard-a.txt" ln "$fixture_dir/nlink/hard-a.txt" "$fixture_dir/nlink/hard-b.txt" printf 'child\n' > "$fixture_dir/nlink/subdir/child.txt" find "$fixture_dir" -exec touch -h -t 197001010000.00 {} + build_image() { uuid=$1 image=$2 source=$3 shift 3 mkfs.erofs -d0 -x-1 -T0 --all-time --all-root --workers=1 \ -U "$uuid" "$@" "$artifact_dir/$image" "$fixture_dir/$source" } build_image 11111111-2222-3333-4444-555555555551 inline.erofs inline build_image 11111111-2222-3333-4444-555555555552 \ special-compact.erofs special -E force-inode-compact build_image 11111111-2222-3333-4444-555555555553 \ special-extended.erofs special -E force-inode-extended build_image 11111111-2222-3333-4444-555555555554 namei-base.erofs namei build_image 11111111-2222-3333-4444-555555555555 \ pager-plain.erofs pager -E noinline_data build_image 11111111-2222-3333-4444-555555555556 \ pager-lz4.erofs pager -z lz4 build_image aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeee1 nfs-a.erofs nfs build_image aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeee2 nfs-b.erofs nfs build_image 11111111-2222-3333-4444-555555555557 \ nlink.erofs nlink -E force-inode-compact ARTIFACT_DIR="$artifact_dir" python3 <<'PY' from pathlib import Path import hashlib import os import struct artifact_dir = Path(os.environ["ARTIFACT_DIR"]) def u16(image, offset): return struct.unpack_from("> 1) ^ ( polynomial if checksum & 1 else 0) put_u32(image, 1028, checksum & 0xFFFFFFFF) def inode_offset(image, nid): block_bits = image[1036] meta_blkaddr = u32(image, 1064) return (meta_blkaddr << block_bits) + (nid << 5) def compact_inode(image, nid): offset = inode_offset(image, nid) inode_format = u16(image, offset) assert (inode_format & 1) == 0 return offset, (inode_format >> 1) & 7, u32(image, offset + 8) def inline_dir_entries(image, nid): inode = inode_offset(image, nid) inode_format = u16(image, inode) inode_size = 64 if inode_format & 1 else 32 layout = (inode_format >> 1) & 7 size = (struct.unpack_from("= 12 and first_nameoff % 12 == 0 count = first_nameoff // 12 entries = [] for index in range(count): entry = data + index * 12 child_nid, nameoff = struct.unpack_from(" block_size inline_cross = bytearray(inline) put_u16(inline_cross, inline_inode + 2, inline_cross_xattr_icount) write_image("inline-cross-block.erofs", inline_cross) namei = bytearray((artifact_dir / "namei-base.erofs").read_bytes()) root_nid = u16(namei, 1038) root_inode, root_data, root_size, root_entries = inline_dir_entries( namei, root_nid) wide_nid = next(nid for nid, _, _, name in root_entries if name == b"wide") wide_inode, wide_layout, _ = compact_inode(namei, wide_nid) assert wide_layout == 2 block_bits = namei[1036] wide_block = u32(namei, wide_inode + 16) << block_bits wide_first_nameoff = u16(namei, wide_block + 8) wide_count = wide_first_nameoff // 12 wide_last_nameoff = u16(namei, wide_block + (wide_count - 1) * 12 + 8) padding_nul = namei.index(0, wide_block + wide_last_nameoff, wide_block + (1 << block_bits)) nonzero_padding = bytearray(namei) nonzero_padding[padding_nul + 1:padding_nul + 9] = b"PAD!ERO!" write_image("namei-padding-nonzero.erofs", nonzero_padding) short_block = bytearray(namei) put_u32(short_block, root_inode + 8, 8) write_image("namei-corrupt-short.erofs", short_block) bad_nameoff = bytearray(namei) first_nameoff = u16(bad_nameoff, root_data + 8) put_u16(bad_nameoff, root_data + 12 + 8, first_nameoff) write_image("namei-corrupt-nameoff.erofs", bad_nameoff) bad_name = bytearray(namei) slash_offset = wide_block + wide_last_nameoff + 5 assert bad_name[slash_offset] not in (0, ord("/")) bad_name[slash_offset] = ord("/") write_image("namei-corrupt-name.erofs", bad_name) nlink = bytearray((artifact_dir / "nlink.erofs").read_bytes()) nlink_root_nid = u16(nlink, 1038) _, _, _, nlink_entries = inline_dir_entries(nlink, nlink_root_nid) single_nid = next(nid for nid, _, _, name in nlink_entries if name == b"single.txt") single_inode, _, _ = compact_inode(nlink, single_nid) nlink1 = bytearray(nlink) put_u16(nlink1, single_inode, u16(nlink1, single_inode) | 0x10) put_u16(nlink1, single_inode + 6, 0x1234) write_image("nlink1-patched.erofs", nlink1) with (artifact_dir / "fixture-evidence.txt").open("w", encoding="ascii") as out: out.write(f"inline_nid={inline_nid} inode_off={inline_inode} " f"inode_blockoff={inline_inode % block_size} " f"xattr_icount=0->{inline_cross_xattr_icount} " f"inline_data_blockoff={inline_data_off % block_size} " f"inline_size={inline_size}\n") out.write(f"wide_nid={wide_nid} block={wide_block >> block_bits} " f"dirents={wide_count} last_nameoff={wide_last_nameoff} " f"padding_patch={padding_nul + 1 - wide_block}:" f"{padding_nul + 9 - wide_block}\n") out.write(f"nlink1_nid={single_nid} i_format_bit4=1 i_nb=0x1234\n") for image_name in ("special-compact.erofs", "special-extended.erofs"): image = bytearray((artifact_dir / image_name).read_bytes()) special_root = u16(image, 1038) _, _, _, entries = inline_dir_entries(image, special_root) out.write(image_name + "\n") for nid, _, _, name in entries: if name not in (b"char-large", b"block-large", b"fifo"): continue offset = inode_offset(image, nid) out.write(f" {name.decode()} nid={nid} inode_size=" f"{64 if u16(image, offset) & 1 else 32} " f"raw_u={u32(image, offset + 16):#010x}\n") with (artifact_dir / "SHA256SUMS").open("w", encoding="ascii") as sums: for path in sorted(artifact_dir.glob("*.erofs")): sums.write(f"{hashlib.sha256(path.read_bytes()).hexdigest()} " f"{path.name}\n") PY fsck.erofs -d1 "$artifact_dir/namei-padding-nonzero.erofs" dump.erofs --cat --path=/wide/entry-079-abcdefghijklmnopqrstuvwxyz.txt \ "$artifact_dir/namei-padding-nonzero.erofs" cat "$artifact_dir/fixture-evidence.txt" cat "$artifact_dir/SHA256SUMS"