DF-4529 / fix.diff
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 | --- a/sys/dev/disk/nvme/nvme_attach.c 2026-09-21 04:58:29.815217063 +0000 +++ b/sys/dev/disk/nvme/nvme_attach.c 2026-09-21 04:58:58.746843828 +0000 @@ -112,6 +112,7 @@ #endif sc->dev = dev; + sc->flags &= ~NVME_SC_UNLOADING; /* * Map the register window @@ -473,6 +474,22 @@ int i; /* + * Block new NVMEIOCGETLOG ioctls and drain any in-flight ones + * before the admin queue is torn down. Every getlog ioctl holds + * ioctl_lk across its entire get_request -> submit -> wait -> + * put_request sequence, so once we hold ioctl_lk exclusively no + * user thread is inside the admin request machinery. + * + * Without this, nvme_free_subqueue(0) kfree()s reqary and bzero()s + * the queue (nqe = 0) while an ioctl can still be inside + * nvme_get_request(), whose ring-full check computes + * (tail + unsubmitted + 1) % nqe -> integer divide-by-zero panic + * (observed: nvme_get_request+0x34 divl 0x40(%r13),%eax). + */ + sc->flags |= NVME_SC_UNLOADING; + lockmgr(&sc->ioctl_lk, LK_EXCLUSIVE); + + /* * Stop the admin thread */ nvme_stop_admin_thread(sc); @@ -492,6 +509,7 @@ */ nvme_free_subqueue(sc, 0); nvme_free_comqueue(sc, 0); + lockmgr(&sc->ioctl_lk, LK_RELEASE); /* * Release related resources. --- a/sys/dev/disk/nvme/nvme_ioctl.c 2026-09-21 04:58:29.815217063 +0000 +++ b/sys/dev/disk/nvme/nvme_ioctl.c 2026-09-21 04:58:40.347081192 +0000 @@ -46,6 +46,16 @@ return EINVAL; lockmgr(&sc->ioctl_lk, LK_EXCLUSIVE); + + /* + * Serialize against device teardown: detach sets NVME_SC_UNLOADING + * and then holds this lock across the admin queue teardown, so we + * never touch a freed/bzero'd queue. + */ + if (sc->flags & NVME_SC_UNLOADING) { + lockmgr(&sc->ioctl_lk, LK_RELEASE); + return ENXIO; + } req = nvme_get_admin_request(sc, NVME_OP_GET_LOG_PAGE); req->cmd.head.nsid = -1; req->cmd.getlog.lid = ioc->lid; --- a/sys/dev/disk/nvme/nvme_admin.c +++ b/sys/dev/disk/nvme/nvme_admin.c 2026-09-21 04:58:40.355081088 +0000 @@ -119,7 +119,13 @@ if ((nsc = sc->nscary[i]) != NULL) { nvme_disk_detach(nsc); - + /* + * The cdev may still be referenced by open descriptors + * after disk_destroy(); make any late dispatch (which + * reads nsc->sc through si_drv1) fail cleanly instead + * of touching freed memory. + */ + nsc->sc = NULL; kfree(nsc, M_NVME); sc->nscary[i] = NULL; } --- a/sys/dev/disk/nvme/nvme_disk.c 2026-09-21 04:58:29.815217063 +0000 +++ b/sys/dev/disk/nvme/nvme_disk.c 2026-09-21 05:53:26.808935656 +0000 @@ -116,6 +116,13 @@ nvme_disk_detach(nvme_softns_t *nsc) { if (nsc->cdev) { + /* + * devfs keeps dispatching through open + * descriptors/nodes until the cdev is fully + * released; fail them cleanly instead of + * dereferencing a freed softns. + */ + nsc->cdev->si_drv1 = NULL; disk_destroy(&nsc->disk); devstat_remove_entry(&nsc->stats); } @@ -127,7 +134,11 @@ { cdev_t dev = ap->a_head.a_dev; nvme_softns_t *nsc = dev->si_drv1; - nvme_softc_t *sc = nsc->sc; + nvme_softc_t *sc; + + if (nsc == NULL || nsc->sc == NULL) + return ENXIO; + sc = nsc->sc; if (sc->flags & NVME_SC_UNLOADING) return ENXIO; @@ -143,7 +154,11 @@ { cdev_t dev = ap->a_head.a_dev; nvme_softns_t *nsc = dev->si_drv1; - nvme_softc_t *sc = nsc->sc; + nvme_softc_t *sc; + + if (nsc == NULL || nsc->sc == NULL) + return ENXIO; + sc = nsc->sc; atomic_add_long(&sc->opencnt, -1); @@ -155,11 +170,17 @@ { cdev_t dev = ap->a_head.a_dev; nvme_softns_t *nsc = dev->si_drv1; - nvme_softc_t *sc = nsc->sc; + nvme_softc_t *sc; int error; + if (nsc == NULL || nsc->sc == NULL) + return ENXIO; + sc = nsc->sc; + switch(ap->a_cmd) { case NVMEIOCGETLOG: + if (nsc->sc == NULL) + return ENXIO; error = nvme_getlog_ioctl(sc, (void *)ap->a_data); break; default: @@ -175,6 +196,9 @@ cdev_t dev = ap->a_head.a_dev; nvme_softns_t *nsc = dev->si_drv1; + if (nsc == NULL || nsc->sc == NULL) + return 0; /* fail silently, bio is gone with the disk */ + nvme_strategy_core(nsc, ap->a_bio, nvme_sync_delay); return 0; |