diff options
author | Chunwei Chen <[email protected]> | 2016-05-23 11:58:21 -0700 |
---|---|---|
committer | Brian Behlendorf <[email protected]> | 2016-05-25 14:11:53 -0700 |
commit | 4442f60d8e959e9983e58ff534e7302ebf2f9d7e (patch) | |
tree | 1cc68680db5bed9af9d10824d6449531a044724b /module/zfs/arc.c | |
parent | 83426735026cefc9d8d566615064faaf55074897 (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/zfs/arc.c')
-rw-r--r-- | module/zfs/arc.c | 21 |
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 |