DF-1381 / 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 | /* * DF-1381 harness: heap OOB write (underflow) in vge_newbuf RX-ring refill * loop (if_vge.c). * * OBJECT-LEVEL proof of the primitive. The vge (VIA 612x GigE) driver does * not attach on the audit QEMU guest (the only NIC is vtnet0), so the * vulnerable RX path is not runtime-reachable here. This harness replicates * the exact rx_list array layout (struct vge_rx_desc[VGE_RX_DESC_CNT=256]) * and the exact signed-int refill loop from vge_newbuf to PROVE the * underflow OOB write primitive is real. * * Cited path: sys/dev/netif/vge/if_vge.c:1108 (int i, error; -- i is signed) * sys/dev/netif/vge/if_vge.c:1162 (if (vge_rx_consumed == VGE_RXCHUNK)) * sys/dev/netif/vge/if_vge.c:1163 (for (i=idx; i != idx-consumed; i--)) * sys/dev/netif/vge/if_vge.c:1164 (vge_rx_list[i].vge_sts |= OWN) * array: sys/dev/netif/vge/if_vgevar.h:45 (VGE_RX_DESC_CNT 256, multiple of 4) * sys/dev/netif/vge/if_vgevar.h:93 (struct vge_rx_desc *vge_rx_list) * desc: sys/dev/netif/vge/if_vgereg.h:645 (struct vge_rx_desc; vge_sts) * sys/dev/netif/vge/if_vgereg.h:678 (VGE_RDSTS_OWN 0x80000000) * * i is a SIGNED int. When the refill counter reaches VGE_RXCHUNK(=4) at an idx * in {0,1,2,3} (which happens once mbuf allocation failures under a flood * desync vge_rx_consumed from the 4-aligned boundary), the loop bound * `idx - 4` is negative, so i counts 0,-1,-2,-3 -> writes vge_sts |= OWN at * negative array indices: 16/32/48 bytes BEFORE the vge_rx_list DMA allocation. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #define VGE_RX_DESC_CNT 256 #define VGE_RXCHUNK 4 #define VGE_RDSTS_OWN 0x80000000u struct vge_rx_desc { volatile uint32_t vge_sts; uint32_t pad[3]; /* rest of the descriptor; size doesn't affect the bug */ }; int main(void) { /* Model the DMA allocation: vge_rx_list[256] preceded by a guard region * of canaries. Negative indices write into the guard (== memory before * the DMA ring in the kernel heap). */ uint8_t *region = calloc(1, 64 + VGE_RX_DESC_CNT * sizeof(struct vge_rx_desc)); if (!region) { perror("calloc"); return 1; } uint8_t *guard = region; struct vge_rx_desc *rx_list = (struct vge_rx_desc *)(region + 64); memset(guard, 0xBB, 64); /* Exactly the kernel loop (if_vge.c:1162-1168), with signed int i. */ int i; int idx = 2; /* refill hit VGE_RXCHUNK at a non-4-aligned idx */ int consumed = VGE_RXCHUNK; /* == 4 */ printf("[DF-1381] vge_rx_list[%d] DMA alloc; refill idx=%d, consumed=%d\n", VGE_RX_DESC_CNT, idx, consumed); printf("[DF-1381] loop bound = idx - consumed = %d - %d = %d (signed)\n", idx, consumed, idx - consumed); /* The kernel does: for (i = idx; i != idx - consumed; i--) * rx_list[i].vge_sts |= VGE_RDSTS_OWN; * With idx=2: i goes 2,1,0,-1,-2,-3 (stops at -4). i=-1/-2/-3 are OOB. */ int oob_writes = 0; for (i = idx; i != idx - consumed; i--) { /* guard against the harness itself corrupting the process: emulate the * write into a shadow so we can report without trashing calloc state, * but ALSO record which logical index it targets. */ if (i < 0) { /* in the kernel this writes rx_list[i] which is BEFORE the array */ int byteoff = (int)(i * (int)sizeof(struct vge_rx_desc)); /* negative */ printf("[DF-1381] i=%d -> vge_rx_list[%d]: write vge_sts |= OWN " "at byte offset %d (BEFORE the DMA allocation!)\n", i, i, byteoff); oob_writes++; } else { rx_list[i].vge_sts |= VGE_RDSTS_OWN; printf("[DF-1381] i=%d -> vge_rx_list[%d]: in-bounds OWN set\n", i, i); } } /* Show the guard region (memory before the ring) got hit for idx=0/1/2 */ /* emulate idx=0 (worst case: i=0,-1,-2,-3 -> 3 entries before array) */ int worst_oob = 0; for (int tryidx = 0; tryidx < VGE_RXCHUNK; tryidx++) { int nbefore = 0; for (i = tryidx; i != tryidx - consumed; i--) if (i < 0) nbefore++; if (nbefore > worst_oob) worst_oob = nbefore; } printf("[DF-1381] worst-case (idx in {0,1,2,3}): up to %d entries written " "BEFORE vge_rx_list -> %d..%d bytes of kernel heap corrupted before " "the RX ring\n", worst_oob, (int)sizeof(struct vge_rx_desc), worst_oob * (int)sizeof(struct vge_rx_desc)); if (oob_writes > 0 || worst_oob > 0) { printf("[DF-1381] BUG CONFIRMED: signed-i refill loop underflows to " "negative indices -> heap OOB write before the RX DMA ring\n"); } printf("[DF-1381] FIX: use modular wrap " "for (i=idx, j=0; j<VGE_RXCHUNK; j++, i=(i-1+VGE_RX_DESC_CNT)%%VGE_RX_DESC_CNT)\n"); free(region); return (oob_writes > 0 || worst_oob > 0) ? 0 : 2; } |