DF-0866 / leak_dirent.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 | /* * DF-0866 trigger: getdirentries() on a mounted cd9660 image, * inspect every returned dirent's d_unused1 (offset 11, 1B) and * d_unused2 (offset 12-15, 4B) and the d_name trailing padding * for leaked (non-zero) kernel heap bytes. * * Build: cc -O2 -D_BSD_VISIBLE -o leak_dirent leak_dirent.c * Run: ./leak_dirent <mounted-iso-dir> * * Exit 0 if NO leak, exit 1 if leak detected (non-zero bytes in * d_unused1, d_unused2, or trailing d_name padding). */ #define _BSD_VISIBLE 1 #define _XOPEN_SOURCE 700 #include <sys/types.h> #include <sys/dirent.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> /* getdirentries is in <dirent.h> when _BSD_VISIBLE, but the kernel * syscall returns the raw dirent stream. Declare it explicitly to be safe. */ ssize_t getdirentries(int, char *, size_t, off_t *); /* * DragonFly struct dirent layout (kernel == user, no d_reclen): * ino_t d_ino; // off 0, 8 bytes * __uint16_t d_namlen; // off 8, 2 bytes * __uint8_t d_type; // off 10, 1 byte * __uint8_t d_unused1; // off 11, 1 byte (UNINIT in cd9660) * __uint32_t d_unused2; // off 12, 4 bytes (UNINIT in cd9660) * char d_name[256]; // off 16 * * Record length (size of bytes actually written for this entry) is * _DIRENT_DIRSIZ(dp) = round_up(offsetof(d_name) + d_namlen + 1, 8). */ #define OFF_UNUSED1 11 #define OFF_UNUSED2 12 #define OFF_DNAME 16 /* Same formula as the kernel's _DIRENT_DIRSIZ: * (__offsetof(struct dirent, d_name) + d_namlen + 1 + 7) & ~7 * which simplifies to (16 + d_namlen + 1 + 7) & ~7 = (d_namlen + 24) & ~7 */ #define OUR_DIRSIZ(d) (((OFF_DNAME + (d)->d_namlen + 1) + 7) & ~7) static void hexdump(const unsigned char *p, size_t n) { for (size_t i = 0; i < n; i++) printf("%02x", p[i]); } int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "usage: %s <mounted-iso-dir>\n", argv[0]); return 2; } int fd = open(argv[1], O_RDONLY); if (fd < 0) { perror("open"); return 2; } char buf[8192]; long total_leak_bytes = 0; long total_entries = 0; int run = 0; off_t base = 0; for (;;) { ssize_t n = getdirentries(fd, buf, sizeof(buf), &base); if (n < 0) { perror("getdirentries"); return 2; } if (n == 0) break; run++; char *p = buf; while (p < buf + n) { struct dirent *d = (struct dirent *)p; /* Record length: name area rounded up to 8 bytes. */ unsigned reclen = OUR_DIRSIZ(d); if (reclen < OFF_DNAME + 1) break; /* safety */ total_entries++; unsigned char *raw = (unsigned char *)d; int leak_unused1 = raw[OFF_UNUSED1] != 0 ? 1 : 0; int leak_unused2 = 0; for (int i = 0; i < 4; i++) if (raw[OFF_UNUSED2 + i] != 0) leak_unused2++; /* d_name trailing padding: [d_namlen+1 .. reclen-1] */ int name_end = OFF_DNAME + d->d_namlen + 1; int leak_namepad = 0; for (int i = name_end; i < (int)reclen; i++) if (raw[i] != 0) leak_namepad++; int leak_total = leak_unused1 + leak_unused2 + leak_namepad; total_leak_bytes += leak_total; printf("[run %d] entry %3ld reclen=%3u namlen=%u type=%u name='%s'\n", run, total_entries, reclen, d->d_namlen, d->d_type, d->d_name); printf(" bytes[0..%u]: ", reclen - 1); hexdump(raw, reclen); printf("\n"); printf(" d_unused1=%02x d_unused2=", raw[OFF_UNUSED1]); hexdump(raw + OFF_UNUSED2, 4); printf(" leak(unused1=%d unused2=%d namepad=%d)=%d\n", leak_unused1, leak_unused2, leak_namepad, leak_total); p += reclen; if ((size_t)(p - buf) > (size_t)n) break; } } close(fd); printf("\n=== SUMMARY ===\n"); printf("entries read: %ld\n", total_entries); printf("non-zero leaked bytes (d_unused1+d_unused2+d_name padding): %ld\n", total_leak_bytes); return (total_leak_bytes > 0) ? 1 : 0; } |