Andreas Gruenbacher (2): gfs2: Rename sd_{ glock => kill }_wait gfs2: Fix potential glock use-after-free on unmount
fs/gfs2/glock.c | 45 ++++++++++++++++++++++++++++++++++++-------- fs/gfs2/glock.h | 1 + fs/gfs2/incore.h | 3 ++- fs/gfs2/lock_dlm.c | 14 ++++++++++++-- fs/gfs2/ops_fstype.c | 5 +++-- fs/gfs2/super.c | 3 --- 6 files changed, 55 insertions(+), 16 deletions(-)
-- 2.25.1
From: Andreas Gruenbacher agruenba@redhat.com
mainline inclusion from mainline-v6.6-rc1 commit 3c69c437bf9832d2201702c5ccc3b8a77a7e0aa3 category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IA6SA1 CVE: CVE-2024-38570
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=...
--------------------------------
Rename sd_glock_wait to sd_kill_wait: we'll use it for other things related to "killing" a filesystem on unmount soon (kill_sb).
Signed-off-by: Andreas Gruenbacher agruenba@redhat.com Conflicts: fs/gfs2/glock.c [Resolve conflicts due to several refactor patches not merged.] Signed-off-by: Zeng Heng zengheng4@huawei.com --- fs/gfs2/glock.c | 6 +++--- fs/gfs2/incore.h | 2 +- fs/gfs2/ops_fstype.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c index ee5cca5926ed..b1cc826f1e5a 100644 --- a/fs/gfs2/glock.c +++ b/fs/gfs2/glock.c @@ -169,7 +169,7 @@ void gfs2_glock_free(struct gfs2_glock *gl) wake_up_glock(gl); call_rcu(&gl->gl_rcu, gfs2_glock_dealloc); if (atomic_dec_and_test(&sdp->sd_glock_disposal)) - wake_up(&sdp->sd_glock_wait); + wake_up(&sdp->sd_kill_wait); }
/** @@ -1097,7 +1097,7 @@ int gfs2_glock_get(struct gfs2_sbd *sdp, u64 number, kfree(gl->gl_lksb.sb_lvbptr); kmem_cache_free(cachep, gl); if (atomic_dec_and_test(&sdp->sd_glock_disposal)) - wake_up(&sdp->sd_glock_wait); + wake_up(&sdp->sd_kill_wait);
out: return ret; @@ -2010,7 +2010,7 @@ void gfs2_gl_hash_clear(struct gfs2_sbd *sdp) flush_workqueue(glock_workqueue); glock_hash_walk(clear_glock, sdp); flush_workqueue(glock_workqueue); - wait_event_timeout(sdp->sd_glock_wait, + wait_event_timeout(sdp->sd_kill_wait, atomic_read(&sdp->sd_glock_disposal) == 0, HZ * 600); glock_hash_walk(dump_glock_func, sdp); diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h index f8858d995b24..4025d2be8016 100644 --- a/fs/gfs2/incore.h +++ b/fs/gfs2/incore.h @@ -741,7 +741,7 @@ struct gfs2_sbd { struct gfs2_glock *sd_rename_gl; struct gfs2_glock *sd_freeze_gl; struct work_struct sd_freeze_work; - wait_queue_head_t sd_glock_wait; + wait_queue_head_t sd_kill_wait; wait_queue_head_t sd_async_glock_wait; atomic_t sd_glock_disposal; struct completion sd_locking_init; diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c index 648f7336043f..f6166f590fc9 100644 --- a/fs/gfs2/ops_fstype.c +++ b/fs/gfs2/ops_fstype.c @@ -87,7 +87,7 @@ static struct gfs2_sbd *init_sbd(struct super_block *sb) set_bit(SDF_NOJOURNALID, &sdp->sd_flags); gfs2_tune_init(&sdp->sd_tune);
- init_waitqueue_head(&sdp->sd_glock_wait); + init_waitqueue_head(&sdp->sd_kill_wait); init_waitqueue_head(&sdp->sd_async_glock_wait); atomic_set(&sdp->sd_glock_disposal, 0); init_completion(&sdp->sd_locking_init);
From: Andreas Gruenbacher agruenba@redhat.com
stable inclusion from stable-v6.6.33 commit 0636b34b44589b142700ac137b5f69802cfe2e37 category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IA6SA1 CVE: CVE-2024-38570
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=...
--------------------------------
[ Upstream commit d98779e687726d8f8860f1c54b5687eec5f63a73 ]
When a DLM lockspace is released and there ares still locks in that lockspace, DLM will unlock those locks automatically. Commit fb6791d100d1b started exploiting this behavior to speed up filesystem unmount: gfs2 would simply free glocks it didn't want to unlock and then release the lockspace. This didn't take the bast callbacks for asynchronous lock contention notifications into account, which remain active until until a lock is unlocked or its lockspace is released.
To prevent those callbacks from accessing deallocated objects, put the glocks that should not be unlocked on the sd_dead_glocks list, release the lockspace, and only then free those glocks.
As an additional measure, ignore unexpected ast and bast callbacks if the receiving glock is dead.
Fixes: fb6791d100d1b ("GFS2: skip dlm_unlock calls in unmount") Signed-off-by: Andreas Gruenbacher agruenba@redhat.com Cc: David Teigland teigland@redhat.com Signed-off-by: Sasha Levin sashal@kernel.org Conflicts: fs/gfs2/glock.c fs/gfs2/glock.h fs/gfs2/incore.h fs/gfs2/lock_dlm.c fs/gfs2/ops_fstype.c [Resolve conflicts due to several refactor patches not merged.] Signed-off-by: Zeng Heng zengheng4@huawei.com --- fs/gfs2/glock.c | 39 ++++++++++++++++++++++++++++++++++----- fs/gfs2/glock.h | 1 + fs/gfs2/incore.h | 1 + fs/gfs2/lock_dlm.c | 14 ++++++++++++-- fs/gfs2/ops_fstype.c | 3 ++- fs/gfs2/super.c | 3 --- 6 files changed, 50 insertions(+), 11 deletions(-)
diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c index b1cc826f1e5a..c854e4851e26 100644 --- a/fs/gfs2/glock.c +++ b/fs/gfs2/glock.c @@ -159,19 +159,46 @@ 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; - gfs2_glock_assert_withdraw(gl, atomic_read(&gl->gl_revokes) == 0); 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 @@ -369,7 +396,7 @@ static void do_error(struct gfs2_glock *gl, const int ret) /** * do_promote - promote as many requests as possible on the current queue * @gl: The glock - * + * * Returns: 1 if there is a blocked holder at the head of the list, or 2 * if a type specific operation is underway. */ @@ -1327,7 +1354,7 @@ void gfs2_print_dbg(struct seq_file *seq, const char *fmt, ...) * Eventually we should move the recursive locking trap to a * debugging option or something like that. This is the fast * path and needs to have the minimum number of distractions. - * + * */
static inline void add_to_queue(struct gfs2_holder *gh) @@ -2008,6 +2035,8 @@ void gfs2_gl_hash_clear(struct gfs2_sbd *sdp) { set_bit(SDF_SKIP_DLM_UNLOCK, &sdp->sd_flags); flush_workqueue(glock_workqueue); + gfs2_lm_unmount(sdp); + gfs2_free_dead_glocks(sdp); glock_hash_walk(clear_glock, sdp); flush_workqueue(glock_workqueue); wait_event_timeout(sdp->sd_kill_wait, diff --git a/fs/gfs2/glock.h b/fs/gfs2/glock.h index 53813364517b..67717cc60806 100644 --- a/fs/gfs2/glock.h +++ b/fs/gfs2/glock.h @@ -253,6 +253,7 @@ extern void gfs2_glock_finish_truncate(struct gfs2_inode *ip); extern void gfs2_glock_thaw(struct gfs2_sbd *sdp); extern void gfs2_glock_add_to_lru(struct gfs2_glock *gl); extern void gfs2_glock_free(struct gfs2_glock *gl); +void gfs2_glock_free_later(struct gfs2_glock *gl);
extern int __init gfs2_glock_init(void); extern void gfs2_glock_exit(void); diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h index 4025d2be8016..d3dbcf23e304 100644 --- a/fs/gfs2/incore.h +++ b/fs/gfs2/incore.h @@ -863,6 +863,7 @@ struct gfs2_sbd { struct gfs2_holder sd_freeze_gh; atomic_t sd_freeze_state; struct mutex sd_freeze_mutex; + struct list_head sd_dead_glocks;
char sd_fsname[GFS2_FSNAME_LEN + 3 * sizeof(int) + 2]; char sd_table_name[GFS2_FSNAME_LEN]; diff --git a/fs/gfs2/lock_dlm.c b/fs/gfs2/lock_dlm.c index 5564aa8b4592..98dce5826d66 100644 --- a/fs/gfs2/lock_dlm.c +++ b/fs/gfs2/lock_dlm.c @@ -112,12 +112,17 @@ static inline void gfs2_update_request_times(struct gfs2_glock *gl) gfs2_update_stats(&lks->lkstats[gltype], GFS2_LKS_SIRT, irt); /* Global */ preempt_enable(); } - + static void gdlm_ast(void *arg) { struct gfs2_glock *gl = arg; unsigned ret = gl->gl_state;
+ /* If the glock is dead, we only react to a dlm_unlock() reply. */ + if (__lockref_is_dead(&gl->gl_lockref) && + gl->gl_lksb.sb_status != -DLM_EUNLOCK) + return; + gfs2_update_reply_times(gl); BUG_ON(gl->gl_lksb.sb_flags & DLM_SBF_DEMOTED);
@@ -168,6 +173,9 @@ static void gdlm_bast(void *arg, int mode) { struct gfs2_glock *gl = arg;
+ if (__lockref_is_dead(&gl->gl_lockref)) + return; + switch (mode) { case DLM_LOCK_EX: gfs2_glock_cb(gl, LM_ST_UNLOCKED); @@ -286,6 +294,8 @@ static void gdlm_put_lock(struct gfs2_glock *gl) struct lm_lockstruct *ls = &sdp->sd_lockstruct; int error;
+ BUG_ON(!__lockref_is_dead(&gl->gl_lockref)); + if (gl->gl_lksb.sb_lkid == 0) { gfs2_glock_free(gl); return; @@ -305,7 +315,7 @@ static void gdlm_put_lock(struct gfs2_glock *gl)
if (test_bit(SDF_SKIP_DLM_UNLOCK, &sdp->sd_flags) && !gl->gl_lksb.sb_lvbptr) { - gfs2_glock_free(gl); + gfs2_glock_free_later(gl); return; }
diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c index f6166f590fc9..f7c232f3ecac 100644 --- a/fs/gfs2/ops_fstype.c +++ b/fs/gfs2/ops_fstype.c @@ -141,6 +141,7 @@ static struct gfs2_sbd *init_sbd(struct super_block *sb) init_waitqueue_head(&sdp->sd_log_flush_wait); atomic_set(&sdp->sd_freeze_state, SFS_UNFROZEN); mutex_init(&sdp->sd_freeze_mutex); + INIT_LIST_HEAD(&sdp->sd_dead_glocks);
return sdp;
@@ -1015,7 +1016,7 @@ static int gfs2_lm_mount(struct gfs2_sbd *sdp, int silent) switch (token) { case Opt_jid: ret = match_int(&tmp[0], &option); - if (ret || option < 0) + if (ret || option < 0) goto hostdata_error; if (test_and_clear_bit(SDF_NOJOURNALID, &sdp->sd_flags)) ls->ls_jid = option; diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c index 8cf4ef61cdc4..039d678b1689 100644 --- a/fs/gfs2/super.c +++ b/fs/gfs2/super.c @@ -662,10 +662,7 @@ static void gfs2_put_super(struct super_block *sb) gfs2_gl_hash_clear(sdp); truncate_inode_pages_final(&sdp->sd_aspace); gfs2_delete_debugfs_file(sdp); - /* Unmount the locking protocol */ - gfs2_lm_unmount(sdp);
- /* At this point, we're through participating in the lockspace */ gfs2_sys_fs_del(sdp); free_sbd(sdp); }
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://gitee.com/openeuler/kernel/pulls/9873 邮件列表地址:https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/N...
FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://gitee.com/openeuler/kernel/pulls/9873 Mailing list address: https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/N...