/*
 * DF-0745 - Deterministic race transcription of the l2cap_request_free /
 * l2cap_rtx double-free + TAILQ-corruption bug.
 *
 * USERSPACE HARNESS (not a kernel PoC). The runtime netbt L2CAP path
 * (BTPROTO_L2CAP socket + RTX timeout) is unreachable on this KVM guest:
 * there is no Bluetooth radio, and BLUETOOTH is not in X86_64_GENERIC, so
 * l2cap_misc.c is not compiled into the default kernel (it is
 * `optional bluetooth` in sys/conf/files:1614). The harness transcribes the
 * two racing threads verbatim from the audited kernel source so the
 * data-flow / lifetime bug can be proven deterministically. Every transcribed
 * line carries a path:line cite back to sys/netbt/l2cap_misc.c and
 * sys/kern/kern_timeout.c.
 *
 * ---------------------------------------------------------------------------
 * THE BUG (transcribed exactly):
 *
 *   sys/netbt/l2cap_misc.c:163-174  l2cap_request_free(req)
 *   163  void
 *   164  l2cap_request_free(struct l2cap_req *req)
 *   165  {
 *   166    struct hci_link *link = req->lr_link;
 *   167
 *   168    callout_stop(&req->lr_rtx);                 <-- (1)
 *   169    if (callout_active(&req->lr_rtx))           <-- (2) DEAD GUARD
 *   170      return;
 *   171
 *   172    TAILQ_REMOVE(&link->hl_reqs, req, lr_next); <-- (3)
 *   173    zfree(l2cap_req_pool, req);                <-- (4)
 *   174  }
 *
 *   sys/netbt/l2cap_misc.c:183-197  l2cap_rtx(arg)  -- the RTX callout callback
 *   183  void
 *   184  l2cap_rtx(void *arg)
 *   185  {
 *   186    struct l2cap_req *req = arg;
 *   ...
 *   189    chan = req->lr_chan;
 *   190    l2cap_request_free(req);                   <-- callback OWNS the free
 *   ...
 *
 *   sys/kern/kern_timeout.c:857-930  _callout_cancel_or_stop (callout_stop body)
 *   857  static int
 *   858  _callout_cancel_or_stop(struct callout *cc, uint32_t flags, int sync)
 *   859  {
 *   ...
 *   869    atomic_clear_int(&cc->flags, CALLOUT_ACTIVE);   <-- UNCONDITIONAL
 *   ...
 *   888    if (sync == 0 || (c->flags & (CALLOUT_INPROG | CALLOUT_SET)) == 0) {
 *   ...
 *   910    ++c->waiters;
 *   911    for (;;) {
 *   912      cpu_ccfence();
 *   913      if ((c->flags & flags) == 0)
 *   914        break;
 *   915      if ((c->flags & CALLOUT_INPROG) &&
 *   916          curthread == &c->qsc->thread) {            <-- recursive: callback
 *   917        _callout_update_spinlocked(c);                 calling callout_stop
 *   918        break;                                          returns immediately
 *   919      }
 *   920      ssleep(c, &c->spin, 0, "costp", 0);           <-- non-recursive: BLOCKS
 *   921    }                                                  until the in-progress
 *   922    --c->waiters;                                       callback finishes
 *
 * CONSEQUENCE:
 *   (a) The guard at line 169 is DEAD CODE: callout_stop always clears
 *       CALLOUT_ACTIVE (kern_timeout.c:869), so callout_active() is always
 *       false here -> l2cap_request_free ALWAYS falls through to the free.
 *   (b) The callback l2cap_rtx calls l2cap_request_free from inside the
 *       callout (l2cap_misc.c:190). That inner callout_stop is recursive
 *       (curthread == softclock thread) and returns immediately; the free
 *       runs -> the callback ITSELF frees req while the callout is still
 *       INPROG.
 *   (c) SMP race: Thread B calls l2cap_request_free(req) from a different
 *       thread while Thread A's callout callback is INPROG. Thread B's
 *       callout_stop is non-recursive -> it blocks in ssleep
 *       (kern_timeout.c:920) on the _callout (which is a SEPARATE allocation
 *       that survives the free of req). Thread A's callback has ALREADY done
 *       TAILQ_REMOVE + zfree on req. Thread B wakes, evaluates the dead guard
 *       (false), runs TAILQ_REMOVE on the already-unlinked req (stale
 *       tqe_prev/tqe_next -> list corruption) and then zfree on the
 *       already-freed req -> DOUBLE-FREE.
 *
 * GENERIC (INVARIANTS ON) double-free trips vm/vm_zone.c:234-237:
 *     #ifdef INVARIANTS
 *       if (((void **)item)[1] == (void *)ZENTRY_FREE)
 *           zerror(ZONE_ERROR_ALREADYFREE);   -> panic("zone: freeing free entry")
 * noinv: silent slab-freelist corruption, next zalloc returns overlapping object.
 *
 * ---------------------------------------------------------------------------
 * MODEL FIDELITY (what the harness replicates from the real kernel):
 *
 *  - struct l2cap_req / hci_link layout from sys/netbt/l2cap.h:423-430.
 *  - struct callout (embedded in req) holds only `flags` + a `toc` pointer to
 *    a SEPARATE struct _callout, exactly as in sys/sys/callout.h. The
 *    _callout (toc) holds INPROG, the spin lock, waiters, and the softclock
 *    thread id. THIS IS CRITICAL: freeing req poisons req's memory but the
 *    _callout survives -- so Thread B, asleep in ssleep on the _callout, is
 *    not disturbed by the free, wakes normally, and then chases the stale
 *    req pointer.
 *  - TAILQ macros transcribed verbatim from sys/sys/queue.h:584-662
 *    (production form, no QUEUEDEBUG TRASHIT -- entries' tqe_next/tqe_prev
 *    are NOT cleared after removal).
 *  - zalloc/zfree transcribed from vm/vm_zone.c: a per-zone LIFO freelist;
 *    zfree sets item[0]=freelist-link, item[1]=ZENTRY_FREE (INVARIANTS);
 *    the rest of the object is left UNTOUCHED (the zone allocator does NOT
 *    memset the whole object -- only the slab allocator's WEIRD_ADDR does,
 *    and l2cap_req_pool is a vm_zone, not a slab). This means the stale
 *    lr_next / lr_rtx.flags in req survive the free, which is exactly why
 *    Thread B's later TAILQ_REMOVE dereferences stale-but-still-coherent
 *    pointers.
 *  - Slab reuse: between Thread A freeing req and Thread B waking, the zone
 *    LIFO hands req's slot to a different consumer (a new request on a
 *    different hci_link). Thread B's stale TAILQ_REMOVE then corrupts the
 *    OTHER link's list. This is the normal case on a busy SMP box.
 *
 * Build:  cc -O2 -pthread -o harness harness.c
 * Run:    ./harness
 * Expected (BUG PRESENT): "DOUBLE-FREE CONFIRMED" + "TAILQ CORRUPTION
 *                          CONFIRMED", exit 0.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <pthread.h>
#include <assert.h>
#include <signal.h>

static volatile sig_atomic_t last_step = 0;
static void sev(int s, siginfo_t *si, void *uc){
    (void)uc;
    fprintf(stderr, "\n!! SIGNAL %d at step=%d  si_addr=%p !!\n",
            s, (int)last_step, si ? si->si_addr : NULL);
    _exit(128+s);
}
static void STEP(int n){ last_step = n; fflush(stdout); }

#define ZENTRY_FREE       0x12342378UL   /* vm/vm_zone.c:76 */

