Skip to content

# every ADF file over 36 KB read back wrong, and the parent pointer sat in the extension field #84

Description

@Hawkynt

Found by wiring the affs kernel module — present here all along — into the lifecycle check, which put ADF in front of an outside reader for the first time.

Silent corruption past 36 864 bytes

An Amiga FFS file header names at most 72 data blocks; everything past that is named by a chain of file-extension blocks. The writer never emitted one:

for (var i = 0; i < dataBlockCount && i < HashTableCount; i++)
  WriteUInt32BE(disk, hdrOff + 308 - i * 4, (uint)dataBlocks[i]);

The blocks past the 72nd were still allocated and still filled with the file's bytes — nothing pointed at them. So a file of any size came back at exactly the right length, without an error anywhere, correct to byte 36 863 and wrong after it:

size  1024: all files identical
size  4096: all files identical
size 20000: all files identical
size 49152: WRONG, first difference at 36864

The writer also set high_seq to the total block count rather than the number of pointers in that block.

Two fields in the wrong place

The last four words of a block are hash_chain (496), parent (500), extension (504), sec_type (508). We had:

ours correct
writer, parent 504 500
reader, extension 496 504
extent map, extension 496 504

So every file header this project wrote put its parent directory into the extension pointer — claiming a file-extension block that was really a directory. And the reader took the hash chain for the extension chain, so a file sharing a hash bucket with another would have followed its neighbour as a list of data blocks. Neither showed, because the two mistakes cancelled: the reader never looked at 504, and 496 was zero for the last entry in every bucket.

The reader's own comment recorded the assumption: ExtBlockOffset = 496; // same field used for extension blocks.

Defragmentation

AdfBlockMover patched only the 72 pointers in the header, so once extension blocks exist, every block of a file past the first 36 KB kept naming where it had been. It now follows the chain, repointing the chain links and the data pointers inside each block.

Verification

The kernel's own Amiga driver, on files that cross the 72-block boundary:

$ sudo mount -t affs -o loop,ro,setuid=…,setgid=…,mode=644 v.adf m
$ md5sum m/*
e7f1a12e5108ef4706c011e19325a87d  m/KEEP00.BIN   <- matches the source
e1d9c510a5f3869bb66f6a48380686fb  m/KEEP02.BIN   <- matches
dafd9ae0f99ee6bd5b2ffbecc842043e  m/KEEP04.BIN   <- matches
f61a32382bd235ea85bd6ec50ed22c54  m/KEEP06.BIN   <- matches

affs does not know uid=/gid= — it hands everything to root as 0700 — so the check now passes setuid=/setgid=/mode= instead, which it does know.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions