ext4_put_super() destroys the journal before it calls ext4_mb_release(), but the failure unwind of __ext4_fill_super() runs the two steps in the opposite order: failed_mount6 calls ext4_mb_release(), and the journal is destroyed only later, just above failed_mount3a. ext4_mb_release() flushes sbi->s_discard_work before releasing the mballoc structures, a drain made unconditional in commit 9ee29d20aab2 ("ext4: always drain queued discard work in ext4_mb_release()"). With -o discard, the journal destroy that runs afterwards re-arms that work after the flush: ext4_journal_destroy() calls ext4_force_commit(), and the commit callback, registered once mballoc is initialized, queues s_discard_work whenever the discard option is set, even with an empty freed-data list. A quota-enabled mount reaches this with a live transaction (ext4_enable_quotas() failing into failed_mount8, ext4_quotas_off() on the failed_mount9 path), so the final force commit is not a no-op. Nothing drains s_discard_work after that point: failed_mount3 flushes only s_sb_upd_work and the s_err_report timer, and ext4_fill_super() then frees sbi with a plain kfree() through ext4_free_sbi(). If the system_dfl_wq worker is delayed across the rest of the unwind, ext4_discard_work() then accesses the freed sbi, first through sbi->s_sb and then while taking sbi->s_md_lock. This is the pattern fixed for the s_err_report timer in commit 0ce160c5bdb6 ("ext4: fix timer use-after-free on failed mount"): async state armed after the unwind's last drain point. Destroy the journal at failed_mount6, before ext4_mb_release(). This restores the ext4_put_super() order, so the final commit re-arms s_discard_work before the flush_work() in ext4_mb_release() drains it. The journal destroy further down the unwind still covers paths that enter below failed_mount6; on the failed_mount6..failed_mount9 paths it does nothing, because ext4_journal_destroy() clears sbi->s_journal. This issue was found by an in-house static analysis tool. Fixes: 55cdd0af2bc5 ("ext4: get discard out of jbd2 commit kthread contex") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu --- fs/ext4/super.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 4b6112e5d6c5..58b210637b57 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -5761,6 +5761,12 @@ failed_mount8: __maybe_unused failed_mount7: ext4_unregister_li_request(sb); failed_mount6: + /* + * The last commit can re-arm s_discard_work, so destroy the + * journal before ext4_mb_release() flushes it. + */ + if (sbi->s_journal) + ext4_journal_destroy(sbi, sbi->s_journal); ext4_mb_release(sb); ext4_flex_groups_free(sbi); failed_mount5: