]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ext4: fix use-after-free in update_super_work when racing with umount
authorJiayuan Chen <jiayuan.chen@shopee.com>
Thu, 19 Mar 2026 12:03:35 +0000 (20:03 +0800)
committerTheodore Ts'o <tytso@mit.edu>
Sat, 28 Mar 2026 03:37:39 +0000 (23:37 -0400)
Commit b98535d09179 ("ext4: fix bug_on in start_this_handle during umount
filesystem") moved ext4_unregister_sysfs() before flushing s_sb_upd_work
to prevent new error work from being queued via /proc/fs/ext4/xx/mb_groups
reads during unmount. However, this introduced a use-after-free because
update_super_work calls ext4_notify_error_sysfs() -> sysfs_notify() which
accesses the kobject's kernfs_node after it has been freed by kobject_del()
in ext4_unregister_sysfs():

  update_super_work                ext4_put_super
  -----------------                --------------
                                   ext4_unregister_sysfs(sb)
                                     kobject_del(&sbi->s_kobj)
                                       __kobject_del()
                                         sysfs_remove_dir()
                                           kobj->sd = NULL
                                         sysfs_put(sd)
                                           kernfs_put()  // RCU free
  ext4_notify_error_sysfs(sbi)
    sysfs_notify(&sbi->s_kobj)
      kn = kobj->sd              // stale pointer
      kernfs_get(kn)             // UAF on freed kernfs_node
                                   ext4_journal_destroy()
                                     flush_work(&sbi->s_sb_upd_work)

Instead of reordering the teardown sequence, fix this by making
ext4_notify_error_sysfs() detect that sysfs has already been torn down
by checking s_kobj.state_in_sysfs, and skipping the sysfs_notify() call
in that case. A dedicated mutex (s_error_notify_mutex) serializes
ext4_notify_error_sysfs() against kobject_del() in ext4_unregister_sysfs()
to prevent TOCTOU races where the kobject could be deleted between the
state_in_sysfs check and the sysfs_notify() call.

Fixes: b98535d09179 ("ext4: fix bug_on in start_this_handle during umount filesystem")
Cc: Jiayuan Chen <jiayuan.chen@linux.dev>
Suggested-by: Jan Kara <jack@suse.cz>
Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
Reviewed-by: Ritesh Harjani (IBM) <ritesh.list@gmail.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Link: https://patch.msgid.link/20260319120336.157873-1-jiayuan.chen@linux.dev
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Cc: stable@kernel.org
fs/ext4/ext4.h
fs/ext4/super.c
fs/ext4/sysfs.c

index c579f68b3c113f563cf5344dfd10db333274cb3a..7617e2d454ea516fee9047366458c0bda568fddf 100644 (file)
@@ -1570,6 +1570,7 @@ struct ext4_sb_info {
        struct proc_dir_entry *s_proc;
        struct kobject s_kobj;
        struct completion s_kobj_unregister;
+       struct mutex s_error_notify_mutex; /* protects sysfs_notify vs kobject_del */
        struct super_block *s_sb;
        struct buffer_head *s_mmp_bh;
 
index baa067eb8cf4c0661f56dd49d09b32a401d8f995..cb69a9b38b7ca95e3272a2c377a4b82bc3c0dbde 100644 (file)
@@ -5406,6 +5406,7 @@ static int __ext4_fill_super(struct fs_context *fc, struct super_block *sb)
 
        timer_setup(&sbi->s_err_report, print_daily_error_info, 0);
        spin_lock_init(&sbi->s_error_lock);
+       mutex_init(&sbi->s_error_notify_mutex);
        INIT_WORK(&sbi->s_sb_upd_work, update_super_work);
 
        err = ext4_group_desc_init(sb, es, logical_sb_block, &first_not_zeroed);
index b87d7bdab06a863159e6013ba510f11b5df9f0d9..923b375e017faaa8d06b809708bd623b3926ad71 100644 (file)
@@ -597,7 +597,10 @@ static const struct kobj_type ext4_feat_ktype = {
 
 void ext4_notify_error_sysfs(struct ext4_sb_info *sbi)
 {
-       sysfs_notify(&sbi->s_kobj, NULL, "errors_count");
+       mutex_lock(&sbi->s_error_notify_mutex);
+       if (sbi->s_kobj.state_in_sysfs)
+               sysfs_notify(&sbi->s_kobj, NULL, "errors_count");
+       mutex_unlock(&sbi->s_error_notify_mutex);
 }
 
 static struct kobject *ext4_root;
@@ -610,8 +613,10 @@ int ext4_register_sysfs(struct super_block *sb)
        int err;
 
        init_completion(&sbi->s_kobj_unregister);
+       mutex_lock(&sbi->s_error_notify_mutex);
        err = kobject_init_and_add(&sbi->s_kobj, &ext4_sb_ktype, ext4_root,
                                   "%s", sb->s_id);
+       mutex_unlock(&sbi->s_error_notify_mutex);
        if (err) {
                kobject_put(&sbi->s_kobj);
                wait_for_completion(&sbi->s_kobj_unregister);
@@ -644,7 +649,10 @@ void ext4_unregister_sysfs(struct super_block *sb)
 
        if (sbi->s_proc)
                remove_proc_subtree(sb->s_id, ext4_proc_root);
+
+       mutex_lock(&sbi->s_error_notify_mutex);
        kobject_del(&sbi->s_kobj);
+       mutex_unlock(&sbi->s_error_notify_mutex);
 }
 
 int __init ext4_init_sysfs(void)