[PATCH] ext4: fix inode use after free in ext4_end_io_rsv_work()

libaokun@huaweicloud.com posted 1 patch 3 months ago
fs/ext4/page-io.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
[PATCH] ext4: fix inode use after free in ext4_end_io_rsv_work()
Posted by libaokun@huaweicloud.com 3 months ago
From: Baokun Li <libaokun1@huawei.com>

In ext4_io_end_defer_completion(), check if io_end->list_vec is empty to
avoid adding an io_end that requires no conversion to the
i_rsv_conversion_list, which in turn prevents starting an unnecessary
worker. An ext4_emergency_state() check is also added to avoid attempting
to abort the journal in an emergency state.

Additionally, ext4_put_io_end_defer() is refactored to call
ext4_io_end_defer_completion() directly instead of being open-coded.
This also prevents starting an unnecessary worker when EXT4_IO_END_FAILED
is set but data_err=abort is not enabled.

This ensures that the check in ext4_put_io_end_defer() is consistent with
the check in ext4_end_bio(). Otherwise, we might add an io_end to the
i_rsv_conversion_list and then call ext4_finish_bio(), after which the
inode could be freed before ext4_end_io_rsv_work() is called, triggering
a use-after-free issue.

Fixes: ce51afb8cc5e ("ext4: abort journal on data writeback failure if in data_err=abort mode")
Signed-off-by: Baokun Li <libaokun1@huawei.com>
---
 fs/ext4/page-io.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
index 179e54f3a3b6..3d8b0f6d2dea 100644
--- a/fs/ext4/page-io.c
+++ b/fs/ext4/page-io.c
@@ -236,10 +236,12 @@ static void dump_completed_IO(struct inode *inode, struct list_head *head)
 
 static bool ext4_io_end_defer_completion(ext4_io_end_t *io_end)
 {
-	if (io_end->flag & EXT4_IO_END_UNWRITTEN)
+	if (io_end->flag & EXT4_IO_END_UNWRITTEN &&
+	    !list_empty(&io_end->list_vec))
 		return true;
 	if (test_opt(io_end->inode->i_sb, DATA_ERR_ABORT) &&
-	    io_end->flag & EXT4_IO_END_FAILED)
+	    io_end->flag & EXT4_IO_END_FAILED &&
+	    !ext4_emergency_state(io_end->inode->i_sb))
 		return true;
 	return false;
 }
@@ -256,6 +258,7 @@ static void ext4_add_complete_io(ext4_io_end_t *io_end)
 	WARN_ON(!(io_end->flag & EXT4_IO_END_DEFER_COMPLETION));
 	WARN_ON(io_end->flag & EXT4_IO_END_UNWRITTEN &&
 		!io_end->handle && sbi->s_journal);
+	WARN_ON(!io_end->bio);
 
 	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
 	wq = sbi->rsv_conversion_wq;
@@ -318,12 +321,9 @@ ext4_io_end_t *ext4_init_io_end(struct inode *inode, gfp_t flags)
 void ext4_put_io_end_defer(ext4_io_end_t *io_end)
 {
 	if (refcount_dec_and_test(&io_end->count)) {
-		if (io_end->flag & EXT4_IO_END_FAILED ||
-		    (io_end->flag & EXT4_IO_END_UNWRITTEN &&
-		     !list_empty(&io_end->list_vec))) {
-			ext4_add_complete_io(io_end);
-			return;
-		}
+		if (ext4_io_end_defer_completion(io_end))
+			return ext4_add_complete_io(io_end);
+
 		ext4_release_io_end(io_end);
 	}
 }
-- 
2.46.1
Re: [PATCH] ext4: fix inode use after free in ext4_end_io_rsv_work()
Posted by Theodore Ts'o 2 months, 2 weeks ago
On Tue, 08 Jul 2025 19:15:04 +0800, libaokun@huaweicloud.com wrote:
> In ext4_io_end_defer_completion(), check if io_end->list_vec is empty to
> avoid adding an io_end that requires no conversion to the
> i_rsv_conversion_list, which in turn prevents starting an unnecessary
> worker. An ext4_emergency_state() check is also added to avoid attempting
> to abort the journal in an emergency state.
> 
> Additionally, ext4_put_io_end_defer() is refactored to call
> ext4_io_end_defer_completion() directly instead of being open-coded.
> This also prevents starting an unnecessary worker when EXT4_IO_END_FAILED
> is set but data_err=abort is not enabled.
> 
> [...]

