summaryrefslogtreecommitdiffstats
path: root/module
diff options
context:
space:
mode:
authorChunwei Chen <[email protected]>2016-05-23 11:58:21 -0700
committerBrian Behlendorf <[email protected]>2016-05-25 14:11:53 -0700
commit4442f60d8e959e9983e58ff534e7302ebf2f9d7e (patch)
tree1cc68680db5bed9af9d10824d6449531a044724b /module
parent83426735026cefc9d8d566615064faaf55074897 (diff)
Fix arc_prune_task use-after-free
arc_prune_task uses a refcount to protect arc_prune_t, but it doesn't prevent the underlying zsb from disappearing if there's a concurrent umount. We fix this by force the caller of arc_remove_prune_callback to wait for arc_prune_taskq to finish. Signed-off-by: Chunwei Chen <[email protected]> Signed-off-by: Brian Behlendorf <[email protected]> Closes #4687 Closes #4690
Diffstat (limited to 'module')
-rw-r--r--module/zfs/arc.c21
1 files changed, 11 insertions, 10 deletions
diff --git a/module/zfs/arc.c b/module/zfs/arc.c
index 716ba5c2d..18e9a145d 100644
--- a/module/zfs/arc.c
+++ b/module/zfs/arc.c
@@ -2707,12 +2707,7 @@ arc_prune_task(void *ptr)
if (func != NULL)
func(ap->p_adjust, ap->p_private);
- /* Callback unregistered concurrently with execution */
- if (refcount_remove(&ap->p_refcnt, func) == 0) {
- ASSERT(!list_link_active(&ap->p_node));
- refcount_destroy(&ap->p_refcnt);
- kmem_free(ap, sizeof (*ap));
- }
+ refcount_remove(&ap->p_refcnt, func);
}
/*
@@ -4628,13 +4623,19 @@ arc_add_prune_callback(arc_prune_func_t *func, void *private)
void
arc_remove_prune_callback(arc_prune_t *p)
{
+ boolean_t wait = B_FALSE;
mutex_enter(&arc_prune_mtx);
list_remove(&arc_prune_list, p);
- if (refcount_remove(&p->p_refcnt, &arc_prune_list) == 0) {
- refcount_destroy(&p->p_refcnt);
- kmem_free(p, sizeof (*p));
- }
+ if (refcount_remove(&p->p_refcnt, &arc_prune_list) > 0)
+ wait = B_TRUE;
mutex_exit(&arc_prune_mtx);
+
+ /* wait for arc_prune_task to finish */
+ if (wait)
+ taskq_wait_outstanding(arc_prune_taskq, 0);
+ ASSERT0(refcount_count(&p->p_refcnt));
+ refcount_destroy(&p->p_refcnt);
+ kmem_free(p, sizeof (*p));
}
void