⬢ DragonFlyBSD Kernel Audit
DF-5146 / hash64_collapse.c
← back to finding ↓ download raw
/*
 * DF-5146 -- userspace arithmetic proof.
 *
 * Claim: on 64-bit DragonFly, the drm linux-compat hash_long() is
 * identically ZERO for every bit-width <= 32, so every key hashed by
 * sys/dev/drm/drm_hashtab.c lands in bucket 0 of drm hash tables
 * (order 12 => 4096 buckets; 4095 stay empty forever).
 *
 * This program replicates, verbatim, the arithmetic of:
 *   sys/sys/hash.h:33-53                 (djb2 hash32_buf; 3rd arg = initval)
 *   sys/dev/drm/include/linux/hash.h:32-54  (hash_64 / hash_32 / hash_long)
 *
 * Build:  cc -O2 -o hash64_collapse hash64_collapse.c
 * Run:    ./hash64_collapse
 * Success criterion: "RESULT: hash_long(key,bits)==0 for ALL ... zero outputs"
 * and, for contrast, hash_32 (the i386 path) spreading over the bucket space.
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* ---- sys/sys/hash.h (verbatim) ---- */
#define HASHINIT 5381
#define HASHSTEP(x,c) (((x << 5) + x) + c)

static uint32_t hash32_buf(const void *buf, size_t len, uint32_t hash)
{
	const unsigned char *p = buf;
	while (len--)
		hash = HASHSTEP(hash, *p++);
	return hash;
}

/* ---- sys/dev/drm/include/linux/hash.h (verbatim) ---- */
static uint64_t hash_64(uint64_t val, unsigned int bits)
{
	uint64_t ret;
	ret = (uint64_t)hash32_buf(&val, sizeof(val), bits);
	return ret >> (64 - bits);
}

static uint32_t hash_32(uint32_t val, unsigned int bits)
{
	uint32_t ret;
	ret = hash32_buf(&val, sizeof(val), bits);
	return ret >> (32 - bits);
}

int main(void)
{
	unsigned bits;
	unsigned long long key;
	unsigned long long tested = 0, zero64 = 0;
	unsigned maxb32 = 0;

	/* 1) hash_long == hash_64 on x86_64: must be 0 for all bits<=32 */
	for (bits = 1; bits <= 32; bits++) {
		for (key = 0; key < 200000; key++) {
			uint64_t k = key * 0x9E3779B97F4A7C15ULL; /* spread keys */
			uint64_t h = hash_64(k, bits);
			tested++;
			if (h == 0)
				zero64++;
			if (h >> bits) { /* wider than bits => would index OOB */
				printf("BUG-IN-PROOF: h wider than bits\n");
				return 1;
			}
		}
	}

	/* 2) the actual drm table order used in-tree: 12 (drm_drv.c:603,
	 *    drm_gem.c:148) -- show a few concrete values */
	printf("hash_long(key, 12) samples (drm map_hash / gem offset_hash):\n");
	for (key = 0; key < 8; key++) {
		uint64_t k = key * 0x9E3779B97F4A7C15ULL;
		printf("  key=0x%016llx -> bucket=%llu\n",
		    (unsigned long long)k,
		    (unsigned long long)hash_64(k, 12));
	}

	/* 3) i386 path for contrast: hash_32 uses top bits properly */
	for (key = 0; key < 200000; key++) {
		uint32_t k = (uint32_t)(key * 2654435761UL);
		unsigned h = hash_32(k, 12);
		if (h > maxb32)
			maxb32 = h;
	}
	printf("i386 hash_32(key,12) max bucket = %u (table has 4096 buckets)\n",
	    maxb32);

	printf("tested=%llu zero64=%llu\n", tested, zero64);
	if (zero64 == tested && maxb32 <= 4095) {
		printf("RESULT: hash_long(key,bits)==0 for ALL %llu inputs "
		    "with bits in [1,32] -- 64-bit drm hash tables collapse "
		    "to bucket 0\n", tested);
		return 0;
	}
	printf("RESULT: NOT reproduced (unexpected)\n");
	return 1;
}