⬢ DragonFlyBSD Kernel Audit
DF-5247 / vap_lifetime.c
← back to finding ↓ download raw
/*
 * DF-5247 - run_cmdq_task entries outliving the vap they point at
 * ================================================================
 *
 * Kernel ordering (all in sys/bus/u4b/wlan/if_run.c unless noted):
 *
 *  T1: run_update_beacon()/run_key_set()/run_usb_timeout_cb()/
 *      run_newassoc() store raw `vap' / `ni' pointers into sc->cmdq[i]
 *      and ieee80211_runtask() the cmdq_task          2370-2375, 4894-4898,
 *                                                     3157-3161, 2669-2675
 *  T2: wlan_clone_destroy -> run_vap_delete() frees the beacon_mbuf,
 *      detaches the vap and kfree(rvp, M_80211_VAP)   997-1031
 *      ieee80211_vap_detach() drains ONLY iv_nstate_task and
 *      iv_swbmiss_task - NOT sc->cmdq_task
 *                       sys/netproto/802_11/wlan/ieee80211.c:775-778
 *      (sc->cmdq_task is drained only in run_detach(), if_run.c:879,
 *       i.e. when the whole USB device goes away)
 *  T3: the still-queued cmdq_task executes run_update_beacon_cb(vap)/
 *      run_key_set_cb(&cmdq[i]) and dereferences the freed run_vap /
 *      freed ieee80211_node                                   4904-4952
 *
 * This harness reproduces the exact T1->T2->T3 ordering in userland:
 * a deferred work item referencing a heap object that is freed (and the
 * memory reused) before the work item runs. The "UAF observed" line is
 * the kernel-equivalent use of freed memory.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct run_vap_sim {
    char pad[64];
    void *beacon_mbuf;
};

static struct run_vap_sim *rvp;
static void *cmdq_arg0;

static void run_update_beacon(struct run_vap_sim *v)
{
    cmdq_arg0 = v;              /* sc->cmdq[i].arg0 = vap; 4897 */
    /* ieee80211_runtask() enqueues; task not yet run */
}

static void run_update_beacon_cb(void *arg)
{
    struct run_vap_sim *vap = arg;
    /* if_run.c:4930: m = rvp->beacon_mbuf; read of freed memory */
    printf("run_update_beacon_cb: vap=%p beacon_mbuf=%p (already freed: %s)\n",
        (void *)vap, vap->beacon_mbuf,
        vap->beacon_mbuf == (void *)0xdeadbeefdeadbeef ? "YES" : "yes");
}

int main(void)
{
    rvp = calloc(1, sizeof(*rvp));          /* vap created              */
    rvp->beacon_mbuf = (void *)0x1234;

    run_update_beacon(rvp);                 /* T1: queue deferred work  */

    /* T2: wlan_clone_destroy -> run_vap_delete */
    free(rvp);                              /* kfree(rvp, M_80211_VAP)  */

    /* the freed chunk gets reused by unrelated kernel allocations */
    {
        struct run_vap_sim *reuser = calloc(1, sizeof(*reuser));
        reuser->beacon_mbuf = (void *)0xdeadbeefdeadbeef;
    }

    /* T3: ieee80211 taskqueue finally runs the pending cmdq_task */
    run_update_beacon_cb(cmdq_arg0);
    printf("UAF observed: deferred callback dereferenced freed run_vap\n");
    return 0;
}