Applied, thanks!

[1/1] ext4: fix inode use after free in ext4_end_io_rsv_work()
      commit: c678bdc998754589cea2e6afab9401d7d8312ac4

Best regards,
-- 
Theodore Ts'o <tytso@mit.edu>
Re: [PATCH] ext4: fix inode use after free in ext4_end_io_rsv_work()
Posted by Zhang Yi 2 months, 4 weeks ago
On 2025/7/8 19:15, libaokun@huaweicloud.com wrote:
> From: Baokun Li <libaokun1@huawei.com>
> 
> In ext4_io_end_defer_completion(), check if io_end->list_vec is empty to
> avoid adding an io_end that requires no conversion to the
> i_rsv_conversion_list, which in turn prevents starting an unnecessary
> worker. An ext4_emergency_state() check is also added to avoid attempting
> to abort the journal in an emergency state.
> 
> Additionally, ext4_put_io_end_defer() is refactored to call
> ext4_io_end_defer_completion() directly instead of being open-coded.
> This also prevents starting an unnecessary worker when EXT4_IO_END_FAILED
> is set but data_err=abort is not enabled.
> 
> This ensures that the check in ext4_put_io_end_defer() is consistent with
> the check in ext4_end_bio(). Otherwise, we might add an io_end to the
> i_rsv_conversion_list and then call ext4_finish_bio(), after which the
> inode could be freed before ext4_end_io_rsv_work() is called, triggering
> a use-after-free issue.
> 
> Fixes: ce51afb8cc5e ("ext4: abort journal on data writeback failure if in data_err=abort mode")
> Signed-off-by: Baokun Li <libaokun1@huawei.com>

Looks good to me.

Reviewed-by: Zhang Yi <yi.zhang@huawei.com>

