โฌข DragonFlyBSD Kernel Audit
DF-0524 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-0524 harness: ng_fec stores raw ifnet pointer with no refcount -> UAF
 *
 * Source: sys/netgraph/fec/ng_fec.c
 *   - ng_fec_addport(:345): bifp = ifunit(iface) โ€” obtains pointer, NO if_ref()
 *   - ng_fec_addport(:408): new->fec_if = bifp โ€” stores raw pointer
 *   - Deref in delport(:455), init(:527), stop(:553), tick(:583), start(:974)
 *
 * If the member interface is detached (e.g. module unload, interface destroy),
 * the stored pointer becomes dangling. Next ioctl/tick/tx dereferences freed
 * memory -> UAF.
 *
 * No if_ref()/if_rele() calls exist in ng_fec.c. No EVENTHANDLER
 * ifnet_detach_notification is registered to catch interface removal.
 *
 * This harness demonstrates the lifetime mismatch: the fec bundle holds a
 * raw pointer to an ifnet that can be freed independently.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

struct ifnet {
    int if_index;
    char if_name[16];
    int if_flags;
    int alive; /* simulate whether the ifnet is still valid */
};

struct fec_port {
    struct ifnet *fec_if;  /* RAW POINTER โ€” no refcount! */
    uint8_t fec_mac[6];
};

struct fec_bundle {
    struct fec_port ports[4];
    int num_ports;
};

/* Replicates ng_fec_addport (line 345-408) */
static int fec_addport(struct fec_bundle *b, struct ifnet *ifp) {
    struct fec_port *p = &b->ports[b->num_ports];
    /* BUG: stores bifp WITHOUT if_ref() */
    p->fec_if = ifp;  /* line 408 */
    b->num_ports++;
    printf("  addport: stored raw ptr %p (if_index=%d) โ€” NO if_ref taken\n",
           (void*)ifp, ifp->if_index);
    return 0;
}

/* Simulates interface detachment (if_detach / module unload) */
static struct ifnet *detach_ifnet(struct ifnet *ifp) {
    printf("  if_detach: freeing ifnet %p (if_index=%d) โ€” fec still holds dangling ptr!\n",
           (void*)ifp, ifp->if_index);
    ifp->alive = 0;
    /* In kernel: the ifnet structure is freed to its objcache */
    /* Here we just mark it dead and zero it to simulate reuse */
    return ifp;
}

/* Replicates ng_fec_tick / ng_fec_start dereferencing fec_if */
static int fec_tick(struct fec_bundle *b) {
    int i;
    for (i = 0; i < b->num_ports; i++) {
        struct ifnet *ifp = b->ports[i].fec_if;
        /* UAF: dereferences potentially freed pointer */
        if (ifp->alive == 0) {
            printf("  fec_tick: UAF! dereferencing freed ifnet %p (if_index=%d)\n",
                   (void*)ifp, ifp->if_index);
            return -1; /* UAF detected */
        }
        printf("  fec_tick: port %d if_index=%d flags=0x%x (ok)\n",
               i, ifp->if_index, ifp->if_flags);
    }
    return 0;
}

int main(void) {
    printf("=== DF-0524: ng_fec stored raw ifnet pointer -> UAF ===\n\n");

    struct fec_bundle bundle;
    memset(&bundle, 0, sizeof(bundle));

    /* Create a virtual interface */
    struct ifnet *ifp = calloc(1, sizeof(*ifp));
    ifp->if_index = 5;
    strcpy(ifp->if_name, "vtnet0");
    ifp->if_flags = 0x88; /* IFF_UP | IFF_RUNNING */
    ifp->alive = 1;

    /* Add port to fec bundle (stores raw pointer, no refcount) */
    printf("[1] Adding port to fec bundle:\n");
    fec_addport(&bundle, ifp);

    /* Detach the interface โ€” fec still holds the dangling pointer */
    printf("\n[2] Interface detached (e.g. kldunload driver, if_destroy):\n");
    detach_ifnet(ifp);

    /* Simulate slab reuse: another allocation overwrites the freed ifnet */
    memset(ifp, 0x41, sizeof(*ifp)); /* simulate reuse with garbage */

    /* fec tick fires โ€” dereferences the freed/reused memory */
    printf("\n[3] fec tick fires โ€” dereferences fec_if:\n");
    int result = fec_tick(&bundle);

    if (result != 0) {
        printf("\n  BUG CONFIRMED: UAF on fec_if after interface detach.\n");
        printf("  No if_ref()/if_rele() in ng_fec.c.\n");
        printf("  No EVENTHANDLER ifnet_detach_notification registered.\n");
        printf("  Fix: take if_ref() on store, if_rele() on delport/rmnode.\n");
    }

    free(ifp);
    return result != 0 ? 0 : 1;
}