DragonFlyBSD Kernel Audit
DF-1805 / harness.c
← back to finding ↓ download raw
/*
 * DF-1805 source-confirmation harness (bktr_filter_detach calls knote_INSERT
 * instead of knote_REMOVE).
 *
 * This is a plain typo at sys/dev/video/bktr/bktr_os.c:731 inside
 * bktr_filter_detach().  Every other *_filter_detach in the tree calls
 * knote_remove (cyapa, cxm, atmel_mxt, ipmi, drm_file, psm, ...).  The
 * harness below is a SLIST model showing that knote_insert on a node
 * that knote_drop will independently free leaves a dangling head pointer
 * (and a self-referential cycle when re-inserted), exactly as described
 * in the finding.  No live trigger is possible on this guest (needs
 * Bt848/Bt878 PCI card); the source line is self-evidently wrong.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>

struct knote { struct knote *kn_next; int alive; };

/* SLIST_INSERT_HEAD semantics */
static void knote_insert(struct knote **head, struct knote *kn) {
    kn->kn_next = *head;
    *head = kn;
}
/* SLIST_REMOVE_HEAD semantics — what knote_remove reduces to for the head */
static void knote_remove(struct knote **head, struct knote *kn) {
    if (*head == kn) { *head = kn->kn_next; kn->kn_next = NULL; return; }
    /* (full search omitted — bktr only ever inserts at head) */
}

/* knote_drop frees the knote but does NOT touch kn_next (it removes
 * kn_link / kn_kqlink, never the SLIST the driver owns). */
static void knote_drop(struct knote *kn) { kn->alive = 0; /* free(kn) */ }

int main(void) {
    struct knote *head = NULL;
    struct knote *kn = calloc(1, sizeof(*kn)); kn->alive = 1;

    /* filter_attach adds it: */
    knote_insert(&head, kn);
    printf("after attach: head=%p kn=%p alive=%d\n", (void*)head, (void*)kn, kn->alive);

    /* BUGGY filter_detach (bktr_os.c:731): knote_insert AGAIN instead of remove */
    knote_insert(&head, kn);           /* <- BUG */
    printf("after buggy detach (knote_insert): head=%p kn=%p kn->kn_next=%p "
           "(self-cycle=%d)\n", (void*)head, (void*)kn, (void*)kn->kn_next,
           kn->kn_next == kn);

    /* userspace closes the fd -> knote_drop frees kn, but head still points at it */
    knote_drop(kn);
    printf("after knote_drop: head=%p -> freed kn (alive=%d) = DANGLING\n",
           (void*)head, kn->alive);

    /* FIXED detach would have removed it first */
    struct knote *h2 = NULL; struct knote *k2 = calloc(1,sizeof(*k2)); k2->alive=1;
    knote_insert(&h2, k2);
    knote_remove(&h2, k2);             /* <- FIXED */
    knote_drop(k2);
    printf("after FIXED detach+drop: head=%p (clean)\n", (void*)h2);

    free(kn); free(k2);
    return 0;
}