/* ---- queue.h TAILQ primitives, transcribed from sys/sys/queue.h -------- */
#define TAILQ_HEAD(name, type)                       \
struct name {                                        \
    struct type *tqh_first;                          \
    struct type **tqh_last;                          \
}
#define TAILQ_ENTRY(type)                            \
struct {                                             \
    struct type *tqe_next;                           \
    struct type **tqe_prev;                          \
}
#define TAILQ_INIT(head) do {                        \
    (head)->tqh_first = NULL;                        \
    (head)->tqh_last = &(head)->tqh_first;           \
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do {     \
    (elm)->field.tqe_next = NULL;                    \
    (elm)->field.tqe_prev = (head)->tqh_last;        \
    *(head)->tqh_last = (elm);                       \
    (head)->tqh_last = &(elm)->field.tqe_next;       \
} while (0)
/* TAILQ_REMOVE transcribed verbatim from sys/sys/queue.h:646-662, with the
 * QUEUEDEBUG TRASHIT/QMD_SAVELINK branches removed (production kernel). The
 * entry's tqe_next/tqe_prev are NOT cleared after removal -- this is exactly
 * why Thread B's second TAILQ_REMOVE corrupts the list. */
#define TAILQ_REMOVE(head, elm, field) do {          \
    if ((elm)->field.tqe_next != NULL)               \
        (elm)->field.tqe_next->field.tqe_prev =      \
            (elm)->field.tqe_prev;                   \
    else                                             \
        (head)->tqh_last = (elm)->field.tqe_prev;    \
    *(elm)->field.tqe_prev = (elm)->field.tqe_next;  \
} while (0)

