DragonFlyBSD Kernel Audit
DF-2627 / fix.diff
← back to finding ↓ download raw
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)