/*
 * DF-4835 harness — mrsas_alloc_mfi_cmds()/mrsas_create_frame_pool()
 * error path leaves dangling frame pointers + destroyed tag;
 * mrsas_attach -> mrsas_free_mem() then DOUBLE-FREES every frame and the tag.
 *
 * Flow replicated verbatim from sys/dev/raid/mrsas/:
 *   mrsas_ioctl.c:464-474  create_frame_pool: frames[0..K-1] alloc'd, fail at K
 *   mrsas_ioctl.c:422-431  alloc_mfi_cmds error path:
 *        for (i = 0; i < 32; i++) mrsas_free_frame(sc, list[i]);  // frees
 *        if (sc->mficmd_frame_tag) bus_dma_tag_destroy(...);      // tag freed
 *        return ENOMEM;            // frame_mem/frame_phys_addr NOT cleared,
 *                                   // mfi_cmd_list NOT cleared, tag NOT NULLed
 *   mrsas_ioctl.c:532-538  mrsas_free_frame:
 *        if (frame_phys_addr) bus_dmamap_unload(tag, map);
 *        if (frame_mem)      bus_dmamem_free(tag, frame_mem, map); // no clearing
 *   mrsas.c:1719            init_fw -> return 1
 *   mrsas.c:800,842         attach: goto attach_fail_fw -> mrsas_free_mem()
 *   mrsas.c:1011-1018       free_mem:
 *        for (i = 0; i < 32; i++) mrsas_free_frame(sc, list[i]);  // 2nd free!
 *        if (mficmd_frame_tag) bus_dma_tag_destroy(...);          // 2nd destroy
 *
 * Arms:
 *   baseline — verbatim sequence; the 2nd free pass must trip the allocator's
 *              double-free detection (abort) => would-be kernel heap corruption
 *   fixed    — error path clears frame_mem/frame_phys_addr and NULLs the tag
 *              (as in fix.diff) => free_mem runs clean
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/wait.h>

#define MRSAS_MAX_MFI_CMDS 32
#define FAIL_AT 5                    /* frame pool dies at cmd #5 */

struct mrsas_mfi_cmd {
    void    *frame_mem;
    void    *frame_dmamap;
    uint64_t frame_phys_addr;
};
static struct mrsas_mfi_cmd cmds[MRSAS_MAX_MFI_CMDS];
static struct mrsas_mfi_cmd *list[MRSAS_MAX_MFI_CMDS];
static void *g_tag;                          /* sc->mficmd_frame_tag */

static int bus_dmamem_alloc_fail_at = -1;    /* which alloc fails (inject OOM) */

/* --- mock allocator with double-free / use-after-free detection --- */
static void *live_ptr[256];  static int nlive;
static void *freed_ptr[256]; static int nfreed;
static int g_free_faults;

static int is_freed(void *p) {
    for (int i = 0; i < nfreed; i++) if (freed_ptr[i] == p) return 1;
    return 0;
}
static void *kalloc(size_t n) {
    void *p = malloc(n);
    live_ptr[nlive++] = p;
    return p;
}
static void kfree_real(void *p) {
    if (is_freed(p)) {
        g_free_faults++;
        printf("    ** DOUBLE FREE of %p detected (kernel: heap corruption)\n", p);
    } else {
        freed_ptr[nfreed++] = p;
    }
    free(p);                     /* keep going to count every occurrence */
}
static void kmcheck(void *p) {   /* read of possibly-freed kernel memory */
    if (p && is_freed(p)) {
        g_free_faults++;
        printf("    ** USE-AFTER-FREE touch of freed %p\n", p);
    }
}

static void *tag_create(void) { return kalloc(64); }
static int   tag_destroy(void *t) { kfree_real(t); return 0; }

/* mrsas_ioctl.c:486-503 mrsas_alloc_frame */
static void *alloc_frame(struct mrsas_mfi_cmd *cmd, int idx) {
    if (idx == bus_dmamem_alloc_fail_at) return NULL;   /* ENOMEM injected */
    cmd->frame_mem  = kalloc(1024);                     /* MRSAS_MFI_FRAME_SIZE */
    cmd->frame_dmamap = (void *)0x1;
    cmd->frame_phys_addr = 0x1000 + idx * 0x1000;       /* loaded */
    return cmd->frame_mem;
}

