โฌข DragonFlyBSD Kernel Audit
DF-1912 / harness.c
โ† back to finding โ†“ download raw
/*
 * 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;
}