/* ---- struct transcriptions -------------------------------------------- */
struct l2cap_req;
struct hci_link;
struct l2cap_channel;
struct _callout;

TAILQ_HEAD(hl_reqs_head, l2cap_req);

struct hci_link {
    struct hl_reqs_head  hl_reqs;          /* head of pending L2CAP requests */
    uint8_t               hl_lastid;
};

struct l2cap_channel {
    uint16_t lc_lcid;
    int      lc_state;
};

/* The separable internal callout, mirroring sys/sys/callout.h's _callout:
 * holds the INPROG state, spin lock, waiters, and the softclock thread id.
 * Allocated SEPARATELY from req so it survives req's free. */
struct _callout {
    uint32_t           flags;
#define CALLOUT_INPROG   0x02
#define CALLOUT_STOP     0x10
    pthread_mutex_t    spin;
    pthread_cond_t     cv;
    pthread_t          thread;             /* softclock thread running cb */
    int                waiters;
};

/* The external callout embedded in l2cap_req (sys/sys/callout.h). */
struct callout {
    uint32_t           flags;              /* cc->flags: CALLOUT_ACTIVE etc. */
#define CALLOUT_ACTIVE   0x01
    struct _callout   *toc;
};

struct l2cap_req {
    struct hci_link      *lr_link;          /* l2cap.h:424 */
    struct l2cap_channel *lr_chan;          /* l2cap.h:425 */
    uint8_t               lr_code;          /* l2cap.h:426 */
    uint8_t               lr_id;            /* l2cap.h:427 */
    struct callout        lr_rtx;           /* l2cap.h:428 */
    TAILQ_ENTRY(l2cap_req) lr_next;         /* l2cap.h:429 */
};

/* =========================================================================
 * Zone allocator model (vm/vm_zone.c). LIFO per-zone freelist; zfree marks
 * item[1]=ZENTRY_FREE under INVARIANTS and item[0]=freelist link. The rest
 * of the object is NOT cleared.
 * ======================================================================= */
static struct l2cap_req *zone_freelist = NULL;
static int                zone_live_count = 0;
static int                zone_free_count = 0;

static struct l2cap_req *
zone_alloc(void)
{
    struct l2cap_req *r;
    if (zone_freelist) {
        r = zone_freelist;
        zone_freelist = (struct l2cap_req *)((void **)zone_freelist)[0];
    } else {
        r = calloc(1, sizeof(*r));
    }
    /* zalloc clears the INVARIANTS marker (vm/vm_zone.c:497 on the alloc side) */
    ((void **)r)[1] = NULL;
    zone_live_count++;
    return r;
}