static void mrsas_free_frame(struct mrsas_mfi_cmd *cmd);
static void bus_dmamap_unload(struct mrsas_mfi_cmd *cmd)
{
    /* real one touches the tag; on the 2nd pass the tag is freed memory */
    if (g_tag) kmcheck(g_tag);
    cmd->frame_dmamap = (void *)0x2;            /* map state cleared */
}

/* mrsas_ioctl.c:532-538 — verbatim semantics: NO field clearing */
static void mrsas_free_frame(struct mrsas_mfi_cmd *cmd) {
    if (cmd->frame_phys_addr)
        bus_dmamap_unload(cmd);                 /* unload(tag, map) */
    if (cmd->frame_mem != NULL) {
        kmcheck(cmd->frame_mem);
        kfree_real(cmd->frame_mem);
    }
}

/* mrsas_ioctl.c:446-477 */
static int mrsas_create_frame_pool(int fixed) {
    g_tag = tag_create();
    for (int i = 0; i < MRSAS_MAX_MFI_CMDS; i++) {
        if (alloc_frame(cmds + i, i) == NULL) {
            printf("  create_frame_pool: alloc failed at cmd %d "
                   "(BUS_DMA_NOWAIT ENOMEM)\n", i);
            if (fixed) {
                /* fix.diff: unwind properly */
                for (int j = 0; j < i; j++) { cmds[j].frame_phys_addr = 0; kfree_real(cmds[j].frame_mem); cmds[j].frame_mem = NULL; }
                tag_destroy(g_tag); g_tag = NULL;
                return -1;
            }
            return -1;                           /* original: just fail */
        }
    }
    return 0;
}

/* mrsas_ioctl.c:382-434 */
static int mrsas_alloc_mfi_cmds(int fixed) {
    for (int i = 0; i < MRSAS_MAX_MFI_CMDS; i++) list[i] = cmds + i;
    memset(cmds, 0, sizeof(cmds));
    if (mrsas_create_frame_pool(fixed)) {
        if (!fixed) {
            for (int i = 0; i < MRSAS_MAX_MFI_CMDS; i++)   /* :424-427 */
                mrsas_free_frame(list[i]);
            if (g_tag != NULL) { tag_destroy(g_tag); }      /* :428-429, not NULLed */
        }
        /* both: mfi_cmd_list leaked/alive, returns ENOMEM */
        return -1;
    }
    return 0;
}

/* mrsas.c:927 mrsas_free_mem (MFI part :1011-1018) */
static int mrsas_free_mem(void) {
    int faults = 0;
    for (int i = 0; i < MRSAS_MAX_MFI_CMDS; i++)
        mrsas_free_frame(list[i]);                          /* 2nd pass */
    if (g_tag != NULL) { if (g_free_faults) faults++; tag_destroy(g_tag); }
    return faults;
}

int main(int argc, char **argv) {
    setvbuf(stdout, NULL, _IONBF, 0);
    int fixed = (argc > 1 && strcmp(argv[1], "fixed") == 0);
    pid_t pid = fork();
    if (pid == 0) {
        int prev = g_free_faults;
        (void)prev;
        bus_dmamem_alloc_fail_at = FAIL_AT;
        if (mrsas_alloc_mfi_cmds(fixed) != 0) {
            printf("  alloc_mfi_cmds -> ENOMEM -> init_fw fails -> "
                   "attach_fail_fw -> mrsas_free_mem()\n");
        }
        int faults = mrsas_free_mem();
        printf("  mrsas_free_mem: %s (use-after-free/double-free detected: %s)\n",
               faults ? "HEAP CORRUPTION" : "clean",
               (faults || g_free_faults) ? "YES" : "no");
        exit(faults ? 42 : 0);
    }
    int st; waitpid(pid, &st, 0);
    if (WIFSIGNALED(st))
        printf("[%s] child KILLED by %s (allocator double-free abort — "
               "kernel equivalent: heap corruption/UAF)\n",
               fixed ? "FIXED " : "BASELINE", strsignal(WTERMSIG(st)));
    else
        printf("[%s] child exit=%d\n", fixed ? "FIXED " : "BASELINE", WEXITSTATUS(st));
    return 0;
}
