DF-1805 / harness.c
/* * 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; } |