DF-1912 / harness.c
/* * DF-1912 source-confirmation harness (mdstrategy_preload stale bp across biodone). * * sys/dev/disk/md/md.c:344-400 mdstrategy_preload: * bp = bio->bio_buf; // line 349 โ assigned ONCE * while (1) { * bio = bioq_takefirst(&sc->bio_queue); // line 372 โ NEW bio, bp STALE * crit_exit(); // line 373 โ window for re-entry * if (bio == NULL) break; * switch (bp->b_cmd) { // line 379 โ STALE bp->b_cmd * case BUF_CMD_READ: * bcopy(sc->pl_ptr + bio->bio_offset, // line 383 โ NEW bio_offset * bp->b_data, bp->b_bcount); // STALE bp->b_data/b_bcount * break; * ... * } * biodone(bio); // line 395 โ may free the buf * crit_enter(); // line 396 * } * * From the 2nd iteration on, bp refers to the PREVIOUS bio's buf (already * handed to biodone, which may have freed it). The NEW bio's bio_offset * is paired with the STALE bp's b_data/b_bcount โ mismatched length, * offset, and possibly a freed bp. Sibling mdstrategy_malloc correctly * refreshes bp = bio->bio_buf at md.c:239. * * Live trigger needs MD_ROOT with md0 mounted as root (not in X86_64_GENERIC; * md0 is root:operator 0640 โ not accessible to maxx). Harness shows the * stale-bp mismatch with two synthetic bios. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <string.h> struct buf { int b_cmd; long b_bcount; unsigned char *b_data; }; struct bio { long bio_offset; struct buf *bio_buf; }; #define BUF_CMD_READ 1 #define pl_len (256) /* model sc->pl_len */ int main(void) { unsigned char pl_ptr[pl_len]; memset(pl_ptr, 0x55, sizeof(pl_ptr)); /* Two bios with DIFFERENT buf/bio_offset pairs. */ unsigned char data1[16], data2[200]; struct buf b1 = { BUF_CMD_READ, 16, data1 }; struct buf b2 = { BUF_CMD_READ, 200, data2 }; struct bio bioq[2] = { {0, &b1}, {128, &b2} }; /* Verbatim control flow from md.c:348-397 (stale-bp version) */ struct bio *bio = &bioq[0]; struct buf *bp = bio->bio_buf; /* line 349 โ assigned once */ int qi = 0; int bad = 0; while (1) { /* line 371 */ bio = &bioq[qi++]; /* line 372 bioq_takefirst */ if (qi > 2) break; /* bio == NULL sentinel */ /* switch (bp->b_cmd) โ STALE bp from line 349 */ if (bp->b_cmd == BUF_CMD_READ) { /* bcopy(pl_ptr + bio->bio_offset, bp->b_data, bp->b_bcount) * bio_offset is NEW, bp->b_data/b_bcount are STALE */ long off = bio->bio_offset; long len = bp->b_bcount; if (off + len > pl_len) bad++; printf(" iter %d: bio_offset=%ld bp->b_bcount=%ld (from buf#%d) " "-> off+len=%ld %s\n", qi, off, len, (qi==1)?1:1, off+len, off+len>pl_len ? "OOB on pl_ptr" : "in-bounds"); } /* biodone(bio); may free bp's buf (if same buf). In the 2nd iter * bp is still &b1 (the FIRST buf), but bio is the SECOND bio. */ } printf("DF-1912: mdstrategy_preload (md.c:344-400)\n"); printf(" bp assigned ONCE at line 349, never refreshed inside the loop.\n"); printf(" From iter 2 on: bio->bio_offset is NEW but bp->b_data/b_bcount " "are STALE -> mismatched OOB / wrong-bytes / UAF after biodone frees buf.\n"); printf(" Detected %d iterations where stale bp caused OOB on pl_ptr.\n", bad); printf(" Compare mdstrategy_malloc (md.c:239) which DOES refresh bp=bio->bio_buf.\n"); return 0; } |