/*
 * DF-0546 harness: netmap_mem_ofstophys OOB read of lut[]
 *
 * Source: sys/net/netmap/netmap_mem2.c:165-195 (DEAD CODE — netmap is NOT
 * compiled on DragonFlyBSD master; not listed in sys/conf/files)
 *
 * The bug:
 *   netmap_mem_ofstophys(:165-195):
 *     for (i = 0; i < NETMAP_POOLS_NR; offset -= p[i].memtotal, i++) {
 *         if (offset >= p[i].memtotal)
 *             continue;
 *         pa = p[i].lut[offset / p[i]._objsize].paddr +  // OOB index!
 *             offset % p[i]._objsize;
 *
 *   memtotal = numclusters * _clustsize (:622), where _clustsize is rounded
 *   UP to PAGE_SIZE (:531-533). When clustentries * _objsize is not a page
 *   multiple, memtotal > objtotal * _objsize.
 *
 *   An offset in [objtotal*_objsize, memtotal) passes the `offset < memtotal`
 *   check but produces lut index = offset/_objsize >= objtotal -> OOB read
 *   past the lut[] array (allocated with objtotal entries at :565).
 *
 *   Result: garbage paddr interpreted as physical address -> vm_page_getfake
 *   maps an arbitrary physical page RW into the faulting process.
 *
 * Since netmap is dead code on master, this harness demonstrates the OOB
 * indexing logic in userspace with controlled pool parameters.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define PAGE_SIZE 4096

struct lut_entry {
    uint64_t paddr;
};

struct netmap_obj_pool {
    uint32_t _objsize;      /* object size */
    uint32_t _clustsize;    /* cluster size, rounded to PAGE_SIZE */
    uint32_t numclusters;   /* number of clusters */
    uint32_t clustentries;  /* objects per cluster */
    uint32_t objtotal;      /* total objects = numclusters * clustentries */
    uint64_t memtotal;      /* = numclusters * _clustsize */
    struct lut_entry *lut;  /* allocated with objtotal entries */
};

/* Replicates netmap_mem2.c:531-533 _clustsize rounding */
static uint32_t round_to_page(uint32_t sz) {
    if (sz < PAGE_SIZE)
        return PAGE_SIZE;
    return (sz + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
}

/* Setup a pool where memtotal > objtotal * _objsize */
static void init_pool(struct netmap_obj_pool *p, uint32_t objsize) {
    p->_objsize = objsize;
    p->clustentries = PAGE_SIZE / objsize; /* objects per page */
    if (p->clustentries == 0) p->clustentries = 1;

    /* _clustsize = round_up(clustentries * objsize, PAGE_SIZE) */
    uint32_t raw_clustsize = p->clustentries * objsize;
    p->_clustsize = round_to_page(raw_clustsize);

    p->numclusters = 16; /* arbitrary */
    p->objtotal = p->numclusters * p->clustentries;
    p->memtotal = (uint64_t)p->numclusters * p->_clustsize;

    p->lut = calloc(p->objtotal, sizeof(struct lut_entry));
    /* Fill lut with recognizable addresses */
    for (uint32_t i = 0; i < p->objtotal; i++)
        p->lut[i].paddr = 0x10000000ULL + i * objsize;
}

/* Replicates netmap_mem_ofstophys (netmap_mem2.c:165-195) */
static uint64_t mem_ofstophys(struct netmap_obj_pool *p, uint64_t offset) {
    if (offset >= p->memtotal)
        return 0; /* error path */

    uint32_t idx = offset / p->_objsize;

    if (idx >= p->objtotal) {
        /* OOB READ — reads past lut[] array */
        printf("  -> lut index %u >= objtotal %u -> OOB READ!\n", idx, p->objtotal);
        printf("  -> reads %ld bytes past lut array end\n",
               (long)((idx - p->objtotal) * sizeof(struct lut_entry)));
        /* In kernel: reads whatever is adjacent in the slab */
        return 0xDEADBEEF; /* garbage */
    }

    return p->lut[idx].paddr + (offset % p->_objsize);
}

int main(void) {
    printf("=== DF-0546: netmap_mem_ofstophys lut[] OOB read ===\n\n");
    printf("NOTE: netmap is NOT compiled on DragonFlyBSD master.\n");
    printf("      Bug confirmed by source trace; harness demonstrates the logic.\n\n");

    /* Use objsize=2048: clustentries = 4096/2048 = 2, raw=4096, _clustsize=4096 */
    /* objtotal * objsize = 16*2 * 2048 = 65536; memtotal = 16*4096 = 65536 */
    /* This packs cleanly — no OOB. Need a non-page-multiple case. */

    /* Use objsize=1500: clustentries = 4096/1500 = 2, raw=3000 */
    /* _clustsize = round_up(3000) = 4096 */
    /* objtotal * objsize = 32 * 1500 = 48000; memtotal = 16*4096 = 65536 */
    /* memtotal (65536) > objtotal*objsize (48000) — OOB window exists! */
    struct netmap_obj_pool pool;
    init_pool(&pool, 1500);

    printf("Pool parameters (objsize=1500):\n");
    printf("  _objsize     = %u\n", pool._objsize);
    printf("  _clustsize   = %u (rounded up from %u)\n",
           pool._clustsize, pool.clustentries * pool._objsize);
    printf("  clustentries = %u\n", pool.clustentries);
    printf("  numclusters  = %u\n", pool.numclusters);
    printf("  objtotal     = %u (lut has this many entries)\n", pool.objtotal);
    printf("  memtotal     = %lu (offset validated against this)\n",
           (unsigned long)pool.memtotal);
    printf("  objtotal*objsize = %lu\n",
           (unsigned long)pool.objtotal * pool._objsize);
    printf("  OOB window   = [%lu, %lu) = %lu bytes\n\n",
           (unsigned long)pool.objtotal * pool._objsize,
           (unsigned long)pool.memtotal,
           (unsigned long)pool.memtotal - (unsigned long)pool.objtotal * pool._objsize);

    /* Test: offset just before OOB window (valid) */
    uint64_t off_valid = (uint64_t)(pool.objtotal - 1) * pool._objsize;
    printf("[1] Valid offset %lu:\n", (unsigned long)off_valid);
    uint64_t pa = mem_ofstophys(&pool, off_valid);
    printf("  -> pa = 0x%lx (valid)\n\n", (unsigned long)pa);

    /* Test: offset in OOB window */
    uint64_t off_oob = (uint64_t)pool.objtotal * pool._objsize + 100;
    printf("[2] OOB offset %lu (in padding window):\n", (unsigned long)off_oob);
    printf("  offset < memtotal (%lu < %lu)? %s\n",
           (unsigned long)off_oob, (unsigned long)pool.memtotal,
           off_oob < pool.memtotal ? "YES — passes check" : "NO");
    pa = mem_ofstophys(&pool, off_oob);
    printf("  -> pa = 0x%lx (GARBAGE from adjacent slab heap)\n\n",
           (unsigned long)pa);

    printf("=== BUG CONFIRMED ===\n");
    printf("Offset in [objtotal*_objsize, memtotal) passes validation\n");
    printf("but produces lut index >= objtotal -> OOB read of adjacent heap.\n");
    printf("Garbage paddr mapped into userspace -> info leak / arbitrary phys R/W.\n");

    free(pool.lut);
    return 0;
}
