From 136faa862d9e94e04cab41b0f699daf4f867cdbd Mon Sep 17 00:00:00 2001 From: Taylor R Campbell Date: Sun, 23 Jan 2022 19:30:41 +0000 Subject: [PATCH 30/37] specfs: Take an I/O reference across bdev/cdev_open. - Revoke is used to invalidate all prior access control checks when device permissions are changing, so it must wait for .d_open to exit so any new access must go through new access control checks. - Revoke is used by vdevgone in xyz_detach to wait until all use of the driver's data structures have completed before xyz_detach frees them. So we need to make sure spec_close waits for .d_open too. --- sys/miscfs/specfs/spec_vnops.c | 39 ++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/sys/miscfs/specfs/spec_vnops.c b/sys/miscfs/specfs/spec_vnops.c index dd25f68c52ea..0a4fcc145a84 100644 --- a/sys/miscfs/specfs/spec_vnops.c +++ b/sys/miscfs/specfs/spec_vnops.c @@ -695,10 +695,10 @@ spec_open(void *v) } */ *ap = v; struct lwp *l = curlwp; struct vnode *vp = ap->a_vp; - dev_t dev; + dev_t dev, dev1; int error; enum kauth_device_req req; - specnode_t *sn; + specnode_t *sn, *sn1; specdev_t *sd; spec_ioctl_t ioctl; u_int gen = 0; @@ -807,18 +807,34 @@ spec_open(void *v) } /* - * Open the device. If .d_open returns ENXIO (device not - * configured), the driver may not be loaded, so try - * autoloading a module and then try .d_open again if anything - * got loaded. - * * Because opening the device may block indefinitely, e.g. when * opening a tty, and loading a module may cross into many * other subsystems, we must not hold the vnode lock while * calling .d_open, so release it now and reacquire it when * done. + * + * Take an I/O reference so that any concurrent spec_close via + * spec_node_revoke will wait for us to finish calling .d_open. + * The vnode can't be dead at this point because we have it + * locked. Note that if revoked, the driver must interrupt + * .d_open before spec_close starts waiting for I/O to drain so + * this doesn't deadlock. */ VOP_UNLOCK(vp); + error = spec_io_enter(vp, &sn1, &dev1); + if (error) { + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + return error; + } + KASSERT(sn1 == sn); + KASSERT(dev1 == dev); + + /* + * Open the device. If .d_open returns ENXIO (device not + * configured), the driver may not be loaded, so try + * autoloading a module and then try .d_open again if anything + * got loaded. + */ switch (vp->v_type) { case VCHR: do { @@ -873,7 +889,16 @@ spec_open(void *v) default: __unreachable(); } + + /* + * Release the I/O reference now that we have called .d_open, + * and reacquire the vnode lock. At this point, the device may + * have been revoked, so we must tread carefully. However, sn + * and sd remain valid pointers until we drop our reference. + */ + spec_io_exit(vp, sn); vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + KASSERT(vp->v_specnode == sn); /* * If it has been revoked since we released the vnode lock and