> ---
>  fs/ext4/page-io.c | 16 ++++++++--------
>  1 file changed, 8 insertions(+), 8 deletions(-)
> 
> diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> index 179e54f3a3b6..3d8b0f6d2dea 100644
> --- a/fs/ext4/page-io.c
> +++ b/fs/ext4/page-io.c
> @@ -236,10 +236,12 @@ static void dump_completed_IO(struct inode *inode, struct list_head *head)
>  
>  static bool ext4_io_end_defer_completion(ext4_io_end_t *io_end)
>  {
> -	if (io_end->flag & EXT4_IO_END_UNWRITTEN)
> +	if (io_end->flag & EXT4_IO_END_UNWRITTEN &&
> +	    !list_empty(&io_end->list_vec))
>  		return true;
>  	if (test_opt(io_end->inode->i_sb, DATA_ERR_ABORT) &&
> -	    io_end->flag & EXT4_IO_END_FAILED)
> +	    io_end->flag & EXT4_IO_END_FAILED &&
> +	    !ext4_emergency_state(io_end->inode->i_sb))
>  		return true;
>  	return false;
>  }
> @@ -256,6 +258,7 @@ static void ext4_add_complete_io(ext4_io_end_t *io_end)
>  	WARN_ON(!(io_end->flag & EXT4_IO_END_DEFER_COMPLETION));
>  	WARN_ON(io_end->flag & EXT4_IO_END_UNWRITTEN &&
>  		!io_end->handle && sbi->s_journal);
> +	WARN_ON(!io_end->bio);
>  
>  	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
>  	wq = sbi->rsv_conversion_wq;
> @@ -318,12 +321,9 @@ ext4_io_end_t *ext4_init_io_end(struct inode *inode, gfp_t flags)
>  void ext4_put_io_end_defer(ext4_io_end_t *io_end)
>  {
>  	if (refcount_dec_and_test(&io_end->count)) {
> -		if (io_end->flag & EXT4_IO_END_FAILED ||
> -		    (io_end->flag & EXT4_IO_END_UNWRITTEN &&
> -		     !list_empty(&io_end->list_vec))) {
> -			ext4_add_complete_io(io_end);
> -			return;
> -		}
> +		if (ext4_io_end_defer_completion(io_end))
> +			return ext4_add_complete_io(io_end);
> +
>  		ext4_release_io_end(io_end);
>  	}
>  }
Re: [PATCH] ext4: fix inode use after free in ext4_end_io_rsv_work()
Posted by Jan Kara 3 months ago
On Tue 08-07-25 19:15:04, libaokun@huaweicloud.com wrote:
> From: Baokun Li <libaokun1@huawei.com>
> 
> In ext4_io_end_defer_completion(), check if io_end->list_vec is empty to
> avoid adding an io_end that requires no conversion to the
> i_rsv_conversion_list, which in turn prevents starting an unnecessary
> worker. An ext4_emergency_state() check is also added to avoid attempting
> to abort the journal in an emergency state.
> 
> Additionally, ext4_put_io_end_defer() is refactored to call
> ext4_io_end_defer_completion() directly instead of being open-coded.
> This also prevents starting an unnecessary worker when EXT4_IO_END_FAILED
> is set but data_err=abort is not enabled.
> 
> This ensures that the check in ext4_put_io_end_defer() is consistent with
> the check in ext4_end_bio(). Otherwise, we might add an io_end to the
> i_rsv_conversion_list and then call ext4_finish_bio(), after which the
> inode could be freed before ext4_end_io_rsv_work() is called, triggering
> a use-after-free issue.
> 
> Fixes: ce51afb8cc5e ("ext4: abort journal on data writeback failure if in data_err=abort mode")
> Signed-off-by: Baokun Li <libaokun1@huawei.com>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  fs/ext4/page-io.c | 16 ++++++++--------
>  1 file changed, 8 insertions(+), 8 deletions(-)
> 
> diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> index 179e54f3a3b6..3d8b0f6d2dea 100644
> --- a/fs/ext4/page-io.c
> +++ b/fs/ext4/page-io.c
> @@ -236,10 +236,12 @@ static void dump_completed_IO(struct inode *inode, struct list_head *head)
>  
>  static bool ext4_io_end_defer_completion(ext4_io_end_t *io_end)
>  {
> -	if (io_end->flag & EXT4_IO_END_UNWRITTEN)
> +	if (io_end->flag & EXT4_IO_END_UNWRITTEN &&
> +	    !list_empty(&io_end->list_vec))
>  		return true;
>  	if (test_opt(io_end->inode->i_sb, DATA_ERR_ABORT) &&
> -	    io_end->flag & EXT4_IO_END_FAILED)
> +	    io_end->flag & EXT4_IO_END_FAILED &&
> +	    !ext4_emergency_state(io_end->inode->i_sb))
>  		return true;
>  	return false;
>  }
> @@ -256,6 +258,7 @@ static void ext4_add_complete_io(ext4_io_end_t *io_end)
>  	WARN_ON(!(io_end->flag & EXT4_IO_END_DEFER_COMPLETION));
>  	WARN_ON(io_end->flag & EXT4_IO_END_UNWRITTEN &&
>  		!io_end->handle && sbi->s_journal);
> +	WARN_ON(!io_end->bio);
>  
>  	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
>  	wq = sbi->rsv_conversion_wq;
> @@ -318,12 +321,9 @@ ext4_io_end_t *ext4_init_io_end(struct inode *inode, gfp_t flags)
>  void ext4_put_io_end_defer(ext4_io_end_t *io_end)
>  {
>  	if (refcount_dec_and_test(&io_end->count)) {
> -		if (io_end->flag & EXT4_IO_END_FAILED ||
> -		    (io_end->flag & EXT4_IO_END_UNWRITTEN &&
> -		     !list_empty(&io_end->list_vec))) {
> -			ext4_add_complete_io(io_end);
> -			return;
> -		}
> +		if (ext4_io_end_defer_completion(io_end))
> +			return ext4_add_complete_io(io_end);
> +
>  		ext4_release_io_end(io_end);
>  	}
>  }
> -- 
> 2.46.1
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR