DF-5787 / biopoison.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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | /* * biopoison.c - DF-5787 KLD rig * * Demonstrates that vfs_bio.c buffer-cache recycling does NOT clear * b_bio1.bio_flags: a buffer released with a stale BIO_DONE flag carries * that flag into its next life, and the very next bread() on the same * (vp, loffset) trips vn_strategy()'s KKASSERT((bio->bio_flags & * BIO_DONE) == 0) (sys/kern/vfs_bio.c:3385) - or, on a kernel without * that assertion, makes biowait() return before the device I/O completes * (torn data + brelse() while DMA is in flight). * * The poison step emulates exactly what breadcb()'s B_CACHE fast path * does (sys/kern/vfs_bio.c:883 sets BIO_DONE and relies on the callback * to clear it) minus the callback's cleanup - i.e. any forgetful/abandoning * completion path. The kernel provides no defense at recycling time * (getnewbuf() resets b_flags only, vfs_bio.c:2095; reinitbufbio() only * clears bio_done/bio_offset, vfs_bio.c:743-751). * * Case 0 (control): same manipulation but WITHOUT the stale flag. * Case 1 (poison): stale BIO_DONE set before bqrelse(). */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/types.h> #include <sys/errno.h> #include <sys/buf.h> #include <sys/bio.h> #include <sys/vnode.h> #include <sys/nlookup.h> #include <sys/namecache.h> #include <sys/ucred.h> #include <sys/proc.h> #include <sys/buf2.h> #define BPIO_BLKSIZE 16384 #define BPIO_LOFF 0 static struct vnode *bpio_vp; static int bpio_getvp(const char *path) { struct nlookupdata nd; struct vnode *vp = NULL; int error; error = nlookup_init(&nd, path, UIO_SYSSPACE, NLC_FOLLOW); if (error == 0) error = nlookup(&nd); if (error == 0) error = cache_vref(&nd.nl_nch, curthread->td_ucred, &vp); nlookup_done(&nd); if (error == 0) bpio_vp = vp; return (error); } static void bpio_case(int poison) { struct buf *bp; int error; bp = getblk(bpio_vp, BPIO_LOFF, BPIO_BLKSIZE, 0, 0); kprintf("biopoison[%d]: getblk -> bp=%p b_flags=%08x " "bio1.flags=%08x B_CACHE=%d\n", poison, bp, bp->b_flags, bp->b_bio1.bio_flags, !!(bp->b_flags & B_CACHE)); /* * Force the next bread() to issue real I/O, and (poison case) * leave a stale BIO_DONE behind, emulating a completion callback * that forgot to clear it (breadcb()/cluster_readcb() contract). */ bp->b_flags &= ~B_CACHE; if (poison) bp->b_bio1.bio_flags |= BIO_DONE; bqrelse(bp); kprintf("biopoison[%d]: released (stale BIO_DONE=%d), " "issuing bread()\n", poison, poison); error = bread(bpio_vp, BPIO_LOFF, BPIO_BLKSIZE, &bp); kprintf("biopoison[%d]: bread returned %d (no panic)\n", poison, error); if (error == 0) brelse(bp); } static int biopoison_handler(struct module *m, int what, void *arg) { int error = 0; switch (what) { case MOD_LOAD: error = bpio_getvp("/etc/fstab"); if (error) { kprintf("biopoison: cannot resolve vp: %d\n", error); return (error); } kprintf("biopoison: vp=%p v_type=%d\n", bpio_vp, bpio_vp->v_type); vn_lock(bpio_vp, LK_SHARED | LK_RETRY); kprintf("biopoison: === control run (no stale flag) ===\n"); bpio_case(0); kprintf("biopoison: === poisoned run (stale BIO_DONE across " "bqrelse -> getblk recycling) ===\n"); bpio_case(1); kprintf("biopoison: bread SURVIVED - stale flag was cleared " "somewhere during recycling (finding refuted)\n"); vn_unlock(bpio_vp); vrele(bpio_vp); break; case MOD_UNLOAD: break; default: error = EOPNOTSUPP; break; } return (error); } static moduledata_t biopoison_mod = { "biopoison", biopoison_handler, NULL }; DECLARE_MODULE(biopoison, biopoison_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_VERSION(biopoison, 1); |