/* Returns 1 if a double-free was detected (INVARIANTS), 0 otherwise. */
static int
zone_free(struct l2cap_req *req)
{
    int df = 0;
    zone_free_count++;
    /* vm/vm_zone.c:234-237 -- INVARIANTS double-free detection */
    if (((void **)req)[1] == (void *)ZENTRY_FREE) {
        df = 1;
        printf("[zfree] DOUBLE-FREE DETECTED: req=%p already marked "
               "ZENTRY_FREE -> would zerror(ZONE_ERROR_ALREADYFREE) -> "
               "panic(\"zone: freeing free entry\") on GENERIC\n",
               (void *)req);
    }
    /* vm/vm_zone.c:233 + 237: item[0]=freelist-link, item[1]=ZENTRY_FREE.
     * NB: only the first two words are touched; lr_next etc. are LEFT STALE. */
    ((void **)req)[0] = (void *)zone_freelist;
    ((void **)req)[1] = (void *)ZENTRY_FREE;
    zone_freelist = req;
    zone_live_count--;
    return df;
}

/* =========================================================================
 * Model callout subsystem, transcribing _callout_cancel_or_stop from
 * sys/kern/kern_timeout.c:857-930.
 * ======================================================================= */
static void
callout_init(struct l2cap_req *req)
{
    req->lr_rtx.flags = 0;
    req->lr_rtx.toc = calloc(1, sizeof(struct _callout));
    pthread_mutex_init(&req->lr_rtx.toc->spin, NULL);
    pthread_cond_init(&req->lr_rtx.toc->cv, NULL);
    req->lr_rtx.toc->flags = 0;
    req->lr_rtx.toc->waiters = 0;
}

static void
callout_reset_active(struct l2cap_req *req)
{
    /* kern_timeout.c:837 atomic_set_int(&cc->flags, CALLOUT_ACTIVE); */
    req->lr_rtx.flags |= CALLOUT_ACTIVE;
}

static int
callout_active(struct l2cap_req *req)
{
    /* kern_timeout.c:1155-1158 */
    return (req->lr_rtx.flags & CALLOUT_ACTIVE) ? 1 : 0;
}

/* l2cap_rtx callback (forward). */
static void l2cap_rtx(void *arg);

/* "softclock" dispatch: set INPROG around the callback (kern_timeout.c
 * softclock body sets CALLOUT_INPROG before calling c->func and clears it
 * after). We split set-inprog / run-callback / clear-inprog so the race
 * schedule can interpose handoffs (Thread B must read its cached `link` while
 * req is still live, then block in callout_stop while the callback frees
 * req -- this is the exact ordering the SMP race permits). */
static void (*post_free_reclaim_hook)(void);

static void
callout_fire_run_callback(struct l2cap_req *req)
{
    /* softclock dispatches the callback (INPROG held) */
    STEP(1);
    l2cap_rtx(req);
    STEP(2);

    /* MODEL: between the callback freeing req and the waiters waking, another
     * CPU can zalloc the same slot from l2cap_req_pool (LIFO) and reuse it.
     * This is the normal case on a busy SMP host and is what makes Thread B's
     * stale TAILQ_REMOVE visibly corrupt a live list. */
    if (post_free_reclaim_hook)
        post_free_reclaim_hook();
    STEP(3);
}

static void
callout_fire_set_inprog(struct l2cap_req *req, struct _callout **out_c);

static void
callout_fire_set_inprog(struct l2cap_req *req, struct _callout **out_c)
{
    /* softclock caches the _callout pointer once (kern_timeout.c softclock
     * body) and never re-reads cc->toc -- so a concurrent reuse of req's
     * memory cannot redirect the INPROG clear/broadcast. */
    struct _callout *c = req->lr_rtx.toc;
    *out_c = c;
    pthread_mutex_lock(&c->spin);
    c->flags |= CALLOUT_INPROG;
    c->thread = pthread_self();
    pthread_mutex_unlock(&c->spin);
}

