DF-4929 / uafsim.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 | /* * DF-4929 reproduction module. * * Reuses the STOCK sys/dev/crypto/tpm/tpm20.c (verbatim #include below) so * that tpm20_write / tpm20_release / tpm20_discard_buffer / the discard * callout arming / the dev_lock protocol are the real shipped code. * Only the TPM MMIO transport is stubbed: fake_transmit() mimics * tpmcrb_transmit()'s timing (tsleep poll loop while holding dev_lock, * tpm_crb.c:254-260,377) and its epilogue (sc->pending_data_length = N, * tpm_crb.c:402). * * The "detach" sysctl performs EXACTLY what tpmcrb_detach (tpm_crb.c:230-243) * + newbus do on kldunload(8): * tpm20_release(sc) - no dev_lock taken, no draining * kfree(sc, M_DEVBUF) - device_set_driver() frees the softc * heap spray - ordinary concurrent kernel allocation * while a writer thread is in the middle of tpm20_write() -> transmit with * dev_lock held exclusively. */ #include <sys/module.h> #include <sys/sysctl.h> #include <sys/sysproto.h> /* for SYSCTL_HANDLER_ARGS glue on DF */ #include "tpm20.c" /* STOCK, verbatim */ /* same shape/size as struct tpmcrb_sc in tpm_crb.c:79-85 */ struct sim_crb_sc { struct tpm_sc base; bus_size_t cmd_off; bus_size_t rsp_off; size_t cmd_buf_size; size_t rsp_buf_size; }; static struct sim_crb_sc *gsc; static struct sysctl_ctx_list ctx; static int fake_transmit(struct tpm_sc *sc, size_t length) { int i; /* * tpmcrb_transmit polls with dev_lock held (tpm_crb.c:254-260,377); * mimic a few seconds of that (a real CreatePrimary polls up to 40s). */ for (i = 0; i < 100; i++) { tsleep(fake_transmit, 0, "uafsim", 5); /* ~5 ticks x hz */ } /* tpm_crb.c:402 epilogue */ sc->pending_data_length = length; return (0); } /* * sysctl hw.uafsim.detach=1 == `kldunload tpm` racing an in-flight write. */ static int uafsim_detach_sysctl(SYSCTL_HANDLER_ARGS) { int v = 0, error; int i; void **spray; error = sysctl_handle_opaque(oidp, &v, sizeof(v), req); if (error != 0 || req->newptr == NULL) return (error); kprintf("uafsim: [detach] lockstatus(dev_lock)=%d (1 == LK_EXCLUSIVE held by writer)\n", lockstatus(&gsc->base.dev_lock, curthread)); kprintf("uafsim: [detach] calling stock tpm20_release() with dev_lock HELD elsewhere\n"); tpm20_release(&gsc->base); /* stock: kfree(buf), lockuninit, cv_destroy, destroy_dev */ kprintf("uafsim: [detach] tpm20_release() returned; lockuninit assert did NOT fire\n"); /* newbus device_set_driver() frees the softc after detach */ kfree(gsc, M_DEVBUF); gsc = NULL; /* ordinary kernel heap traffic reclaims the freed softc */ spray = kmalloc(sizeof(void *) * 128, M_TEMP, M_WAITOK); for (i = 0; i < 128; i++) { spray[i] = kmalloc(sizeof(struct sim_crb_sc), M_DEVBUF, M_WAITOK); memset(spray[i], 0x41, sizeof(struct sim_crb_sc)); } kprintf("uafsim: [detach] softc freed + sprayed; writer still asleep inside transmit\n"); /* deliberately leak spray; module will die with the kernel anyway */ kfree(spray, M_TEMP); return (0); } static int uafsim_modevent(module_t mod, int type, void *arg) { struct tpm_sc *sc; switch (type) { case MOD_LOAD: gsc = kmalloc(sizeof(struct sim_crb_sc), M_DEVBUF, M_WAITOK | M_ZERO); sc = &gsc->base; /* verbatim tpm20_init (tpm20.c:190-207) minus device_get_unit */ sc->buf = kmalloc(TPM_BUFSIZE, M_TPM20, M_WAITOK); lockinit(&sc->dev_lock, "TPM driver lock", 0, LK_CANRECURSE); cv_init(&sc->buf_cv, "TPM buffer cv"); callout_init_mp(&sc->discard_buffer_callout); sc->pending_data_length = 0; sc->sc_cdev = make_dev(&tpm20_ops, 0, UID_ROOT, GID_WHEEL, 0600, "uafsim0"); sc->sc_cdev->si_drv1 = sc; sc->transmit = fake_transmit; sysctl_ctx_init(&ctx); SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "uafsim_detach", CTLTYPE_INT | CTLFLAG_RW, 0, 0, uafsim_detach_sysctl, "I", "perform the tpmcrb_detach race"); kprintf("uafsim: loaded; write >=10 bytes to /dev/uafsim0 then sysctl hw.uafsim_detach=1\n"); return (0); case MOD_UNLOAD: return (EBUSY); } return (0); } DEV_MODULE(uafsim, uafsim_modevent, NULL); |