DragonFlyBSD Kernel Audit
DF-2605 / 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
@@ -180,16 +180,42 @@
 {
 	hammer2_chain_t *chain;
 	u_int bytes;
+	int radix;
 
 	/*
 	 * Special case - radix of 0 indicates a chain that does not
 	 * need a data reference (context is completely embedded in the
 	 * bref).
-	 */
-	if ((int)(bref->data_off & HAMMER2_OFF_MASK_RADIX))
-		bytes = 1U << (int)(bref->data_off & HAMMER2_OFF_MASK_RADIX);
-	else
+	 *
+	 * Validate the radix (DF-2605): the low 6 bits of data_off are
+	 * attacker-controlled on a mounted image.  A radix greater than
+	 * HAMMER2_RADIX_MAX (16) yields bytes > HAMMER2_PBUFSIZE which the
+	 * I/O layer cannot satisfy in a single DIO (KKASSERT panic in
+	 * hammer2_io_alloc() / _hammer2_io_getblk()); a radix >= 32 also
+	 * makes `1U << radix` undefined behavior (shift count >= type
+	 * width).  Cap bytes at the legal maximum so the shift is always
+	 * defined and the value is always I/O-safe, and mark the chain
+	 * errored so hammer2_chain_load_data() rejects it before any I/O.
+	 * The on-disk bref is left unmodified.
+	 */
+	radix = (int)(bref->data_off & HAMMER2_OFF_MASK_RADIX);
+	if (radix) {
+		if (radix > HAMMER2_RADIX_MAX) {
+			static int radix_warnonce;
+			if (radix_warnonce++ < 4) {
+				kprintf("hammer2_chain_alloc: %016jx: forged "
+					"radix=%d (max=%d); capping and "
+					"marking chain errored\n",
+					(intmax_t)bref->data_off, radix,
+					HAMMER2_RADIX_MAX);
+			}
+			bytes = 1U << HAMMER2_RADIX_MAX;
+		} else {
+			bytes = 1U << radix;
+		}
+	} else {
 		bytes = 0;
+	}
 
 	switch(bref->type) {
 	case HAMMER2_BREF_TYPE_INODE:
@@ -227,6 +253,13 @@
 	chain->flags = HAMMER2_CHAIN_ALLOCATED;
 
 	/*
+	 * A forged/oversized radix (above) is recorded on the chain so that
+	 * hammer2_chain_load_data() rejects it before issuing any I/O.
+	 */
+	if (radix > HAMMER2_RADIX_MAX)
+		chain->error = HAMMER2_ERROR_CHECK;
+
+	/*
 	 * Set the PFS boundary flag if this chain represents a PFS root.
 	 */
 	if (bref->flags & HAMMER2_BREF_FLAG_PFSROOT)
@@ -938,6 +971,18 @@
 	if ((chain->bref.data_off & ~HAMMER2_OFF_MASK_RADIX) == 0)
 		return;
 
+	/*
+	 * Reject a chain whose on-disk radix was forged/corrupted beyond the
+	 * legal maximum (DF-2605 / DF-2583).  hammer2_chain_alloc() caps the
+	 * (potentially UB) bytes derivation and marks such chains errored;
+	 * bailing here before hammer2_io_bread() avoids the oversized I/O
+	 * that would otherwise KKASSERT in hammer2_io_alloc() on INVARIANTS
+	 * kernels, and the OOB blockref-array walk in hammer2_flush_core()
+	 * on non-INVARIANTS kernels.
+	 */
+	if (chain->error || chain->bytes > HAMMER2_PBUFSIZE)
+		return;
+
 	hmp = chain->hmp;
 	KKASSERT(hmp != NULL);