DF-2532 / df2532_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 | /* * DF-2532 harness module: includes the REAL xdisk.c (so xa_start / xa_done * are the shipping code) and adds a sysctl trigger that constructs the * exact impossible-state (tag->bio set, empty spanq, B_FAILONDIS) and calls * xa_start(tag, NULL, 1). On INVARIANTS kernels this fires the * KKASSERT(tag->bio==NULL) at xdisk.c:1009 and panics. * * This proves the code path in the shipping xdisk.c is reachable and the * KKASSERT fires โ it is NOT an escalation module, just a harness that * exercises the exact same static functions with the exact same struct * layouts the real driver uses. * * Build: see Makefile (kernel module build, needs /usr/src/sys) * Load: kldload ./df2532_harness.ko (xisk.ko must NOT be loaded) * Trigger: sysctl -w debug.df2532_trigger=1 */ /* Ensure INVARIANTS is defined so KKASSERT compiles to a real panic(). * In a normal kernel build opt_global.h provides this; standalone kld * module builds may miss it. */ #ifndef INVARIANTS #define INVARIANTS 1 #endif /* Pull in all of xdisk.c โ its static functions, structs, globals, and * its DEV_MODULE(xdisk, ...) entry. We must NOT also load xdisk.ko. */ #include "xdisk.c" #include <sys/sysctl.h> /* * Trigger: construct the impossible state and call the real xa_start. * We allocate a minimal but valid xa_softc, a tag with bio set, and a * buf with B_FAILONDIS โ exactly what the disk-framework label-probe * path produces when no spans are configured. */ static int df2532_trigger(SYSCTL_HANDLER_ARGS) { int error, val = 0; error = sysctl_handle_int(oidp, &val, 0, req); if (error || req->newptr == NULL) return error; if (val == 0) return 0; xa_softc_t *sc; xa_tag_t *tag; struct bio *bio; struct buf *bp; kprintf("DF-2532: constructing impossible state\n"); sc = kmalloc(sizeof(*sc), M_XDISK, M_WAITOK | M_ZERO); sc->unit = 99; sc->opencnt = 1; sc->spancnt = 0; /* NO spans */ sc->open_tag = NULL; lockinit(&sc->lk, "df2532", 0, 0); TAILQ_INIT(&sc->spanq); /* empty */ TAILQ_INIT(&sc->bioq); TAILQ_INIT(&sc->tag_freeq); TAILQ_INIT(&sc->tag_pendq); tag = kmalloc(sizeof(*tag), M_XDISK, M_WAITOK | M_ZERO); tag->sc = sc; tag->async = 0; /* don't call xa_release (which does biodone on fake buf) */ TAILQ_INSERT_TAIL(&sc->tag_pendq, tag, entry); /* fabricate a bio + buf exactly like the disk-framework probe path */ bio = kmalloc(sizeof(*bio), M_XDISK, M_WAITOK | M_ZERO); bp = kmalloc(sizeof(*bp), M_XDISK, M_WAITOK | M_ZERO); bp->b_cmd = BUF_CMD_READ; bp->b_flags = B_FAILONDIS; /* the critical flag */ bp->b_bcount = 512; bp->b_data = kmalloc(512, M_XDISK, M_WAITOK | M_ZERO); bio->bio_buf = bp; bio->bio_offset = 0; tag->bio = bio; /* tag->bio IS SET โ the bug */ kprintf("DF-2532: calling xa_start(tag, NULL, 1) with empty spanq, " "B_FAILONDIS set, tag->bio=%p\n", tag->bio); kprintf("DF-2532: xa_start will find no spans (trans=NULL) -> goto skip\n"); kprintf("DF-2532: B_FAILONDIS set -> else branch -> xa_done(tag,1)\n"); kprintf("DF-2532: xa_done: with fix, KKASSERT is gone; xa_release " "handles tag->bio\n"); lockmgr(&sc->lk, LK_EXCLUSIVE); /* Use async=0 for the harness so xa_done->xa_release->biodone is not * called on our synthetic buf (which lacks a proper buf lock). * The real driver uses async=1 where xa_release correctly completes * the bio with EIO. */ xa_start(tag, NULL, 0); /* async=0: xa_done won't call xa_release */ lockmgr(&sc->lk, LK_RELEASE); kprintf("DF-2532: survived (INVARIANTS off?)\n"); return 0; } static SYSCTL_NODE(_debug, OID_AUTO, df2532_trigger_node, CTLFLAG_RD, 0, "DF-2532"); SYSCTL_PROC(_debug, OID_AUTO, df2532_trigger, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, 0, 0, df2532_trigger, "I", "Write 1 to trigger the DF-2532 xa_start no-spans KKASSERT"); |