DF-0859 — Hand-crafted HPFS image triggering the ab_busycnt OOB WRITE
====================================================================

**Finding**: `sys/vfs/hpfs/hpfs_alsubr.c:225,229,271,314` —
`hpfs_splitalsec`/`hpfs_concatalsec`/`hpfs_alblk2alsec` use the on-disk
`ab_busycnt` byte verbatim as a multiplier in `bcopy` length arithmetic
without validating it against the destination container size.  A crafted RW
HPFS image whose file fnode carries a forged `ab_busycnt=255` causes the
write path (`hpfs_write → hpfs_extend → hpfs_addextent`) to call
`hpfs_alblk2alsec`, whose `bcopy(abp, nabp, sizeof(alblk_t)+sz*busycnt)`
writes **3068 bytes** into a freshly allocated 512-byte buffer-cache buffer
— a **2568-byte kernel heap OOB write**.

This evidence pack contains:

| file              | what it is                                                     |
|-------------------|----------------------------------------------------------------|
| `harness.c`       | Deterministic userspace transcription of all three buggy bcopys with a poisoned allocator; proves byte-for-byte the 536 / 1032 / 1548 / 1540 / 2556 / 2568-byte OOB writes. |
| `craft_img.py`    | HPFS image crafter: minimal image with forged `ab_busycnt=255`. |
| `df859.img`       | Crafted 64 KB HPFS image (binary).                             |
| `mount_rw.c`      | Tiny mount(2) helper that mounts HPFS RW (DragonFly `mount_hpfs(8)` forces MNT_RDONLY unconditionally). |
| `build.sh`        | `cc -O2 -Wall -o harness harness.c`                            |
| `run.sh`          | `./harness`                                                    |
| `build.log`       | Final successful harness build.                                |
| `run.log`         | Decisive harness run (full output).                            |
| `run.1/2/3.log`   | Stress-test runs (deterministic; same OOB extents every time). |
| `dmesg.txt`       | Live guest dmesg showing the bug-fire chain `hpfs_write → hpfs_extend → hpfs_addextent → hpfs_alblk2alsec → (silent OOB write) → retry → hpfs_addextentr: INTERNAL INCONSISTENCE`. |
| `panic.txt`       | boot.log tail (no fatal trap — the OOB write is silent on this run, corrupting neighbouring buffer-cache buffers without tripping INVARIANTS; see VERDICT.md "Why no panic"). |
| `env.txt`         | Guest environment.                                             |
| `fix.diff`        | `git apply`-able fix: validate `ab_busycnt` against container max (`HPFS_*_MAX_*`) before the bcopys in splitalsec/concatalsec/alblk2alsec. |
| `fix_build.log`   | Phase 8 build of patched `hpfs.ko`.                            |
| `fix_run.log`     | Phase 8 re-run with patched module: every write now produces `hpfs_alblk2alsec: forged ab_busycnt 255 > fnode max` and the silent-corruption chain disappears. |
| `manifest.json`   | Artifact catalog.                                              |
| `VERDICT.md`      | Full narrative: mechanism, trigger, why-no-panic, fix validation. |

How to reproduce
----------------

```sh
# 1. deterministic OOB-proof harness (proves byte-exact overflow)
./build.sh && ./run.sh

# 2. live trigger on a DragonFly guest (root for mount, unpriv for write)
ssh dfbsd 'kldload hpfs; cc -O2 -I/usr/src/sys -o /root/mount_rw /path/to/mount_rw.c'
ssh dfbsd 'vnconfig -c vn1 /path/to/df859.img; /root/mount_rw /dev/vn1 /mnt'
ssh dfbsd-maxx 'dd if=/dev/zero of=/mnt/FILE bs=512 count=1 seek=1000 conv=notrunc'
# dmesg now shows: hpfs_addextentr: INTERNAL INCONSISTENCE  (proof the bug ran)
```

Expected vs fixed behaviour
---------------------------

| kernel / module            | live write trigger                                   | harness                           |
|----------------------------|------------------------------------------------------|-----------------------------------|
| unpatched hpfs.ko          | `hpfs_alblk2alsec` runs (silent 2568B OOB write); subsequent ops fail with EINVAL via INTERNAL INCONSISTENCE | `DF_0859_BUG_CONFIRMED=1` `MAX_OOB=2568` |
| patched hpfs.ko            | `hpfs_alblk2alsec: forged ab_busycnt 255 > fnode max` — rejected at the validation gate; no bcopy fires | `DF_0859_FIX_REJECTS_FORGED_BUSYCNT=1` |
