⬢ DragonFlyBSD Kernel Audit
DF-5146 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/dev/drm/include/linux/hash.h b/sys/dev/drm/include/linux/hash.h
index 1a2b3c4..5d6e7f8 100644
--- a/sys/dev/drm/include/linux/hash.h
+++ b/sys/dev/drm/include/linux/hash.h
@@ -29,17 +29,31 @@
 #ifndef _LINUX_HASH_H_
 #define _LINUX_HASH_H_
 
-#include <sys/hash.h>
+/*
+ * DF-5146: hash32_buf()'s third argument is an initval (seed), NOT a
+ * bit count, and its result is a full 32-bit hash.  Casting that result
+ * to u64 and right-shifting by (64 - bits) is identically 0 for every
+ * bits <= 32, so on 64-bit platforms hash_long(key, order) == 0 for all
+ * keys and every drm_ht table (drm_hashtab.c) collapses to bucket 0.
+ *
+ * Use the canonical 64-bit Fibonacci-hashing mixer (as in Linux) so the
+ * returned value really is `bits` bits wide.
+ */
+#include <linux/types.h>
 
 static inline u64 hash_64(u64 val, unsigned int bits)
 {
-	u64 ret;
+	u64 hash = val;
+	u64 n = hash;
 
-	ret = (uint64_t)hash32_buf(&val, sizeof(val), bits);
+	if (bits == 0)
+		return 0;
+	if (bits > 64)
+		bits = 64;
 
-	return ret >> (64 - bits);
+	n <<= 18; hash -= n;
+	n <<= 33; hash -= n;
+	n <<= 3; hash += n;
+	n <<= 3; hash -= n;
+	n <<= 4; hash += n;
+	n <<= 2; hash += n;
+
+	/* High bits are more random, so use them. */
+	return hash >> (64 - bits);
 }
 
 static inline u32 hash_32(u32 val, unsigned int bits)