gfs2: Fix potential glock use-after-free on unmount
[linux-2.6-block.git] / fs / gfs2 / glock.c
index bf7538274985cbf1e3aefb31469624edccc80bae..4cf8971ce8ee02440b7ea2d5e79aab0d83223b6d 100644 (file)
@@ -166,18 +166,45 @@ static bool glock_blocked_by_withdraw(struct gfs2_glock *gl)
        return true;
 }
 
-void gfs2_glock_free(struct gfs2_glock *gl)
+static void __gfs2_glock_free(struct gfs2_glock *gl)
 {
-       struct gfs2_sbd *sdp = gl->gl_name.ln_sbd;
-
        rhashtable_remove_fast(&gl_hash_table, &gl->gl_node, ht_parms);
        smp_mb();
        wake_up_glock(gl);
        call_rcu(&gl->gl_rcu, gfs2_glock_dealloc);
+}
+
+void gfs2_glock_free(struct gfs2_glock *gl) {
+       struct gfs2_sbd *sdp = gl->gl_name.ln_sbd;
+
+       __gfs2_glock_free(gl);
        if (atomic_dec_and_test(&sdp->sd_glock_disposal))
                wake_up(&sdp->sd_kill_wait);
 }
 
+void gfs2_glock_free_later(struct gfs2_glock *gl) {
+       struct gfs2_sbd *sdp = gl->gl_name.ln_sbd;
+
+       spin_lock(&lru_lock);
+       list_add(&gl->gl_lru, &sdp->sd_dead_glocks);
+       spin_unlock(&lru_lock);
+       if (atomic_dec_and_test(&sdp->sd_glock_disposal))
+               wake_up(&sdp->sd_kill_wait);
+}
+
+static void gfs2_free_dead_glocks(struct gfs2_sbd *sdp)
+{
+       struct list_head *list = &sdp->sd_dead_glocks;
+
+       while(!list_empty(list)) {
+               struct gfs2_glock *gl;
+
+               gl = list_first_entry(list, struct gfs2_glock, gl_lru);
+               list_del_init(&gl->gl_lru);
+               __gfs2_glock_free(gl);
+       }
+}
+
 /**
  * gfs2_glock_hold() - increment reference count on glock
  * @gl: The glock to hold
@@ -2233,6 +2260,8 @@ void gfs2_gl_hash_clear(struct gfs2_sbd *sdp)
        wait_event_timeout(sdp->sd_kill_wait,
                           atomic_read(&sdp->sd_glock_disposal) == 0,
                           HZ * 600);
+       gfs2_lm_unmount(sdp);
+       gfs2_free_dead_glocks(sdp);
        glock_hash_walk(dump_glock_func, sdp);
 }