DragonFlyBSD Kernel Audit
DF-2583 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/hammer2/hammer2_chain.c b/sys/vfs/hammer2/hammer2_chain.c
--- a/sys/vfs/hammer2/hammer2_chain.c
+++ b/sys/vfs/hammer2/hammer2_chain.c
@@ -938,6 +938,37 @@
 	if ((chain->bref.data_off & ~HAMMER2_OFF_MASK_RADIX) == 0)
 		return;
 
+	/*
+	 * Defense against a malicious/corrupted filesystem image (DF-2583):
+	 * reject a chain whose on-disk radix (chain->bytes, derived from
+	 * the low 6 bits of bref.data_off) implies a buffer larger than the
+	 * hammer2 I/O layer can satisfy in a single DIO.  HAMMER2_PBUFSIZE
+	 * is the largest legal radix (HAMMER2_RADIX_MAX).  An inflated radix
+	 * otherwise drives two failure modes:
+	 *   (1) on INVARIANTS kernels, the next hammer2_io_bread() trips the
+	 *       KKASSERT in hammer2_io_alloc() / _hammer2_io_getblk() because
+	 *       the request spans multiple PBUFSIZE pages;
+	 *   (2) on non-INVARIANTS kernels, the KKASSERT is skipped and the
+	 *       oversized chain->bytes later drives an out-of-bounds
+	 *       read/write of the blockref array in hammer2_flush_core
+	 *       (count = parent->bytes / sizeof(hammer2_blockref_t)).
+	 * Marking the chain errored lets callers (lookup, readdir, flush)
+	 * handle it gracefully instead of panicking.  The on-disk bref is
+	 * left unmodified.
+	 */
+	if (chain->bytes > HAMMER2_PBUFSIZE) {
+		static int radix_warnonce;
+		if (radix_warnonce++ < 4) {
+			kprintf("hammer2_chain_load_data: %016jx: forged "
+				"radix (bytes=%u > HAMMER2_PBUFSIZE=%d); "
+				"rejecting chain\n",
+				(intmax_t)chain->bref.data_off,
+				chain->bytes, HAMMER2_PBUFSIZE);
+		}
+		chain->error = HAMMER2_ERROR_CHECK;
+		return;
+	}
+
 	hmp = chain->hmp;
 	KKASSERT(hmp != NULL);