fix.diff for DF-2627 -- hammer2_vop_readdir uninitialized-stack disclosure Root cause: `hammer2_blockref_t bref` (hammer2_vnops.c:601) is only assigned inside the collect loop after a successful collect (:700). When the FIRST collect returns an error (:688-692 break), :750 reads `bref.key` from uninitialized stack into `saveoff`, and :758 stores it into `uio->uio_offset`; kern_getdirentries (sys/kern/vfs_syscalls.c:4645-4646) copies that into fp->f_offset even on the error return, so lseek(fd, 0, SEEK_CUR) hands 63 bits of stale kernel stack to userspace. Fix (two parts): 1. Never advance the directory cookie from `bref` unless the last collect succeeded (error == 0); on error keep the last good offset so the cookie never contains uninitialized data and never regresses past entries that were actually returned. 2. Zero bref at the top as defense-in-depth. Verified in-guest (INVARIANTS kernel): with the fix, getdents on the CRC-broken directory still fails EDOM but the cookie reads 2 (the offset after the artificial "." and "..") instead of kernel-stack pointers, and directory listing on a healthy image is unchanged. Apply inside the guest /usr/src with: patch -p1 < fix.diff (paths below are relative to the repository root). --- a/sys/vfs/hammer2/hammer2_vnops.c 2026-08-28 21:00:30.073215392 +0000 +++ b/sys/vfs/hammer2/hammer2_vnops.c 2026-08-28 21:00:36.133137927 +0000 @@ -631,6 +631,7 @@ } cookie_index = 0; + bzero(&bref, sizeof(bref)); hammer2_inode_lock(ip, HAMMER2_RESOLVE_SHARED); /* @@ -746,9 +747,15 @@ error = 0; eofflag = 1; saveoff = (hammer2_key_t)-1; - } else { + } else if (error == 0) { saveoff = bref.key & HAMMER2_DIRHASH_USERMSK; } + /* + * else: collect error. Leave saveoff at the last good offset; + * reading bref here would use uninitialized stack data and leak + * it into the directory cookie (uio_offset -> f_offset), which + * userspace can read back with lseek() even on the error return. + */ done: hammer2_inode_unlock(ip); if (ap->a_eofflag)