DragonFlyBSD Kernel Audit
DF-1381 / harness.c
← back to finding ↓ download raw
/*
 * 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;
}