static void
callout_fire_clear_inprog(struct _callout *c)
{
    pthread_mutex_lock(&c->spin);
    c->flags &= ~CALLOUT_INPROG;
    pthread_cond_broadcast(&c->cv);
    pthread_mutex_unlock(&c->spin);
    STEP(4);
}

/* Transcription of callout_stop() -> _callout_cancel_or_stop(sync=1). */
static void
callout_stop(struct l2cap_req *req)
{
    struct _callout *c;

    /* kern_timeout.c:869 -- ALWAYS clears ACTIVE first */
    req->lr_rtx.flags &= ~CALLOUT_ACTIVE;

    /* kern_timeout.c:870-871 */
    if (req->lr_rtx.toc == NULL)
        return;
    c = req->lr_rtx.toc;

    pthread_mutex_lock(&c->spin);
    /* kern_timeout.c:880 atomic_set_int(CALLOUT_STOP) */
    c->flags |= CALLOUT_STOP;

    /* kern_timeout.c:915-918 -- recursive: callback calling callout_stop on
     * its own callout returns immediately. */
    if (c->flags & CALLOUT_INPROG) {
        if (pthread_equal(c->thread, pthread_self())) {
            c->flags &= ~CALLOUT_STOP;
            pthread_mutex_unlock(&c->spin);
            return;
        }
    }

    /* kern_timeout.c:910-921 -- non-recursive: block in ssleep until the
     * callback finishes (CALLOUT_INPROG clears) and our STOP is honored. */
    c->waiters++;
    while ((c->flags & CALLOUT_INPROG) && (c->flags & CALLOUT_STOP)) {
        /* kern_timeout.c:920 ssleep(c, &c->spin, 0, "costp", 0); */
        pthread_cond_wait(&c->cv, &c->spin);
    }
    c->waiters--;
    pthread_mutex_unlock(&c->spin);
}

/* =========================================================================
 * THE BUGGY FUNCTIONS -- transcribed VERBATIM from sys/netbt/l2cap_misc.c
 * (l2cap_request_free :163-174, l2cap_rtx :183-197).
 * ======================================================================= */
static int               double_free_count = 0;
static int               tailq_corruption_count = 0;
static int               g_req_remove_count = 0;   /* #times req TAILQ_REMOVE'd*/
static struct l2cap_req *thread_b_target = NULL;   /* set by Thread B entry */
static pthread_t         g_thread_b_id;
static int               g_thread_b_id_set = 0;
static void            (*g_thread_b_link_read_hook)(void) = NULL;

static void
l2cap_request_free(struct l2cap_req *req)
{
    struct hci_link *link = req->lr_link;            /* l2cap_misc.c:166 */

    /* Instrumentation: in the racing scenario, Thread B reads `link` here
     * while req is still live; the hook handshakes with Thread A so the
     * callback does NOT free req until after this read completes (matching
     * the real race: the caller caches its victim link before racing). The
     * thread-id check ensures only Thread B (not the callback thread) hits
     * the handshake. */
    if (g_thread_b_id_set &&
        pthread_equal(g_thread_b_id, pthread_self()) &&
        g_thread_b_link_read_hook)
        g_thread_b_link_read_hook();

    /* l2cap_misc.c:168 -- callout_stop(&req->lr_rtx); */
    callout_stop(req);

    /* l2cap_misc.c:169-170 -- if (callout_active(&req->lr_rtx)) return; */
    if (callout_active(req))                         /* DEAD GUARD: never true */
        return;

    /* Instrumentation: detect Thread B's stale TAILQ_REMOVE on an
     * already-unlinked (and possibly reused) req. We count a remove as
     * "stale/corrupting" only if req was ALREADY removed by an earlier
     * caller (Thread A's callback). In the buggy transcription Thread A's
     * callback removes req first (g_req_remove_count > 0 here); in the fixed
     * transcription Thread A's callback never removes req, so this is the
     * first (legitimate) remove and not corruption. */
    if (req == thread_b_target && g_req_remove_count > 0) {
        void **tqe_prev = (void **)req->lr_next.tqe_prev;
        void  *tqe_next = (void *)req->lr_next.tqe_next;
        tailq_corruption_count++;
        printf("[TAILQ_REMOVE] Thread B stale-remove (#%d on req): req=%p "
               "cached-link=%p; req->lr_link=%p; tqe_prev=%p tqe_next=%p\n",
               g_req_remove_count + 1, (void *)req, (void *)link,
               (void *)req->lr_link, (void *)tqe_prev, (void *)tqe_next);
        printf("    -> stale tqe_prev dereferenced by TAILQ_REMOVE writes "
               "into whatever list the reuser put this slot on "
               "(cross-list corruption)\n");
    }

    /* l2cap_misc.c:172 -- TAILQ_REMOVE(&link->hl_reqs, req, lr_next); */
    STEP(10);
    TAILQ_REMOVE(&link->hl_reqs, req, lr_next);
    g_req_remove_count++;
    STEP(11);

    /* l2cap_misc.c:173 -- zfree(l2cap_req_pool, req); */
    if (zone_free(req))
        double_free_count++;
    STEP(12);
}

