summaryrefslogtreecommitdiffstats
path: root/sys/kern/kern_conf.c
diff options
context:
space:
mode:
authorkib <kib@FreeBSD.org>2008-03-17 13:17:10 +0000
committerkib <kib@FreeBSD.org>2008-03-17 13:17:10 +0000
commitd5211e24af02ebb1725871805b147651a2a8a885 (patch)
tree26f481a2731ae0d1156c6739ded071f6c37d714b /sys/kern/kern_conf.c
parentd1dffe342c7159f0f45353f88f1a79cc7c04a356 (diff)
downloadFreeBSD-src-d5211e24af02ebb1725871805b147651a2a8a885.zip
FreeBSD-src-d5211e24af02ebb1725871805b147651a2a8a885.tar.gz
Fix two races in the handling of the d_gianttrick for the D_NEEDGIANT
drivers. In the giant_XXX wrappers for the device methods of the D_NEEDGIANT drivers, do not dereference the cdev->si_devsw. It is racing with the destroy_devl() clearing of the si_devsw. Instead, use the dev_refthread() and return ENXIO for the destroyed device. [1] The check for the D_INIT in the prep_cdevsw() was not synchronized with the call of the fini_cdevsw() in destroy_devl(), that under rapid device creation/destruction may result in the use of uninitialized cdevsw [2]. Change the protocol for the prep_cdevsw(), requiring it to be called under dev_mtx, where the check for D_INIT is done. Do not free the memory allocated for the gianttrick cdevsw while holding the dev_mtx, put it into the free list to be freed later. Reuse the d_gianttrick pointer to keep the size and layout of the struct cdevsw (requested by phk). Free the memory in the dev_unlock_and_free(), and do all the free after the dev_mtx is dropped (suggested by jhb). Reported by: bsdimp + many [1], pho [2] Reviewed by: phk, jhb Tested by: pho MFC after: 1 week
Diffstat (limited to 'sys/kern/kern_conf.c')
-rw-r--r--sys/kern/kern_conf.c156
1 files changed, 121 insertions, 35 deletions
diff --git a/sys/kern/kern_conf.c b/sys/kern/kern_conf.c
index 1cc8a15..939feea 100644
--- a/sys/kern/kern_conf.c
+++ b/sys/kern/kern_conf.c
@@ -61,6 +61,8 @@ static struct cdev *make_dev_credv(int flags,
static struct cdev_priv_list cdevp_free_list =
TAILQ_HEAD_INITIALIZER(cdevp_free_list);
+static SLIST_HEAD(free_cdevsw, cdevsw) cdevsw_gt_post_list =
+ SLIST_HEAD_INITIALIZER();
void
dev_lock(void)
@@ -69,19 +71,41 @@ dev_lock(void)
mtx_lock(&devmtx);
}
+/*
+ * Free all the memory collected while the cdev mutex was
+ * locked. Since devmtx is after the system map mutex, free() cannot
+ * be called immediately and is postponed until cdev mutex can be
+ * dropped.
+ */
static void
dev_unlock_and_free(void)
{
+ struct cdev_priv_list cdp_free;
+ struct free_cdevsw csw_free;
struct cdev_priv *cdp;
+ struct cdevsw *csw;
mtx_assert(&devmtx, MA_OWNED);
- while ((cdp = TAILQ_FIRST(&cdevp_free_list)) != NULL) {
- TAILQ_REMOVE(&cdevp_free_list, cdp, cdp_list);
- mtx_unlock(&devmtx);
+
+ /*
+ * Make the local copy of the list heads while the dev_mtx is
+ * held. Free it later.
+ */
+ TAILQ_INIT(&cdp_free);
+ TAILQ_CONCAT(&cdp_free, &cdevp_free_list, cdp_list);
+ csw_free = cdevsw_gt_post_list;
+ SLIST_INIT(&cdevsw_gt_post_list);
+
+ mtx_unlock(&devmtx);
+
+ while ((cdp = TAILQ_FIRST(&cdp_free)) != NULL) {
+ TAILQ_REMOVE(&cdp_free, cdp, cdp_list);
devfs_free(&cdp->cdp_c);
- mtx_lock(&devmtx);
}
- mtx_unlock(&devmtx);
+ while ((csw = SLIST_FIRST(&csw_free)) != NULL) {
+ SLIST_REMOVE_HEAD(&csw_free, d_postfree_list);
+ free(csw, M_DEVT);
+ }
}
static void
@@ -94,6 +118,14 @@ dev_free_devlocked(struct cdev *cdev)
TAILQ_INSERT_HEAD(&cdevp_free_list, cdp, cdp_list);
}
+static void
+cdevsw_free_devlocked(struct cdevsw *csw)
+{
+
+ mtx_assert(&devmtx, MA_OWNED);
+ SLIST_INSERT_HEAD(&cdevsw_gt_post_list, csw, d_postfree_list);
+}
+
void
dev_unlock(void)
{
@@ -297,118 +329,164 @@ no_poll(struct cdev *dev __unused, int events, struct thread *td __unused)
static int
giant_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
{
+ struct cdevsw *dsw;
int retval;
+ dsw = dev_refthread(dev);
+ if (dsw == NULL)
+ return (ENXIO);
mtx_lock(&Giant);
- retval = dev->si_devsw->d_gianttrick->
- d_open(dev, oflags, devtype, td);
+ retval = dsw->d_gianttrick->d_open(dev, oflags, devtype, td);
mtx_unlock(&Giant);
+ dev_relthread(dev);
return (retval);
}
static int
giant_fdopen(struct cdev *dev, int oflags, struct thread *td, struct file *fp)
{
+ struct cdevsw *dsw;
int retval;
+ dsw = dev_refthread(dev);
+ if (dsw == NULL)
+ return (ENXIO);
mtx_lock(&Giant);
- retval = dev->si_devsw->d_gianttrick->
- d_fdopen(dev, oflags, td, fp);
+ retval = dsw->d_gianttrick->d_fdopen(dev, oflags, td, fp);
mtx_unlock(&Giant);
+ dev_relthread(dev);
return (retval);
}
static int
giant_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
{
+ struct cdevsw *dsw;
int retval;
+ dsw = dev_refthread(dev);
+ if (dsw == NULL)
+ return (ENXIO);
mtx_lock(&Giant);
- retval = dev->si_devsw->d_gianttrick->
- d_close(dev, fflag, devtype, td);
+ retval = dsw->d_gianttrick->d_close(dev, fflag, devtype, td);
mtx_unlock(&Giant);
+ dev_relthread(dev);
return (retval);
}
static void
giant_strategy(struct bio *bp)
{
+ struct cdevsw *dsw;
+ struct cdev *dev;
+ dev = bp->bio_dev;
+ dsw = dev_refthread(dev);
+ if (dsw == NULL) {
+ biofinish(bp, NULL, ENXIO);
+ return;
+ }
mtx_lock(&Giant);
- bp->bio_dev->si_devsw->d_gianttrick->
- d_strategy(bp);
+ dsw->d_gianttrick->d_strategy(bp);
mtx_unlock(&Giant);
+ dev_relthread(dev);
}
static int
giant_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td)
{
+ struct cdevsw *dsw;
int retval;
+ dsw = dev_refthread(dev);
+ if (dsw == NULL)
+ return (ENXIO);
mtx_lock(&Giant);
retval = dev->si_devsw->d_gianttrick->
d_ioctl(dev, cmd, data, fflag, td);
mtx_unlock(&Giant);
+ dev_relthread(dev);
return (retval);
}
static int
giant_read(struct cdev *dev, struct uio *uio, int ioflag)
{
+ struct cdevsw *dsw;
int retval;
+ dsw = dev_refthread(dev);
+ if (dsw == NULL)
+ return (ENXIO);
mtx_lock(&Giant);
retval = dev->si_devsw->d_gianttrick->
d_read(dev, uio, ioflag);
mtx_unlock(&Giant);
+ dev_relthread(dev);
return (retval);
}
static int
giant_write(struct cdev *dev, struct uio *uio, int ioflag)
{
+ struct cdevsw *dsw;
int retval;
+ dsw = dev_refthread(dev);
+ if (dsw == NULL)
+ return (ENXIO);
mtx_lock(&Giant);
- retval = dev->si_devsw->d_gianttrick->
- d_write(dev, uio, ioflag);
+ retval = dsw->d_gianttrick->d_write(dev, uio, ioflag);
mtx_unlock(&Giant);
+ dev_relthread(dev);
return (retval);
}
static int
giant_poll(struct cdev *dev, int events, struct thread *td)
{
+ struct cdevsw *dsw;
int retval;
+ dsw = dev_refthread(dev);
+ if (dsw == NULL)
+ return (ENXIO);
mtx_lock(&Giant);
- retval = dev->si_devsw->d_gianttrick->
- d_poll(dev, events, td);
+ retval = dsw->d_gianttrick->d_poll(dev, events, td);
mtx_unlock(&Giant);
+ dev_relthread(dev);
return (retval);
}
static int
giant_kqfilter(struct cdev *dev, struct knote *kn)
{
+ struct cdevsw *dsw;
int retval;
+ dsw = dev_refthread(dev);
+ if (dsw == NULL)
+ return (ENXIO);
mtx_lock(&Giant);
- retval = dev->si_devsw->d_gianttrick->
- d_kqfilter(dev, kn);
+ retval = dsw->d_gianttrick->d_kqfilter(dev, kn);
mtx_unlock(&Giant);
+ dev_relthread(dev);
return (retval);
}
static int
giant_mmap(struct cdev *dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot)
{
+ struct cdevsw *dsw;
int retval;
+ dsw = dev_refthread(dev);
+ if (dsw == NULL)
+ return (ENXIO);
mtx_lock(&Giant);
- retval = dev->si_devsw->d_gianttrick->
- d_mmap(dev, offset, paddr, nprot);
+ retval = dsw->d_gianttrick->d_mmap(dev, offset, paddr, nprot);
mtx_unlock(&Giant);
+ dev_relthread(dev);
return (retval);
}
@@ -490,7 +568,7 @@ fini_cdevsw(struct cdevsw *devsw)
if (devsw->d_gianttrick != NULL) {
gt = devsw->d_gianttrick;
memcpy(devsw, gt, sizeof *devsw);
- free(gt, M_DEVT);
+ cdevsw_free_devlocked(gt);
devsw->d_gianttrick = NULL;
}
devsw->d_flags &= ~D_INIT;
@@ -501,11 +579,20 @@ prep_cdevsw(struct cdevsw *devsw)
{
struct cdevsw *dsw2;
- if (devsw->d_flags & D_NEEDGIANT)
+ mtx_assert(&devmtx, MA_OWNED);
+ if (devsw->d_flags & D_INIT)
+ return;
+ if (devsw->d_flags & D_NEEDGIANT) {
+ dev_unlock();
dsw2 = malloc(sizeof *dsw2, M_DEVT, M_WAITOK);
- else
+ dev_lock();
+ } else
dsw2 = NULL;
- dev_lock();
+ if (devsw->d_flags & D_INIT) {
+ if (dsw2 != NULL)
+ cdevsw_free_devlocked(dsw2);
+ return;
+ }
if (devsw->d_version != D_VERSION_01) {
printf(
@@ -536,8 +623,8 @@ prep_cdevsw(struct cdevsw *devsw)
if (devsw->d_gianttrick == NULL) {
memcpy(dsw2, devsw, sizeof *dsw2);
devsw->d_gianttrick = dsw2;
- } else
- free(dsw2, M_DEVT);
+ dsw2 = NULL;
+ }
}
#define FIXUP(member, noop, giant) \
@@ -566,7 +653,8 @@ prep_cdevsw(struct cdevsw *devsw)
devsw->d_flags |= D_INIT;
- dev_unlock();
+ if (dsw2 != NULL)
+ cdevsw_free_devlocked(dsw2);
}
struct cdev *
@@ -580,10 +668,9 @@ make_dev_credv(int flags, struct cdevsw *devsw, int minornr,
KASSERT((minornr & ~MAXMINOR) == 0,
("Invalid minor (0x%x) in make_dev", minornr));
- if (!(devsw->d_flags & D_INIT))
- prep_cdevsw(devsw);
dev = devfs_alloc();
dev_lock();
+ prep_cdevsw(devsw);
dev = newdev(devsw, minornr, dev);
if (flags & MAKEDEV_REF)
dev_refl(dev);
@@ -620,7 +707,7 @@ make_dev_credv(int flags, struct cdevsw *devsw, int minornr,
devfs_create(dev);
clean_unrhdrl(devfs_inos);
- dev_unlock();
+ dev_unlock_and_free();
return (dev);
}
@@ -884,8 +971,6 @@ clone_create(struct clonedevs **cdp, struct cdevsw *csw, int *up, struct cdev **
KASSERT(*up <= CLONE_UNITMASK,
("Too high unit (0x%x) in clone_create", *up));
- if (!(csw->d_flags & D_INIT))
- prep_cdevsw(csw);
/*
* Search the list for a lot of things in one go:
@@ -898,6 +983,7 @@ clone_create(struct clonedevs **cdp, struct cdevsw *csw, int *up, struct cdev **
unit = *up;
ndev = devfs_alloc();
dev_lock();
+ prep_cdevsw(csw);
low = extra;
de = dl = NULL;
cd = *cdp;
@@ -977,7 +1063,7 @@ clone_cleanup(struct clonedevs **cdp)
destroy_devl(dev);
}
}
- dev_unlock();
+ dev_unlock_and_free();
free(cd, M_DEVBUF);
*cdp = NULL;
}
@@ -1004,7 +1090,7 @@ destroy_dev_tq(void *ctx, int pending)
cb = cp->cdp_dtr_cb;
cb_arg = cp->cdp_dtr_cb_arg;
destroy_devl(dev);
- dev_unlock();
+ dev_unlock_and_free();
dev_rel(dev);
if (cb != NULL)
cb(cb_arg);
OpenPOWER on IntegriCloud