DragonFlyBSD Kernel Audit
DF-0763 / 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,15 +180,30 @@
 {
 	hammer2_chain_t *chain;
 	u_int bytes;
+	int badbref = 0;
 
 	/*
 	 * 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
+	 *
+	 * DF-0763: The on-disk radix is extracted from the low 6 bits of
+	 * data_off and is attacker-controlled via a malicious filesystem
+	 * image.  A radix greater than HAMMER2_RADIX_MAX (16, i.e. 64KB
+	 * == HAMMER2_PBUFSIZE) would compute a bytes value larger than
+	 * the DIO page buffer, driving OOB heap reads/writes in
+	 * chain_load_data()/chain_modify() and an assertion failure in
+	 * hammer2_io_alloc().  Validate it here and mark the chain so
+	 * chain_load_data() rejects it cleanly instead of OOB-ing.
+	 */
+	if ((int)(bref->data_off & HAMMER2_OFF_MASK_RADIX)) {
+		int radix = (int)(bref->data_off & HAMMER2_OFF_MASK_RADIX);
+
+		bytes = 1U << radix;
+		if (radix > HAMMER2_RADIX_MAX) {
+			badbref = 1;
+		}
+	} else
 		bytes = 0;
 
 	switch(bref->type) {
@@ -226,6 +241,10 @@
 	chain->refs = 1;
 	chain->flags = HAMMER2_CHAIN_ALLOCATED;
 
+	/* DF-0763: flag out-of-range radix so chain_load_data() rejects it */
+	if (badbref)
+		chain->error = HAMMER2_ERROR_BADBREF;
+
 	/*
 	 * Set the PFS boundary flag if this chain represents a PFS root.
 	 */
@@ -938,6 +957,15 @@
 	if ((chain->bref.data_off & ~HAMMER2_OFF_MASK_RADIX) == 0)
 		return;
 
+	/*
+	 * DF-0763: Reject chains whose on-disk radix exceeded
+	 * HAMMER2_RADIX_MAX.  chain_alloc() flags these as BADBREF;
+	 * loading their data would drive OOB heap accesses in the IO
+	 * layer (lsize > HAMMER2_PBUFSIZE).
+	 */
+	if (chain->error & HAMMER2_ERROR_BADBREF)
+		return;
+
 	hmp = chain->hmp;
 	KKASSERT(hmp != NULL);