--- a/sys/dev/misc/cuse/cuse.c +++ b/sys/dev/misc/cuse/cuse.c @@ struct cuse_server { struct kqinfo selinfo; pid_t pid; int is_closing; + int dying; /* guarded by cuse_global_mtx */ int refs; }; @@ static void cuse_server_free_dev(struct cuse_server_dev *pcsd) { @@ /* prevent creation of more devices */ cuse_server_lock(pcs); if (pcsd->kern_dev != NULL) + cuse_global_lock(); /* pairs with open-side si_drv1 read */ pcsd->kern_dev->si_drv1 = NULL; + cuse_global_unlock(); @@ static void cuse_server_unref(struct cuse_server *pcs) { @@ - cuse_server_lock(pcs); - if (--(pcs->refs) != 0) { - cuse_server_unlock(pcs); + cuse_global_lock(); + if (--(pcs->refs) != 0) { + cuse_global_unlock(); return; } + pcs->dying = 1; + cuse_global_unlock(); + + cuse_server_lock(pcs); cuse_server_is_closing(pcs); @@ static int cuse_client_open(struct dev_open_args *ap) { struct cdev *dev = ap->a_head.a_dev; - struct cuse_client_command *pccmd; - int fflags = ap->a_oflags; - struct cuse_server_dev *pcsd; - struct cuse_client *pcc; - struct cuse_server *pcs; - struct cuse_dev *pcd; + struct cuse_client_command *pccmd; + int fflags = ap->a_oflags; + struct cuse_server_dev *pcsd; + struct cuse_client *pcc; + struct cuse_server *pcs; + struct cuse_dev *pcd; int error; int n; + /* + * Serialize against the final cuse_server_unref(): the si_drv1 + * read, the dying check and the refs increment must happen + * atomically with respect to the teardown path, otherwise an + * in-flight open can resurrect a server whose memory is about + * to be freed (refs 0 -> 1 after the final drop). + */ + cuse_global_lock(); pcsd = dev->si_drv1; if (pcsd != NULL) { pcs = pcsd->server; pcd = pcsd->user_dev; + if (pcs->dying) { + cuse_global_unlock(); + return (EINVAL); + } - cuse_server_lock(pcs); /* * Check that the refcount didn't wrap and that the * same process is not both client and server. This * can easily lead to deadlocks when destroying the * CUSE character device nodes: */ pcs->refs++; if (pcs->refs < 0 || pcs->pid == curproc->p_pid) { /* overflow or wrong PID */ pcs->refs--; - cuse_server_unlock(pcs); + cuse_global_unlock(); return (EINVAL); } - cuse_server_unlock(pcs); + cuse_global_unlock(); } else { + cuse_global_unlock(); return (EINVAL); }