DragonFlyBSD Kernel Audit
DF-0838 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/hammer/hammer.h b/sys/vfs/hammer/hammer.h
--- a/sys/vfs/hammer/hammer.h
+++ b/sys/vfs/hammer/hammer.h
@@ -437,6 +437,7 @@
 #define HAMMER_INODE_REDO	0x02000000 /* REDO logging active */
 #define HAMMER_INODE_RDIRTY	0x04000000 /* REDO records active in fifo */
 #define HAMMER_INODE_SLAVEFLUSH	0x08000000 /* being flushed by slave */
+#define HAMMER_INODE_BADDIRHASH	0x10000000 /* unknown dirhash alg, fall back to ALG0 */
 
 #define HAMMER_INODE_MODMASK	(HAMMER_INODE_DDIRTY|HAMMER_INODE_SDIRTY|   \
 				 HAMMER_INODE_XDIRTY|HAMMER_INODE_BUFS|	    \
diff --git a/sys/vfs/hammer/hammer_subs.c b/sys/vfs/hammer/hammer_subs.c
--- a/sys/vfs/hammer/hammer_subs.c
+++ b/sys/vfs/hammer/hammer_subs.c
@@ -1026,9 +1026,25 @@
 	case HAMMER_INODE_CAP_DIRHASH_ALG2:
 	case HAMMER_INODE_CAP_DIRHASH_ALG3:
 	default:
-		key = 0;			/* compiler warning */
-		*max_iterationsp = 1;		/* sanity */
-		hpanic("bad algorithm %p", dip);
+		/*
+		 * DF-0838: cap_flags is read verbatim from disk and an
+		 * attacker-crafted (or simply corrupt) image can select
+		 * an unimplemented dirhash algorithm.  Panicking gives a
+		 * trivial mount-time / directory-listing DoS, so fall
+		 * back to ALG0 instead and log once per directory inode.
+		 */
+		if ((dip->flags & HAMMER_INODE_BADDIRHASH) == 0) {
+			dip->flags |= HAMMER_INODE_BADDIRHASH;
+			hdkprintf("dir ino %016jx has unknown dirhash alg %d, "
+				  "falling back to ALG0\n",
+				  (intmax_t)dip->obj_id,
+				  dip->ino_data.cap_flags &
+					HAMMER_INODE_CAP_DIRHASH_MASK);
+		}
+		key = (int64_t)(crc32(aname, len) & 0x7FFFFFFF) << 32;
+		if (key == 0)
+			key |= 0x100000000LL;
+		*max_iterationsp = 0xFFFFFFFFU;
 		break;
 	}
 	return(key);