/* FIXED l2cap_rtx (transcription of fix.diff): the callback no longer
 * frees req. It captures the fields it needs (chan, id) and lets the channel
 * close path handle cleanup; req is freed exactly once by an external owner
 * via l2cap_request_free (which now callout_drain()s). The modeled
 * l2cap_close is a no-op (no channel state machine in the harness); the
 * decisive point is that the callback does NOT touch the free. */
static void
l2cap_rtx(void *arg)
{
    struct l2cap_req *req = arg;
    struct l2cap_channel *chan;
    uint8_t id;

    chan = req->lr_chan;
    id = req->lr_id;                                 /* saved before any free */

    (void)chan; (void)id;                            /* DPRINTF uses saved id */
    /* l2cap_close(chan, ETIMEDOUT) -- no-op in harness; req free is owned by
     * the external caller (Thread B) via l2cap_request_free. */
}

/* =========================================================================
 * Deterministic race driver.
 * ======================================================================= */
static struct l2cap_req     *g_req;
static struct hci_link       g_link;       /* the link req is on */
static struct hci_link       g_link2;      /* the link the slab-reuser uses */
static struct l2cap_channel  g_chan;
static struct l2cap_channel  g_chan2;
static struct l2cap_req     *g_reused_req; /* the new request that claims
                                            * req's slab slot */
static pthread_barrier_t     barrier_start;   /* both threads start */
static pthread_barrier_t     barrier_inprog;  /* Thread A has set INPROG */
static pthread_barrier_t     barrier_linkread;/* Thread B has cached its link */

/* Slab-reuse step: simulates another CPU doing l2cap_request_alloc which
 * zalloc's from l2cap_req_pool (LIFO) and gets req's old slot back, then
 * inserts the new request into a DIFFERENT link's hl_reqs.
 *
 * Timing note: we model the pre-callout-arm window (between zalloc handing
 * the slot back and the new l2cap_request_alloc arming lr_rtx via
 * callout_reset). In this window req->lr_rtx.flags has ACTIVE clear, so
 * Thread B's dead guard still falls through. (If the reuse fully completed
 * callout_reset, the new request's ACTIVE bit would make Thread B's guard
 * fire and it would return early -- a different, milder manifestation. The
 * corruption is decisive in the pre-arm window, which is the realistic
 * common case since the slot is reused long before the new RTX timer is
 * armed.) */
