DragonFlyBSD Kernel Audit
DF-0139 / sleepq_hash_oob.c
← back to finding ↓ download raw
/*
 * DF-0139 - SLEEPQ_HASH misplaced-mask OOB index demonstrator.
 *
 * sys/kern/subr_sleepqueue.c:80-85:
 *
 *   #define SLEEPQ_HSIZE    1024
 *   #define SLEEPQ_HMASK    (SLEEPQ_HSIZE - 1)          // 0x3FF (10 bits)
 *   #define SLEEPQ_HASH(wchan)  ((((uintptr_t)(wchan) >> 10) ^ \
 *                                 ((uintptr_t)(wchan) & SLEEPQ_HMASK)))
 *   #define SLEEPQ_LOOKUP(wchan) &sleepq_chains[SLEEPQ_HASH(wchan)]
 *
 * The `& SLEEPQ_HMASK` masks ONLY the second XOR operand, not the whole
 * result.  For any kernel pointer whose high bits survive `>> 10`, the hash
 * value far exceeds SLEEPQ_HSIZE (1024), so sleepq_chains[huge] is a wild
 * out-of-bounds pointer into the kernel bss/data segment.
 *
 * This harness replicates the macro exactly (uintptr_t == unsigned long on the
 * 64-bit kernel) and demonstrates that EVERY realistic kernel wchan produces an
 * index far outside [0, 1024).  sleepq_lock()/sleepq_wclookup() (lines 120,
 * 182) dereference this immediately on first call -> immediate panic or silent
 * memory corruption.
 *
 * The code is compiled into the shipped GENERIC kernel
 *   (nm /boot/kernel/kernel | grep sleepq_lock)
 * but has ZERO in-tree callers today -- it exists solely for future FreeBSD/
 * Linux-KPI compatibility.  The defect fires the instant any caller is wired
 * up.  This is a latent critical-grade defect, confirmed at the code level and
 * by this standalone arithmetic reproduction (no live caller exists to trigger
 * a runtime panic on a default guest).
 */
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

#define SLEEPQ_HSIZE    1024
#define SLEEPQ_HMASK    (SLEEPQ_HSIZE - 1)

/* Exact copy of the kernel macro (sys/kern/subr_sleepqueue.c:82-83). */
#define SLEEPQ_HASH(wchan)  ((((uintptr_t)(wchan) >> 10) ^ \
                              ((uintptr_t)(wchan) & SLEEPQ_HMASK)))

/* What the macro almost certainly was meant to be. */
#define SLEEPQ_HASH_FIXED(wchan) ((((uintptr_t)(wchan) >> 10) ^ \
                                   ((uintptr_t)(wchan) & SLEEPQ_HMASK)) & SLEEPQ_HMASK)

struct sleepq_chain { char pad[32]; };
static struct sleepq_chain sleepq_chains[SLEEPQ_HSIZE];

int
main(void)
{
    /* Realistic DragonFly kernel wait-channel pointers (kmem direct-map range
     * 0xffff_8000_0000_0000 +, and common kmalloc/kobj addresses). */
    uintptr_t samples[] = {
        0xffff800000000000UL,          /* typical kmem object base */
        0xffffffff80b34000UL,          /* kernel-text/data symbol */
        0xfffffe0012345678UL,          /* malloc'd struct */
        0xffff800123456000UL,          /* page-aligned kmem */
        0xffff8000deadbeefUL,
        (uintptr_t)&sleepq_chains[0],  /* the array itself (a real kernel global) */
    };
    int i, oob = 0;

    printf("sleepq_chains[] has %d entries (indices 0..%d allowed)\n",
           SLEEPQ_HSIZE, SLEEPQ_HSIZE - 1);
    printf("%-22s %-20s %-14s %s\n",
           "wchan", "SLEEPQ_HASH", "IN-BOUNDS?", "fixed");
    for (i = 0; i < (int)(sizeof(samples)/sizeof(samples[0])); i++) {
        uintptr_t h = SLEEPQ_HASH(samples[i]);
        uintptr_t hf = SLEEPQ_HASH_FIXED(samples[i]);
        int inb = (h < SLEEPQ_HSIZE);
        if (!inb) oob++;
        printf("0x%016lx       0x%016lx     %-14s 0x%lx\n",
               (unsigned long)samples[i],
               (unsigned long)h,
               inb ? "YES" : "*** OOB ***",
               (unsigned long)hf);
    }
    printf("\n%d/%d realistic kernel wchan pointers produce an OUT-OF-BOUNDS "
           "index into sleepq_chains[1024].\n", oob,
           (int)(sizeof(samples)/sizeof(samples[0])));

    /* Prove the dereference is wild: emulate SLEEPQ_LOOKUP pointer offset. */
    {
        uintptr_t base = (uintptr_t)&sleepq_chains[0];
        size_t eltsz = sizeof(struct sleepq_chain);
        uintptr_t wchan = 0xffff8000deadbeefUL;
        uintptr_t h = SLEEPQ_HASH(wchan);
        uintptr_t wild = base + h * eltsz;
        printf("\nEmulating SLEEPQ_LOOKUP(0x%lx) on array @ %p:\n",
               (unsigned long)wchan, (void *)base);
        printf("  index = 0x%lx  (array has %d slots)\n",
               (unsigned long)h, SLEEPQ_HSIZE);
        printf("  &sleepq_chains[index] = %p  -> %llu BYTES past array start\n",
               (void *)wild,
               (unsigned long long)(wild - base));
        printf("  spin_lock()/TAILQ on this address corrupts unrelated kernel "
               "memory.\n");
    }

    if (oob > 0) {
        printf("\nRESULT: BUG CONFIRMED -- SLEEPQ_HASH returns out-of-range "
               "indices for normal kernel pointers.\n");
        return 0;
    }
    printf("\nRESULT: no OOB (unexpected).\n");
    return 1;
}