[GH-ISSUE #76] hfs: hfs_reclaim follows a stale h_fork when a complex node is reclaimed and panics at unmount #69

Closed
opened 2026-11-03 22:03:41 -07:00 by gitea-mirror · 1 comment
Owner

Originally created by @raynorpat on GitHub (Oct 20, 2026).
Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/76

Symptom

ffsbench stress --fs hfs with all categories (seed 75) panics on the combined ffs-perf kernel (f68d4802c) at the unmount step, 3 of 3 runs:

unexpected kernel trap e eip 18894e

The faulting address is b000d4c1. Backtrace: unmount → dounmount → hfs_unmount → vflush → vgonel → vclean → hfs_reclaim.

Where

bsd/hfs/hfs_vnodeops.c ~3423, the VCPLX branch: VTOH(VTOH(tp)->h_sibling)->h_relative = NULL. Here tp = hp->h_meta->h_fork is a fork vnode that has already been reclaimed, and its h_sibling holds garbage (0xf000d445). The fork reclaim path only updates h_meta->h_fork while h_usecount > 0, so after the last fork goes the complex node probably still points at it.

On the base kernel (4fd6d07b6) the same run doesn't panic but reports "umount failed": before #73, kernel_recover_thread hid the kernel fault. The HFS sources didn't change in this range.

Logs: vm/work/ffsbench/runs/combined*, vm/work/combined-retest/ (pan.py gdbstub dump).

Originally created by @raynorpat on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/76 ## Symptom `ffsbench stress --fs hfs` with all categories (seed 75) panics on the combined ffs-perf kernel (f68d4802c) at the unmount step, 3 of 3 runs: ``` unexpected kernel trap e eip 18894e ``` The faulting address is b000d4c1. Backtrace: unmount → dounmount → hfs_unmount → vflush → vgonel → vclean → hfs_reclaim. ## Where `bsd/hfs/hfs_vnodeops.c` ~3423, the VCPLX branch: `VTOH(VTOH(tp)->h_sibling)->h_relative = NULL`. Here `tp = hp->h_meta->h_fork` is a fork vnode that has already been reclaimed, and its `h_sibling` holds garbage (0xf000d445). The fork reclaim path only updates `h_meta->h_fork` while `h_usecount > 0`, so after the last fork goes the complex node probably still points at it. On the base kernel (4fd6d07b6) the same run doesn't panic but reports "umount failed": before #73, `kernel_recover_thread` hid the kernel fault. The HFS sources didn't change in this range. Logs: `vm/work/ffsbench/runs/combined*`, `vm/work/combined-retest/` (`pan.py` gdbstub dump).
Author
Owner

@raynorpat commented on GitHub (Oct 21, 2026):

Fixed by fa8183e56 and 23a37779f, merged into ffs-perf in 966dc5243.

Root cause (hfs_vnodeops.c hfs_reclaim, ~3349 before the fix):

  • Only VREG/VLNK vnodes went through the branch that releases a fork. hfs_create rejects other types only under #if DIAGNOSTIC, so bind() of an AF_UNIX socket on HFS (net unix) creates a data fork with v_type VSOCK (hfs_makenode).
  • When that fork was reclaimed, its complex node kept a stale h_fork/h_relative, a meta use count that never dropped, and a reference that was never released.
  • That leaked reference made the plain umount fail with EBUSY. The harness's umount -f then reclaimed the complex node, which followed the dead fork (v_data 0; the garbage h_sibling is BIOS vector 15 from address 0x3c) at ~3423.
  • A diagnostic kernel also showed a stale h_fork vnode reused by another file before the crash, so this was corrupting other nodes during normal use too.

Fix:

  • fa8183e56: forks are now chosen by fork type (v_type != VNON && H_FORKTYPE != kDirCmplx), not vnode type.
  • 23a37779f: the last fork reclaimed clears h_fork. On a forced unmount that reclaims the complex node first, the meta is left to the last fork, which frees it.

Verified on the fixed kernel (cksum 3049947097):

  • Small reproducer --fs hfs --cats net: panicked on the combined kernel; passes now, 14 PASS, fsck clean.
  • All categories, seed 75, run twice: 99 PASS, 0 FAIL, 8 UNSUP each, no panic, unmount succeeds, host fsck clean.

Left open: a socket file whose vnode was recycled and is looked up again gets VBAD for both its fork and its complex node. The complex node then leaks its meta, a small leak with no crash. Rejecting socket nodes in hfs_create outright would stop it but make bind() on HFS fail.

<!-- gh-comment-id:6307432555 --> @raynorpat commented on GitHub (Oct 21, 2026): Fixed by fa8183e56 and 23a37779f, merged into ffs-perf in 966dc5243. **Root cause** (`hfs_vnodeops.c` hfs_reclaim, ~3349 before the fix): - Only `VREG`/`VLNK` vnodes went through the branch that releases a fork. `hfs_create` rejects other types only under `#if DIAGNOSTIC`, so `bind()` of an AF_UNIX socket on HFS (`net unix`) creates a data fork with v_type `VSOCK` (`hfs_makenode`). - When that fork was reclaimed, its complex node kept a stale `h_fork`/`h_relative`, a meta use count that never dropped, and a reference that was never released. - That leaked reference made the plain umount fail with EBUSY. The harness's `umount -f` then reclaimed the complex node, which followed the dead fork (v_data 0; the garbage `h_sibling` is BIOS vector 15 from address 0x3c) at ~3423. - A diagnostic kernel also showed a stale `h_fork` vnode reused by another file before the crash, so this was corrupting other nodes during normal use too. **Fix:** - **fa8183e56:** forks are now chosen by fork type (`v_type != VNON && H_FORKTYPE != kDirCmplx`), not vnode type. - **23a37779f:** the last fork reclaimed clears `h_fork`. On a forced unmount that reclaims the complex node first, the meta is left to the last fork, which frees it. **Verified** on the fixed kernel (cksum 3049947097): - **Small reproducer** `--fs hfs --cats net`: panicked on the combined kernel; passes now, 14 PASS, fsck clean. - **All categories, seed 75**, run twice: 99 PASS, 0 FAIL, 8 UNSUP each, no panic, unmount succeeds, host fsck clean. **Left open:** a socket file whose vnode was recycled and is looked up again gets VBAD for both its fork and its complex node. The complex node then leaks its meta, a small leak with no crash. Rejecting socket nodes in `hfs_create` outright would stop it but make `bind()` on HFS fail.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
github-starred/RhapsodiOS#69
No description provided.