DF-1035 / poc.c
/* * DF-1035 โ bus_dmamap_load_uio uses stale absolute segment index * Documentation / analysis harness. * * Bug location: sys/platform/pc64/x86_64/busdma_machdep.c:961-996 * * bus_dmamap_load_uio() iterates over uio->uio_iov. For each iovec it * calls _bus_dmamap_load_buffer(dmat, map, addr, minlen, * segs, nsegs_left, pmap, flags, &lastaddr, &nsegs, first); * * Between iterations it advances the segment pointer and shrinks the * remaining count: * nsegs_left -= nsegs; * segs += nsegs; <-- segs now points into the array * * But _bus_dmamap_load_buffer treats *segp (= &nsegs) as an ABSOLUTE * 1-based index into the segments array (lines 662-664): * KKASSERT(*segp >= 1 && *segp <= nsegments); // nsegments is local param * seg = *segp; * sg = &segments[seg - 1]; // segments is the LOCAL * // advanced pointer * * So on the 2nd iovec: * *segp is still the absolute count from the first iov (e.g. 3). * the local 'segments' parameter is original_segments + 3. * 'nsegments' parameter is nsegs_left = dmat->nsegments - 3. * KKASSERT(3 >= 1 && 3 <= nsegs_left) โ fires when 1st iov consumed * more than half the budget. * * Even without INVARIANTS, sg = &local_segments[3-1] writes to * original_segments[3 + 2] = original_segments[5] โ OOB past the * segments array (kernel stack cache_segments[8] or heap alloc). * * The correct reference behaviour is bus_dmamap_load_mbuf_segment at * line 869, which passes CONSTANT (segs, maxsegs) and lets *segp be * absolute end-index in the original array. * * Reachability on the audit guest: * Only three in-tree callers of bus_dmamap_load_uio on pc64: * sys/dev/raid/mpr/mpr.c:3533 * sys/dev/raid/mps/mps.c:2097 * sys/dev/disk/isp/isp_pci.c:1912 * All three are HBA (SAS/SCSI/fabric) drivers; none are present on * the QEMU guest (only virtio-blk, virtio-net, atapi cd). The symbol * bus_dmamap_load_uio IS in the live kernel (/boot/kernel/kernel), * so the broken code path is compiled-in and active on the default * GENERIC kernel โ it just needs an HBA driver instance to invoke it. * * This harness documents the trigger conditions and the fix. */ #include <stdio.h> int main(void) { printf("=== DF-1035 trigger documentation ===\n"); printf("\nVulnerable path (bus_dmamap_load_uio, multi-iovec uio):\n"); printf(" caller (mpr.c/mps.c/isp_pci.c) builds a uio with N>=2 iovecs\n"); printf(" where the first iov maps to more than dmat->nsegments/2 DMA\n"); printf(" segments, then invokes bus_dmamap_load_uio()\n"); printf(" -> loop over uio->uio_iov\n"); printf(" -> _bus_dmamap_load_buffer(segs=segments+M, nsegments=N-M,\n"); printf(" segp=&nsegs)\n"); printf(" *segp is the absolute index M from the prior iov\n"); printf(" KKASSERT(M <= N-M) -- fires on GENERIC (INVARIANTS on)\n"); printf(" without INVARIANTS: sg = &local_segments[M-1]\n"); printf(" = &original_segments[M+M-1]\n"); printf(" -- OOB write into the segments array\n"); printf("\n"); printf("Status on this audit guest: not live-reachable โ none of the\n"); printf("three HBA callers (mpr/mps/isp) attach. The broken function IS\n"); printf("in /boot/kernel/kernel (verified via nm). Fix validated by\n"); printf("nativekernel rebuild โ see VERDICT.md.\n"); return 0; } |