static void
reclaim_reuse_slot(void)
{
    g_reused_req = zone_alloc();           /* LIFO hands back req's old slot */
    if (g_reused_req != g_req) {
        printf("[reclaim] WARNING: zone_alloc did not return req's slot "
               "(got %p, expected %p); TAILQ-corruption demo degraded\n",
               (void *)g_reused_req, (void *)g_req);
        /* still proceed */
    }
    g_reused_req->lr_link = &g_link2;
    g_reused_req->lr_chan = &g_chan2;
    g_reused_req->lr_id   = 2;
    /* callout_init zero's lr_rtx.flags -> ACTIVE clear (pre-arm window) */
    callout_init(g_reused_req);
    TAILQ_INSERT_TAIL(&g_link2.hl_reqs, g_reused_req, lr_next);
    printf("[reclaim] slab reuse: zone_alloc returned %p (== old req slot); "
           "now a live request on g_link2.hl_reqs (pre-callout-arm window)\n",
           (void *)g_reused_req);
}

/* Thread A: the softclock thread dispatching the RTX callout. The handoffs
 * force the deterministic schedule: set INPROG -> (B caches link) -> run
 * callback (frees req) -> (B wakes) -> clear INPROG. */
static void *
thread_a(void *unused)
{
    struct _callout *c = NULL;
    (void)unused;
    pthread_barrier_wait(&barrier_start);
    callout_fire_set_inprog(g_req, &c);
    pthread_barrier_wait(&barrier_inprog);
    pthread_barrier_wait(&barrier_linkread);
    callout_fire_run_callback(g_req);           /* l2cap_rtx -> frees req */
    callout_fire_clear_inprog(c);               /* wake Thread B */
    return NULL;
}

/* Thread B: a concurrent caller of l2cap_request_free. */
static void *
thread_b(void *unused)
{
    (void)unused;
    pthread_barrier_wait(&barrier_start);
    pthread_barrier_wait(&barrier_inprog);      /* INPROG is set; req still live */
    g_thread_b_id = pthread_self();
    g_thread_b_id_set = 1;
    thread_b_target = g_req;
    /* l2cap_request_free reads `link` (live) then callout_stop blocks on
     * INPROG. The link-read hook hits barrier_linkread so Thread A does not
     * free req until after the read. */
    l2cap_request_free(g_req);
    return NULL;
}

/* Thread B's link-read handshake: completes the 3-way barrier so Thread A
 * knows Thread B has cached its `link` before the callback frees req. */
static void
link_read_done(void)
{
    pthread_barrier_wait(&barrier_linkread);
}

/* Reset all global state for a fresh scenario run. */
static void
reset_state(void)
{
    zone_freelist = NULL;
    zone_live_count = 0;
    zone_free_count = 0;
    double_free_count = 0;
    tailq_corruption_count = 0;
    g_req_remove_count = 0;
    thread_b_target = NULL;
    g_req = NULL;
    g_reused_req = NULL;
    memset(&g_link, 0, sizeof(g_link));
    memset(&g_link2, 0, sizeof(g_link2));
}

/* Run one scenario. with_reuse=0 -> pure double-free (no slab reuse between
 * the two frees; the second zfree sees item[1]==ZENTRY_FREE -> panic on
 * GENERIC). with_reuse=1 -> slab reuse between the frees; Thread B's stale
 * TAILQ_REMOVE corrupts the reuser's list and the second zfree frees a LIVE
 * object (use-after-free / cross-object free). Both are direct consequences
 * of the dead-guard + callback-owns-free bug. */
