fs/f2fs/data.c | 11 ++++++++--- fs/f2fs/super.c | 1 + 2 files changed, 9 insertions(+), 3 deletions(-)
Syzbot reported a slab-use-after-free issue in f2fs_write_end_io():
[ 86.643336] BUG: KASAN: slab-use-after-free in f2fs_write_end_io+0x9b9/0xb60
[ 86.644120] Read of size 4 at addr ffff88804357d170 by task kworker/u4:4/45
...
[ 86.656543] Call Trace:
...
[ 86.660351] f2fs_write_end_io+0x9b9/0xb60
...
[ 86.685123] Allocated by task 5484:
...
[ 86.688325] f2fs_fill_super+0x8c/0x6ec0
...
[ 86.697685] Freed by task 5484:
...
[ 86.702700] kfree+0x1c0/0x660
[ 86.703273] kill_f2fs_super+0x5b6/0x6c0
The problem is a race condition between the shutdown of the filesystem
(kill_f2fs_super) and the asynchronous I/O completion handler
(f2fs_write_end_io).
When unmounting, kill_f2fs_super() frees the sbi structure. However,
if there are pending checkpoint data (CP_DATA) writes, the
f2fs_write_end_io() callback might still be running.
In the original code, f2fs_write_end_io() accesses sbi->cp_wait after
decrementing the page count. If the page count drops to zero,
f2fs_wait_on_all_pages() in the unmount path returns, allowing
kill_f2fs_super() to free sbi. If the callback then tries to wake up
waiters on sbi->cp_wait, a UAF occurs.
To fix this, I applied a two-step solution:
1. In kill_f2fs_super(), explicitly wait for all CP_DATA pages
to obtain a count of zero using f2fs_wait_on_all_pages(). This
ensures specific synchronization for these metadata writes.
2. In f2fs_write_end_io(), move the wake_up() call INSIDE the
bio_for_each_folio_all() loop. This ensures that the wakeup
(which signals completion to the waiter) happens before
processing of the bio is effectively 'done' from the perspective
of the waiter. More importantly, it removes any access to 'sbi'
after the loop, eliminating the UAF window.
Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187
Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
---
fs/f2fs/data.c | 11 ++++++++---
fs/f2fs/super.c | 1 +
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index c30e69392a62..5808d73c2598 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -318,10 +318,13 @@ static void f2fs_write_end_io(struct bio *bio)
{
struct f2fs_sb_info *sbi;
struct folio_iter fi;
+ bool is_close;
iostat_update_and_unbind_ctx(bio);
sbi = bio->bi_private;
+ is_close = is_sbi_flag_set(sbi, SBI_IS_CLOSE);
+
if (time_to_inject(sbi, FAULT_WRITE_IO))
bio->bi_status = BLK_STS_IOERR;
@@ -360,10 +363,12 @@ static void f2fs_write_end_io(struct bio *bio)
f2fs_del_fsync_node_entry(sbi, folio);
folio_clear_f2fs_gcing(folio);
folio_end_writeback(folio);
- }
- if (!get_pages(sbi, F2FS_WB_CP_DATA) &&
+
+ if (!is_close && type == F2FS_WB_CP_DATA &&
+ !get_pages(sbi, F2FS_WB_CP_DATA) &&
wq_has_sleeper(&sbi->cp_wait))
- wake_up(&sbi->cp_wait);
+ wake_up(&sbi->cp_wait);
+ }
bio_put(bio);
}
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index c4c225e09dc4..c9ee3fae1958 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5454,6 +5454,7 @@ static void kill_f2fs_super(struct super_block *sb)
kill_block_super(sb);
/* Release block devices last, after fscrypt_destroy_keyring(). */
if (sbi) {
+ f2fs_wait_on_all_pages(sbi, F2FS_WB_CP_DATA);
destroy_device_list(sbi);
kfree(sbi);
sb->s_fs_info = NULL;
--
2.52.0
On 12/24/2025 12:28 AM, Szymon Wilczek wrote:
> Syzbot reported a slab-use-after-free issue in f2fs_write_end_io():
> [ 86.643336] BUG: KASAN: slab-use-after-free in f2fs_write_end_io+0x9b9/0xb60
> [ 86.644120] Read of size 4 at addr ffff88804357d170 by task kworker/u4:4/45
> ...
> [ 86.656543] Call Trace:
> ...
> [ 86.660351] f2fs_write_end_io+0x9b9/0xb60
> ...
> [ 86.685123] Allocated by task 5484:
> ...
> [ 86.688325] f2fs_fill_super+0x8c/0x6ec0
> ...
> [ 86.697685] Freed by task 5484:
> ...
> [ 86.702700] kfree+0x1c0/0x660
> [ 86.703273] kill_f2fs_super+0x5b6/0x6c0
>
> The problem is a race condition between the shutdown of the filesystem
> (kill_f2fs_super) and the asynchronous I/O completion handler
> (f2fs_write_end_io).
>
> When unmounting, kill_f2fs_super() frees the sbi structure. However,
> if there are pending checkpoint data (CP_DATA) writes, the
> f2fs_write_end_io() callback might still be running.
>
> In the original code, f2fs_write_end_io() accesses sbi->cp_wait after
> decrementing the page count. If the page count drops to zero,
> f2fs_wait_on_all_pages() in the unmount path returns, allowing
> kill_f2fs_super() to free sbi. If the callback then tries to wake up
> waiters on sbi->cp_wait, a UAF occurs.
>
> To fix this, I applied a two-step solution:
>
> 1. In kill_f2fs_super(), explicitly wait for all CP_DATA pages
> to obtain a count of zero using f2fs_wait_on_all_pages(). This
> ensures specific synchronization for these metadata writes.
>
> 2. In f2fs_write_end_io(), move the wake_up() call INSIDE the
> bio_for_each_folio_all() loop. This ensures that the wakeup
> (which signals completion to the waiter) happens before
> processing of the bio is effectively 'done' from the perspective
> of the waiter. More importantly, it removes any access to 'sbi'
> after the loop, eliminating the UAF window.
>
> Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187
> Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
> ---
> fs/f2fs/data.c | 11 ++++++++---
> fs/f2fs/super.c | 1 +
> 2 files changed, 9 insertions(+), 3 deletions(-)
>
> diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
> index c30e69392a62..5808d73c2598 100644
> --- a/fs/f2fs/data.c
> +++ b/fs/f2fs/data.c
> @@ -318,10 +318,13 @@ static void f2fs_write_end_io(struct bio *bio)
> {
> struct f2fs_sb_info *sbi;
> struct folio_iter fi;
> + bool is_close;
>
> iostat_update_and_unbind_ctx(bio);
> sbi = bio->bi_private;
>
> + is_close = is_sbi_flag_set(sbi, SBI_IS_CLOSE);
Seems this check may race w/ set_sbi_flag(sbi, SBI_IS_CLOSE).
> +
> if (time_to_inject(sbi, FAULT_WRITE_IO))
> bio->bi_status = BLK_STS_IOERR;
>
> @@ -360,10 +363,12 @@ static void f2fs_write_end_io(struct bio *bio)
> f2fs_del_fsync_node_entry(sbi, folio);
> folio_clear_f2fs_gcing(folio);
> folio_end_writeback(folio);
> - }
> - if (!get_pages(sbi, F2FS_WB_CP_DATA) &&
> +
> + if (!is_close && type == F2FS_WB_CP_DATA &&
> + !get_pages(sbi, F2FS_WB_CP_DATA) &&
If F2FS_WB_CP_DATA count is zero, then sbi may be released in kill_f2fs_super(),
we should not allow to access sbi->cp_wait below, right?
Thanks,
> wq_has_sleeper(&sbi->cp_wait))
> - wake_up(&sbi->cp_wait);
> + wake_up(&sbi->cp_wait);
> + }
>
> bio_put(bio);
> }
> diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
> index c4c225e09dc4..c9ee3fae1958 100644
> --- a/fs/f2fs/super.c
> +++ b/fs/f2fs/super.c
> @@ -5454,6 +5454,7 @@ static void kill_f2fs_super(struct super_block *sb)
> kill_block_super(sb);
> /* Release block devices last, after fscrypt_destroy_keyring(). */
> if (sbi) {
> + f2fs_wait_on_all_pages(sbi, F2FS_WB_CP_DATA);
> destroy_device_list(sbi);
> kfree(sbi);
> sb->s_fs_info = NULL;
Syzbot reported a slab-use-after-free issue in f2fs_write_end_io():
BUG: KASAN: slab-use-after-free in f2fs_write_end_io+0x9b9/0xb60
Read of size 4 at addr ffff88804357d170 by task kworker/u4:4/45
The race condition occurs between the filesystem unmount path
(kill_f2fs_super) and the asynchronous I/O completion handler
(f2fs_write_end_io).
When unmounting, kill_f2fs_super() frees the sbi structure. However,
if the bio completion callback f2fs_write_end_io() is still running
in softirq context, it may access sbi->cp_wait after sbi has been
freed, causing a use-after-free.
Fix this by calling synchronize_rcu() before kfree(sbi). Since
bio completion callbacks run in softirq context, which is an implicit
RCU read-side critical section, synchronize_rcu() ensures all
in-flight callbacks have completed before we free sbi.
Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187
Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
---
v6: Add comment to explain synchronize_rcu() call.
Resend as reply to original thread.
v5: Resend as reply to original thread (v4 was mistakenly sent to a new thread).
v4: Removed f2fs_wait_on_all_pages() call as pointed out by Chao Yu that
it accesses sbi->write_io which has already been freed in f2fs_put_super().
v3: Simplified to minimal fix - only super.c change with synchronize_rcu(),
as pointed out by Chao Yu that data.c changes are not necessary since
synchronize_rcu() alone guarantees sbi won't be freed before callbacks
complete.
v2: Add synchronize_rcu() to wait for softirq bio callbacks to complete.
fs/f2fs/super.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index c4c225e09dc4..f5707591ba25 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5454,6 +5454,8 @@ static void kill_f2fs_super(struct super_block *sb)
kill_block_super(sb);
/* Release block devices last, after fscrypt_destroy_keyring(). */
if (sbi) {
+ /* wait for f2fs_write_end_io() to finish */
+ synchronize_rcu();
destroy_device_list(sbi);
kfree(sbi);
sb->s_fs_info = NULL;
--
2.52.0
On 1/6/2026 9:06 PM, Szymon Wilczek wrote:
> Syzbot reported a slab-use-after-free issue in f2fs_write_end_io():
>
> BUG: KASAN: slab-use-after-free in f2fs_write_end_io+0x9b9/0xb60
> Read of size 4 at addr ffff88804357d170 by task kworker/u4:4/45
>
> The race condition occurs between the filesystem unmount path
> (kill_f2fs_super) and the asynchronous I/O completion handler
> (f2fs_write_end_io).
>
> When unmounting, kill_f2fs_super() frees the sbi structure. However,
> if the bio completion callback f2fs_write_end_io() is still running
> in softirq context, it may access sbi->cp_wait after sbi has been
> freed, causing a use-after-free.
>
> Fix this by calling synchronize_rcu() before kfree(sbi). Since
> bio completion callbacks run in softirq context, which is an implicit
> RCU read-side critical section, synchronize_rcu() ensures all
> in-flight callbacks have completed before we free sbi.
>
> Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187
As I checked w/ reproduer in syzbot, the patch doesn't fix the UAF issue.
https://lore.kernel.org/lkml/695e0473.050a0220.1c677c.0356.GAE@google.com
Thanks,
> Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
> ---
> v6: Add comment to explain synchronize_rcu() call.
> Resend as reply to original thread.
> v5: Resend as reply to original thread (v4 was mistakenly sent to a new thread).
> v4: Removed f2fs_wait_on_all_pages() call as pointed out by Chao Yu that
> it accesses sbi->write_io which has already been freed in f2fs_put_super().
> v3: Simplified to minimal fix - only super.c change with synchronize_rcu(),
> as pointed out by Chao Yu that data.c changes are not necessary since
> synchronize_rcu() alone guarantees sbi won't be freed before callbacks
> complete.
> v2: Add synchronize_rcu() to wait for softirq bio callbacks to complete.
> fs/f2fs/super.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
> index c4c225e09dc4..f5707591ba25 100644
> --- a/fs/f2fs/super.c
> +++ b/fs/f2fs/super.c
> @@ -5454,6 +5454,8 @@ static void kill_f2fs_super(struct super_block *sb)
> kill_block_super(sb);
> /* Release block devices last, after fscrypt_destroy_keyring(). */
> if (sbi) {
> + /* wait for f2fs_write_end_io() to finish */
> + synchronize_rcu();
> destroy_device_list(sbi);
> kfree(sbi);
> sb->s_fs_info = NULL;
Syzbot reported a slab-use-after-free issue in f2fs_write_end_io():
BUG: KASAN: slab-use-after-free in f2fs_write_end_io+0x9b9/0xb60
Read of size 4 at addr ffff88804357d170 by task kworker/u4:4/45
The race condition occurs between the filesystem unmount path
(kill_f2fs_super) and the asynchronous I/O completion handler
(f2fs_write_end_io).
When unmounting, kill_f2fs_super() frees the sbi structure. However,
if the bio completion callback f2fs_write_end_io() is still running
in softirq context, it may access sbi->cp_wait after sbi has been
freed, causing a use-after-free.
Fix this by calling synchronize_rcu() before kfree(sbi). Since
bio completion callbacks run in softirq context, which is an implicit
RCU read-side critical section, synchronize_rcu() ensures all
in-flight callbacks have completed before we free sbi.
Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187
Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
---
v5: Resend as reply to original thread (v4 was mistakenly sent to a new thread).
v4: Removed f2fs_wait_on_all_pages() call as pointed out by Chao Yu that
it accesses sbi->write_io which has already been freed in f2fs_put_super().
v3: Simplified to minimal fix - only super.c change with synchronize_rcu(),
as pointed out by Chao Yu that data.c changes are not necessary since
synchronize_rcu() alone guarantees sbi won't be freed before callbacks
complete.
v2: Add synchronize_rcu() to wait for softirq bio callbacks to complete.
fs/f2fs/super.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index c4c225e09dc4..dfa3c76c6f2a 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5454,6 +5454,7 @@ static void kill_f2fs_super(struct super_block *sb)
kill_block_super(sb);
/* Release block devices last, after fscrypt_destroy_keyring(). */
if (sbi) {
+ synchronize_rcu();
destroy_device_list(sbi);
kfree(sbi);
sb->s_fs_info = NULL;
--
2.52.0
On 12/30/2025 10:08 PM, Szymon Wilczek wrote:
> Syzbot reported a slab-use-after-free issue in f2fs_write_end_io():
>
> BUG: KASAN: slab-use-after-free in f2fs_write_end_io+0x9b9/0xb60
> Read of size 4 at addr ffff88804357d170 by task kworker/u4:4/45
>
> The race condition occurs between the filesystem unmount path
> (kill_f2fs_super) and the asynchronous I/O completion handler
> (f2fs_write_end_io).
>
> When unmounting, kill_f2fs_super() frees the sbi structure. However,
> if the bio completion callback f2fs_write_end_io() is still running
> in softirq context, it may access sbi->cp_wait after sbi has been
> freed, causing a use-after-free.
>
> Fix this by calling synchronize_rcu() before kfree(sbi). Since
> bio completion callbacks run in softirq context, which is an implicit
> RCU read-side critical section, synchronize_rcu() ensures all
> in-flight callbacks have completed before we free sbi.
>
> Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187
> Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
> ---
> v5: Resend as reply to original thread (v4 was mistakenly sent to a new thread).
> v4: Removed f2fs_wait_on_all_pages() call as pointed out by Chao Yu that
> it accesses sbi->write_io which has already been freed in f2fs_put_super().
> v3: Simplified to minimal fix - only super.c change with synchronize_rcu(),
> as pointed out by Chao Yu that data.c changes are not necessary since
> synchronize_rcu() alone guarantees sbi won't be freed before callbacks
> complete.
> v2: Add synchronize_rcu() to wait for softirq bio callbacks to complete.
> fs/f2fs/super.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
> index c4c225e09dc4..dfa3c76c6f2a 100644
> --- a/fs/f2fs/super.c
> +++ b/fs/f2fs/super.c
> @@ -5454,6 +5454,7 @@ static void kill_f2fs_super(struct super_block *sb)
> kill_block_super(sb);
> /* Release block devices last, after fscrypt_destroy_keyring(). */
> if (sbi) {
> + synchronize_rcu();
Can you please add one line comment to describe why we need to call
synchronize_rcu() here?
Thanks,
> destroy_device_list(sbi);
> kfree(sbi);
> sb->s_fs_info = NULL;
Syzbot reported a slab-use-after-free issue in f2fs_write_end_io():
BUG: KASAN: slab-use-after-free in f2fs_write_end_io+0x9b9/0xb60
Read of size 4 at addr ffff88804357d170 by task kworker/u4:4/45
The race condition occurs between the filesystem unmount path
(kill_f2fs_super) and the asynchronous I/O completion handler
(f2fs_write_end_io).
When unmounting, kill_f2fs_super() frees the sbi structure. However, if
there are pending CP_DATA writes, the f2fs_write_end_io() callback might
still be running in softirq context and attempt to access sbi->cp_wait,
causing a use-after-free.
Fix this by calling synchronize_rcu() after f2fs_wait_on_all_pages()
but before kfree(sbi). Since bio completion callbacks run in softirq
context, which is an implicit RCU read-side critical section,
synchronize_rcu() ensures all in-flight callbacks have completed
before we free sbi.
Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187
Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
---
v3: Simplified to minimal fix - only super.c change with synchronize_rcu(),
as pointed out by Chao Yu that data.c changes are not necessary since
synchronize_rcu() alone guarantees sbi won't be freed before callbacks
complete.
v2: Add synchronize_rcu() to wait for softirq bio callbacks to complete.
---
fs/f2fs/super.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index c4c225e09dc4..924bc30d08b6 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5454,6 +5454,8 @@ static void kill_f2fs_super(struct super_block *sb)
kill_block_super(sb);
/* Release block devices last, after fscrypt_destroy_keyring(). */
if (sbi) {
+ f2fs_wait_on_all_pages(sbi, F2FS_WB_CP_DATA);
+ synchronize_rcu();
destroy_device_list(sbi);
kfree(sbi);
sb->s_fs_info = NULL;
--
2.52.0
On 12/27/2025 10:49 AM, Szymon Wilczek wrote:
> Syzbot reported a slab-use-after-free issue in f2fs_write_end_io():
>
> BUG: KASAN: slab-use-after-free in f2fs_write_end_io+0x9b9/0xb60
> Read of size 4 at addr ffff88804357d170 by task kworker/u4:4/45
>
> The race condition occurs between the filesystem unmount path
> (kill_f2fs_super) and the asynchronous I/O completion handler
> (f2fs_write_end_io).
>
> When unmounting, kill_f2fs_super() frees the sbi structure. However, if
> there are pending CP_DATA writes, the f2fs_write_end_io() callback might
> still be running in softirq context and attempt to access sbi->cp_wait,
> causing a use-after-free.
>
> Fix this by calling synchronize_rcu() after f2fs_wait_on_all_pages()
> but before kfree(sbi). Since bio completion callbacks run in softirq
> context, which is an implicit RCU read-side critical section,
> synchronize_rcu() ensures all in-flight callbacks have completed
> before we free sbi.
>
> Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187
> Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
> ---
> v3: Simplified to minimal fix - only super.c change with synchronize_rcu(),
> as pointed out by Chao Yu that data.c changes are not necessary since
> synchronize_rcu() alone guarantees sbi won't be freed before callbacks
> complete.
> v2: Add synchronize_rcu() to wait for softirq bio callbacks to complete.
> ---
> fs/f2fs/super.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
> index c4c225e09dc4..924bc30d08b6 100644
> --- a/fs/f2fs/super.c
> +++ b/fs/f2fs/super.c
> @@ -5454,6 +5454,8 @@ static void kill_f2fs_super(struct super_block *sb)
> kill_block_super(sb);
> /* Release block devices last, after fscrypt_destroy_keyring(). */
> if (sbi) {
> + f2fs_wait_on_all_pages(sbi, F2FS_WB_CP_DATA);
f2fs_wait_on_all_pages() -> f2fs_submit_merged_write() will access sbi->write_io
which should has been released in f2fs_put_super()?
Thanks,
> + synchronize_rcu();
> destroy_device_list(sbi);
> kfree(sbi);
> sb->s_fs_info = NULL;
Syzbot reported a slab-use-after-free issue in f2fs_write_end_io():
BUG: KASAN: slab-use-after-free in f2fs_write_end_io+0x9b9/0xb60
Read of size 4 at addr ffff88804357d170 by task kworker/u4:4/45
The race condition occurs between the filesystem unmount path
(kill_f2fs_super) and the asynchronous I/O completion handler
(f2fs_write_end_io).
When unmounting, kill_f2fs_super() frees the sbi structure. However, if
there are pending CP_DATA writes, the f2fs_write_end_io() callback might
still be running in softirq context and attempt to access sbi->cp_wait,
causing a use-after-free.
To fix this:
1. In f2fs_write_end_io(), check SBI_IS_CLOSE flag early and skip the
wake_up() call if the filesystem is shutting down. Move the wake_up
inside the loop for correct synchronization.
2. In kill_f2fs_super(), after f2fs_wait_on_all_pages() returns (meaning
the page count is zero), call synchronize_rcu() before kfree(sbi).
Since bio completion callbacks run in softirq context, which is an
implicit RCU read-side critical section, synchronize_rcu() ensures
all in-flight callbacks have completed before we free sbi.
The combination of these two changes eliminates the UAF window: the
is_close check provides fast-path optimization (skip wake_up when no
one is waiting), while synchronize_rcu() provides the hard guarantee
that no callback is accessing sbi when we free it.
Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187
Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
---
v2: Add synchronize_rcu() to wait for softirq bio callbacks to complete,
addressing the race condition pointed out by Chao Yu where sbi could
be freed while f2fs_write_end_io() was still accessing sbi->cp_wait.
---
fs/f2fs/data.c | 11 ++++++++---
fs/f2fs/super.c | 2 ++
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index c30e69392a62..5808d73c2598 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -318,10 +318,13 @@ static void f2fs_write_end_io(struct bio *bio)
{
struct f2fs_sb_info *sbi;
struct folio_iter fi;
+ bool is_close;
iostat_update_and_unbind_ctx(bio);
sbi = bio->bi_private;
+ is_close = is_sbi_flag_set(sbi, SBI_IS_CLOSE);
+
if (time_to_inject(sbi, FAULT_WRITE_IO))
bio->bi_status = BLK_STS_IOERR;
@@ -360,10 +363,12 @@ static void f2fs_write_end_io(struct bio *bio)
f2fs_del_fsync_node_entry(sbi, folio);
folio_clear_f2fs_gcing(folio);
folio_end_writeback(folio);
- }
- if (!get_pages(sbi, F2FS_WB_CP_DATA) &&
+
+ if (!is_close && type == F2FS_WB_CP_DATA &&
+ !get_pages(sbi, F2FS_WB_CP_DATA) &&
wq_has_sleeper(&sbi->cp_wait))
- wake_up(&sbi->cp_wait);
+ wake_up(&sbi->cp_wait);
+ }
bio_put(bio);
}
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index c4c225e09dc4..924bc30d08b6 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5454,6 +5454,8 @@ static void kill_f2fs_super(struct super_block *sb)
kill_block_super(sb);
/* Release block devices last, after fscrypt_destroy_keyring(). */
if (sbi) {
+ f2fs_wait_on_all_pages(sbi, F2FS_WB_CP_DATA);
+ synchronize_rcu();
destroy_device_list(sbi);
kfree(sbi);
sb->s_fs_info = NULL;
--
2.52.0
On 12/26/2025 10:20 PM, Szymon Wilczek wrote:
> Syzbot reported a slab-use-after-free issue in f2fs_write_end_io():
>
> BUG: KASAN: slab-use-after-free in f2fs_write_end_io+0x9b9/0xb60
> Read of size 4 at addr ffff88804357d170 by task kworker/u4:4/45
>
> The race condition occurs between the filesystem unmount path
> (kill_f2fs_super) and the asynchronous I/O completion handler
> (f2fs_write_end_io).
>
> When unmounting, kill_f2fs_super() frees the sbi structure. However, if
> there are pending CP_DATA writes, the f2fs_write_end_io() callback might
> still be running in softirq context and attempt to access sbi->cp_wait,
> causing a use-after-free.
>
> To fix this:
>
> 1. In f2fs_write_end_io(), check SBI_IS_CLOSE flag early and skip the
> wake_up() call if the filesystem is shutting down. Move the wake_up
> inside the loop for correct synchronization.
>
> 2. In kill_f2fs_super(), after f2fs_wait_on_all_pages() returns (meaning
> the page count is zero), call synchronize_rcu() before kfree(sbi).
> Since bio completion callbacks run in softirq context, which is an
> implicit RCU read-side critical section, synchronize_rcu() ensures
> all in-flight callbacks have completed before we free sbi.
>
> The combination of these two changes eliminates the UAF window: the
> is_close check provides fast-path optimization (skip wake_up when no
> one is waiting), while synchronize_rcu() provides the hard guarantee
> that no callback is accessing sbi when we free it.
>
> Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187
> Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
> ---
> v2: Add synchronize_rcu() to wait for softirq bio callbacks to complete,
> addressing the race condition pointed out by Chao Yu where sbi could
> be freed while f2fs_write_end_io() was still accessing sbi->cp_wait.
> ---
> fs/f2fs/data.c | 11 ++++++++---
> fs/f2fs/super.c | 2 ++
> 2 files changed, 10 insertions(+), 3 deletions(-)
>
> diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
> index c30e69392a62..5808d73c2598 100644
> --- a/fs/f2fs/data.c
> +++ b/fs/f2fs/data.c
> @@ -318,10 +318,13 @@ static void f2fs_write_end_io(struct bio *bio)
> {
> struct f2fs_sb_info *sbi;
> struct folio_iter fi;
> + bool is_close;
>
> iostat_update_and_unbind_ctx(bio);
> sbi = bio->bi_private;
>
> + is_close = is_sbi_flag_set(sbi, SBI_IS_CLOSE);
> +
> if (time_to_inject(sbi, FAULT_WRITE_IO))
> bio->bi_status = BLK_STS_IOERR;
>
> @@ -360,10 +363,12 @@ static void f2fs_write_end_io(struct bio *bio)
> f2fs_del_fsync_node_entry(sbi, folio);
> folio_clear_f2fs_gcing(folio);
> folio_end_writeback(folio);
> - }
> - if (!get_pages(sbi, F2FS_WB_CP_DATA) &&
> +
> + if (!is_close && type == F2FS_WB_CP_DATA &&
> + !get_pages(sbi, F2FS_WB_CP_DATA) &&
> wq_has_sleeper(&sbi->cp_wait))
> - wake_up(&sbi->cp_wait);
> + wake_up(&sbi->cp_wait);
> + }
Do we still need above change? due to below change may guarantee sbi
won't be released before soft-irq completion?
Thanks,
>
> bio_put(bio);
> }
> diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
> index c4c225e09dc4..924bc30d08b6 100644
> --- a/fs/f2fs/super.c
> +++ b/fs/f2fs/super.c
> @@ -5454,6 +5454,8 @@ static void kill_f2fs_super(struct super_block *sb)
> kill_block_super(sb);
> /* Release block devices last, after fscrypt_destroy_keyring(). */
> if (sbi) {
> + f2fs_wait_on_all_pages(sbi, F2FS_WB_CP_DATA);
> + synchronize_rcu();
> destroy_device_list(sbi);
> kfree(sbi);
> sb->s_fs_info = NULL;
© 2016 - 2026 Red Hat, Inc.