aboutsummaryrefslogtreecommitdiffstats
path: root/fs/gfs2
diff options
context:
space:
mode:
authorAndreas Gruenbacher <agruenba@redhat.com>2019-04-04 16:11:11 -0400
committerAndreas Gruenbacher <agruenba@redhat.com>2019-05-07 17:39:14 -0400
commit9287c6452d2b1f24ea8e84bd3cf6f3c6f267f712 (patch)
treee46c12612cdac307868495ab41146aa86ca54483 /fs/gfs2
parent7c70b896951c84d63e6d71b82668f9c8b8bbd440 (diff)
gfs2: Fix occasional glock use-after-free
This patch has to do with the life cycle of glocks and buffers. When gfs2 metadata or journaled data is queued to be written, a gfs2_bufdata object is assigned to track the buffer, and that is queued to various lists, including the glock's gl_ail_list to indicate it's on the active items list. Once the page associated with the buffer has been written, it is removed from the ail list, but its life isn't over until a revoke has been successfully written. So after the block is written, its bufdata object is moved from the glock's gl_ail_list to a file-system-wide list of pending revokes, sd_log_le_revoke. At that point the glock still needs to track how many revokes it contributed to that list (in gl_revokes) so that things like glock go_sync can ensure all the metadata has been not only written, but also revoked before the glock is granted to a different node. This is to guarantee journal replay doesn't replay the block once the glock has been granted to another node. Ross Lagerwall recently discovered a race in which an inode could be evicted, and its glock freed after its ail list had been synced, but while it still had unwritten revokes on the sd_log_le_revoke list. The evict decremented the glock reference count to zero, which allowed the glock to be freed. After the revoke was written, function revoke_lo_after_commit tried to adjust the glock's gl_revokes counter and clear its GLF_LFLUSH flag, at which time it referenced the freed glock. This patch fixes the problem by incrementing the glock reference count in gfs2_add_revoke when the glock's first bufdata object is moved from the glock to the global revokes list. Later, when the glock's last such bufdata object is freed, the reference count is decremented. This guarantees that whichever process finishes last (the revoke writing or the evict) will properly free the glock, and neither will reference the glock after it has been freed. Reported-by: Ross Lagerwall <ross.lagerwall@citrix.com> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com> Signed-off-by: Bob Peterson <rpeterso@redhat.com>
Diffstat (limited to 'fs/gfs2')
-rw-r--r--fs/gfs2/glock.c1
-rw-r--r--fs/gfs2/log.c3
-rw-r--r--fs/gfs2/lops.c6
3 files changed, 7 insertions, 3 deletions
diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c
index e4f6d39500bc..71c28ff98b56 100644
--- a/fs/gfs2/glock.c
+++ b/fs/gfs2/glock.c
@@ -140,6 +140,7 @@ void gfs2_glock_free(struct gfs2_glock *gl)
140{ 140{
141 struct gfs2_sbd *sdp = gl->gl_name.ln_sbd; 141 struct gfs2_sbd *sdp = gl->gl_name.ln_sbd;
142 142
143 BUG_ON(atomic_read(&gl->gl_revokes));
143 rhashtable_remove_fast(&gl_hash_table, &gl->gl_node, ht_parms); 144 rhashtable_remove_fast(&gl_hash_table, &gl->gl_node, ht_parms);
144 smp_mb(); 145 smp_mb();
145 wake_up_glock(gl); 146 wake_up_glock(gl);
diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c
index ebbc68dca145..7ba94b66c91b 100644
--- a/fs/gfs2/log.c
+++ b/fs/gfs2/log.c
@@ -606,7 +606,8 @@ void gfs2_add_revoke(struct gfs2_sbd *sdp, struct gfs2_bufdata *bd)
606 gfs2_remove_from_ail(bd); /* drops ref on bh */ 606 gfs2_remove_from_ail(bd); /* drops ref on bh */
607 bd->bd_bh = NULL; 607 bd->bd_bh = NULL;
608 sdp->sd_log_num_revoke++; 608 sdp->sd_log_num_revoke++;
609 atomic_inc(&gl->gl_revokes); 609 if (atomic_inc_return(&gl->gl_revokes) == 1)
610 gfs2_glock_hold(gl);
610 set_bit(GLF_LFLUSH, &gl->gl_flags); 611 set_bit(GLF_LFLUSH, &gl->gl_flags);
611 list_add(&bd->bd_list, &sdp->sd_log_le_revoke); 612 list_add(&bd->bd_list, &sdp->sd_log_le_revoke);
612} 613}
diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c
index aef21b6a608f..b4f0a6a3ba59 100644
--- a/fs/gfs2/lops.c
+++ b/fs/gfs2/lops.c
@@ -669,8 +669,10 @@ static void revoke_lo_after_commit(struct gfs2_sbd *sdp, struct gfs2_trans *tr)
669 bd = list_entry(head->next, struct gfs2_bufdata, bd_list); 669 bd = list_entry(head->next, struct gfs2_bufdata, bd_list);
670 list_del_init(&bd->bd_list); 670 list_del_init(&bd->bd_list);
671 gl = bd->bd_gl; 671 gl = bd->bd_gl;
672 atomic_dec(&gl->gl_revokes); 672 if (atomic_dec_return(&gl->gl_revokes) == 0) {
673 clear_bit(GLF_LFLUSH, &gl->gl_flags); 673 clear_bit(GLF_LFLUSH, &gl->gl_flags);
674 gfs2_glock_queue_put(gl);
675 }
674 kmem_cache_free(gfs2_bufdata_cachep, bd); 676 kmem_cache_free(gfs2_bufdata_cachep, bd);
675 } 677 }
676} 678}