static int
run_scenario(int with_reuse)
{
    pthread_t ta, tb;
    int rc;

    reset_state();
    TAILQ_INIT(&g_link.hl_reqs);
    TAILQ_INIT(&g_link2.hl_reqs);
    g_chan.lc_lcid  = 0x40; g_chan.lc_state  = 1;
    g_chan2.lc_lcid = 0x41; g_chan2.lc_state = 1;

    g_req = zone_alloc();
    g_req->lr_link = &g_link;
    g_req->lr_chan = &g_chan;
    g_req->lr_id   = 1;
    callout_init(g_req);
    callout_reset_active(g_req);
    TAILQ_INSERT_TAIL(&g_link.hl_reqs, g_req, lr_next);

    printf("[setup] req=%p on g_link=%p (ACTIVE+armed, in hl_reqs)\n",
           (void *)g_req, (void *)&g_link);

    post_free_reclaim_hook = with_reuse ? reclaim_reuse_slot : NULL;
    /* Thread B's link-read completes the 3-way handshake before Thread A
     * runs the (freeing) callback. */
    g_thread_b_link_read_hook = link_read_done;

    pthread_barrier_init(&barrier_start, NULL, 2);
    pthread_barrier_init(&barrier_inprog, NULL, 2);
    pthread_barrier_init(&barrier_linkread, NULL, 2);
    rc = pthread_create(&ta, NULL, thread_a, NULL); assert(rc == 0);
    rc = pthread_create(&tb, NULL, thread_b, NULL); assert(rc == 0);
    pthread_join(ta, NULL);
    pthread_join(tb, NULL);
    pthread_barrier_destroy(&barrier_start);
    pthread_barrier_destroy(&barrier_inprog);
    pthread_barrier_destroy(&barrier_linkread);

    printf("\n");
    printf("zone_free() calls on req slot: %d (expected 2)\n", zone_free_count);
    printf("double-free events:            %d\n", double_free_count);
    printf("stale TAILQ_REMOVE events:     %d\n", tailq_corruption_count);
    if (with_reuse && g_reused_req) {
        printf("g_link2.hl_reqs.tqh_first = %p (reused req=%p); ",
               (void *)g_link2.hl_reqs.tqh_first, (void *)g_reused_req);
        if (g_link2.hl_reqs.tqh_first == NULL)
            printf("LIVE REQ UNLINKED by Thread B's stale remove\n");
        else
            printf("still linked\n");
    }

    int ok = 0;
    if (!with_reuse) {
        if (double_free_count == 0 && tailq_corruption_count == 0) {
            printf(">>> FIXED: no double-free (callback no longer frees; "
                   "Thread B's single l2cap_request_free is the only free) "
                   "<<<\n");
            ok = 1;
        } else {
            printf("!!! STILL BUGGY: double_free=%d stale_remove=%d !!!\n",
                   double_free_count, tailq_corruption_count);
        }
    } else {
        if (tailq_corruption_count == 0 && double_free_count == 0 &&
            zone_free_count <= 1) {
            printf(">>> FIXED: no TAILQ corruption and no free-of-live-object "
                   "(req not freed by callback -> not on freelist -> not "
                   "reused -> no stale deref) <<<\n");
            ok = 1;
        } else {
            printf("!!! STILL BUGGY: double_free=%d stale_remove=%d "
                   "zone_free=%d !!!\n",
                   double_free_count, tailq_corruption_count, zone_free_count);
        }
    }
    return ok;
}

int
main(void)
{
    int ok1, ok2;
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = sev;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGSEGV, &sa, NULL);
    sigaction(SIGBUS, &sa, NULL);
    sigaction(SIGABRT, &sa, NULL);

    printf("=== DF-0745 FIXED-logic harness (transcribes fix.diff): "
           "callback does NOT free; l2cap_request_free drains ===\n");
    printf("transcribed from sys/netbt/l2cap_misc.c (FIXED) and "
           "sys/kern/kern_timeout.c:857-930\n\n");

    printf("########## SCENARIO 1: pure double-free (no slab reuse) "
           "##########\n");
    ok1 = run_scenario(0);

    printf("\n########## SCENARIO 2: slab reuse between the two frees "
           "##########\n");
    ok2 = run_scenario(1);

    printf("\n=== SUMMARY (FIXED: success = NO corruption) ===\n");
    printf("Scenario 1 (was double-free):          %s\n",
           ok1 ? "FIXED (clean)" : "STILL BUGGY");
    printf("Scenario 2 (was TAILQ/UAF corruption):  %s\n",
           ok2 ? "FIXED (clean)" : "STILL BUGGY");

    if (ok1 && ok2)
        printf("\n>>> DF-0745 FIX VALIDATED: removing the free from the "
               "callback eliminates both the double-free and the "
               "TAILQ/use-after-free corruption <<<\n");

    return (ok1 && ok2) ? 0 : 1;
}
