[PATCH v3] f2fs: compress: fix UAF of f2fs_inode_info in f2fs_free_dic

Zhiguo Niu posted 1 patch 6 months, 2 weeks ago
fs/f2fs/compress.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
[PATCH v3] f2fs: compress: fix UAF of f2fs_inode_info in f2fs_free_dic
Posted by Zhiguo Niu 6 months, 2 weeks ago
The decompress_io_ctx may be released asynchronously after
I/O completion. If this file is deleted immediately after read,
and the kworker of processing post_read_wq has not been executed yet
due to high workloads, It is possible that the inode(f2fs_inode_info)
is evicted and freed before it is used f2fs_free_dic.

    The UAF case as below:
    Thread A                                      Thread B
    - f2fs_decompress_end_io
     - f2fs_put_dic
      - queue_work
        add free_dic work to post_read_wq
                                                   - do_unlink
                                                    - iput
                                                     - evict
                                                      - call_rcu
    This file is deleted after read.

    Thread C                                 kworker to process post_read_wq
    - rcu_do_batch
     - f2fs_free_inode
      - kmem_cache_free
     inode is freed by rcu
                                             - process_scheduled_works
                                              - f2fs_late_free_dic
                                               - f2fs_free_dic
                                                - f2fs_release_decomp_mem
                                      read (dic->inode)->i_compress_algorithm

This patch use igrab before f2fs_free_dic and iput after free the dic when dic free
action is done by kworker.

Cc: Daeho Jeong <daehojeong@google.com>
Fixes: bff139b49d9f ("f2fs: handle decompress only post processing in softirq")
Signed-off-by: Zhiguo Niu <zhiguo.niu@unisoc.com>
Signed-off-by: Baocong Liu <baocong.liu@unisoc.com>
---
v3: use igrab to replace __iget
v2: use __iget/iput function
---
 fs/f2fs/compress.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
index b3c1df9..729ad16 100644
--- a/fs/f2fs/compress.c
+++ b/fs/f2fs/compress.c
@@ -1687,7 +1687,7 @@ static void f2fs_release_decomp_mem(struct decompress_io_ctx *dic,
 }
 
 static void f2fs_free_dic(struct decompress_io_ctx *dic,
-		bool bypass_destroy_callback);
+		bool bypass_destroy_callback, bool late_free);
 
 struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
 {
@@ -1743,12 +1743,12 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
 	return dic;
 
 out_free:
-	f2fs_free_dic(dic, true);
+	f2fs_free_dic(dic, true, false);
 	return ERR_PTR(ret);
 }
 
 static void f2fs_free_dic(struct decompress_io_ctx *dic,
-		bool bypass_destroy_callback)
+		bool bypass_destroy_callback, bool late_free)
 {
 	int i;
 
@@ -1775,6 +1775,8 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
 	}
 
 	page_array_free(dic->inode, dic->rpages, dic->nr_rpages);
+	if (late_free)
+		iput(dic->inode);
 	kmem_cache_free(dic_entry_slab, dic);
 }
 
@@ -1783,16 +1785,18 @@ static void f2fs_late_free_dic(struct work_struct *work)
 	struct decompress_io_ctx *dic =
 		container_of(work, struct decompress_io_ctx, free_work);
 
-	f2fs_free_dic(dic, false);
+	f2fs_free_dic(dic, false, true);
 }
 
 static void f2fs_put_dic(struct decompress_io_ctx *dic, bool in_task)
 {
 	if (refcount_dec_and_test(&dic->refcnt)) {
 		if (in_task) {
-			f2fs_free_dic(dic, false);
+			f2fs_free_dic(dic, false, false);
 		} else {
 			INIT_WORK(&dic->free_work, f2fs_late_free_dic);
+			/* use igrab to avoid inode is evicted simultaneously */
+			f2fs_bug_on(F2FS_I_SB(dic->inode), !igrab(dic->inode));
 			queue_work(F2FS_I_SB(dic->inode)->post_read_wq,
 					&dic->free_work);
 		}
-- 
1.9.1
Re: [PATCH v3] f2fs: compress: fix UAF of f2fs_inode_info in f2fs_free_dic
Posted by Jaegeuk Kim 6 months, 1 week ago
Hi Zhiguo,

This patch causes CPU hang when running fsstress on compressed/non-compressed
files. Please check.

On 06/05, Zhiguo Niu wrote:
> The decompress_io_ctx may be released asynchronously after
> I/O completion. If this file is deleted immediately after read,
> and the kworker of processing post_read_wq has not been executed yet
> due to high workloads, It is possible that the inode(f2fs_inode_info)
> is evicted and freed before it is used f2fs_free_dic.
> 
>     The UAF case as below:
>     Thread A                                      Thread B
>     - f2fs_decompress_end_io
>      - f2fs_put_dic
>       - queue_work
>         add free_dic work to post_read_wq
>                                                    - do_unlink
>                                                     - iput
>                                                      - evict
>                                                       - call_rcu
>     This file is deleted after read.
> 
>     Thread C                                 kworker to process post_read_wq
>     - rcu_do_batch
>      - f2fs_free_inode
>       - kmem_cache_free
>      inode is freed by rcu
>                                              - process_scheduled_works
>                                               - f2fs_late_free_dic
>                                                - f2fs_free_dic
>                                                 - f2fs_release_decomp_mem
>                                       read (dic->inode)->i_compress_algorithm
> 
> This patch use igrab before f2fs_free_dic and iput after free the dic when dic free
> action is done by kworker.
> 
> Cc: Daeho Jeong <daehojeong@google.com>
> Fixes: bff139b49d9f ("f2fs: handle decompress only post processing in softirq")
> Signed-off-by: Zhiguo Niu <zhiguo.niu@unisoc.com>
> Signed-off-by: Baocong Liu <baocong.liu@unisoc.com>
> ---
> v3: use igrab to replace __iget
> v2: use __iget/iput function
> ---
>  fs/f2fs/compress.c | 14 +++++++++-----
>  1 file changed, 9 insertions(+), 5 deletions(-)
> 
> diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
> index b3c1df9..729ad16 100644
> --- a/fs/f2fs/compress.c
> +++ b/fs/f2fs/compress.c
> @@ -1687,7 +1687,7 @@ static void f2fs_release_decomp_mem(struct decompress_io_ctx *dic,
>  }
>  
>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
> -		bool bypass_destroy_callback);
> +		bool bypass_destroy_callback, bool late_free);
>  
>  struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
>  {
> @@ -1743,12 +1743,12 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
>  	return dic;
>  
>  out_free:
> -	f2fs_free_dic(dic, true);
> +	f2fs_free_dic(dic, true, false);
>  	return ERR_PTR(ret);
>  }
>  
>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
> -		bool bypass_destroy_callback)
> +		bool bypass_destroy_callback, bool late_free)
>  {
>  	int i;
>  
> @@ -1775,6 +1775,8 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
>  	}
>  
>  	page_array_free(dic->inode, dic->rpages, dic->nr_rpages);
> +	if (late_free)
> +		iput(dic->inode);
>  	kmem_cache_free(dic_entry_slab, dic);
>  }
>  
> @@ -1783,16 +1785,18 @@ static void f2fs_late_free_dic(struct work_struct *work)
>  	struct decompress_io_ctx *dic =
>  		container_of(work, struct decompress_io_ctx, free_work);
>  
> -	f2fs_free_dic(dic, false);
> +	f2fs_free_dic(dic, false, true);
>  }
>  
>  static void f2fs_put_dic(struct decompress_io_ctx *dic, bool in_task)
>  {
>  	if (refcount_dec_and_test(&dic->refcnt)) {
>  		if (in_task) {
> -			f2fs_free_dic(dic, false);
> +			f2fs_free_dic(dic, false, false);
>  		} else {
>  			INIT_WORK(&dic->free_work, f2fs_late_free_dic);
> +			/* use igrab to avoid inode is evicted simultaneously */
> +			f2fs_bug_on(F2FS_I_SB(dic->inode), !igrab(dic->inode));
>  			queue_work(F2FS_I_SB(dic->inode)->post_read_wq,
>  					&dic->free_work);
>  		}
> -- 
> 1.9.1
Re: [PATCH v3] f2fs: compress: fix UAF of f2fs_inode_info in f2fs_free_dic
Posted by Chao Yu 6 months, 1 week ago
On 6/11/25 00:08, Jaegeuk Kim wrote:
> Hi Zhiguo,
> 
> This patch causes CPU hang when running fsstress on compressed/non-compressed
> files. Please check.

Oh, seems it may cause below deadlock:

CPU0
process A
- spin_lock(i_lock)
software IRQ
- end_io
 - igrab
  - spin_lock(i_lock)

Thanks,

> 
> On 06/05, Zhiguo Niu wrote:
>> The decompress_io_ctx may be released asynchronously after
>> I/O completion. If this file is deleted immediately after read,
>> and the kworker of processing post_read_wq has not been executed yet
>> due to high workloads, It is possible that the inode(f2fs_inode_info)
>> is evicted and freed before it is used f2fs_free_dic.
>>
>>     The UAF case as below:
>>     Thread A                                      Thread B
>>     - f2fs_decompress_end_io
>>      - f2fs_put_dic
>>       - queue_work
>>         add free_dic work to post_read_wq
>>                                                    - do_unlink
>>                                                     - iput
>>                                                      - evict
>>                                                       - call_rcu
>>     This file is deleted after read.
>>
>>     Thread C                                 kworker to process post_read_wq
>>     - rcu_do_batch
>>      - f2fs_free_inode
>>       - kmem_cache_free
>>      inode is freed by rcu
>>                                              - process_scheduled_works
>>                                               - f2fs_late_free_dic
>>                                                - f2fs_free_dic
>>                                                 - f2fs_release_decomp_mem
>>                                       read (dic->inode)->i_compress_algorithm
>>
>> This patch use igrab before f2fs_free_dic and iput after free the dic when dic free
>> action is done by kworker.
>>
>> Cc: Daeho Jeong <daehojeong@google.com>
>> Fixes: bff139b49d9f ("f2fs: handle decompress only post processing in softirq")
>> Signed-off-by: Zhiguo Niu <zhiguo.niu@unisoc.com>
>> Signed-off-by: Baocong Liu <baocong.liu@unisoc.com>
>> ---
>> v3: use igrab to replace __iget
>> v2: use __iget/iput function
>> ---
>>  fs/f2fs/compress.c | 14 +++++++++-----
>>  1 file changed, 9 insertions(+), 5 deletions(-)
>>
>> diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
>> index b3c1df9..729ad16 100644
>> --- a/fs/f2fs/compress.c
>> +++ b/fs/f2fs/compress.c
>> @@ -1687,7 +1687,7 @@ static void f2fs_release_decomp_mem(struct decompress_io_ctx *dic,
>>  }
>>  
>>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
>> -		bool bypass_destroy_callback);
>> +		bool bypass_destroy_callback, bool late_free);
>>  
>>  struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
>>  {
>> @@ -1743,12 +1743,12 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
>>  	return dic;
>>  
>>  out_free:
>> -	f2fs_free_dic(dic, true);
>> +	f2fs_free_dic(dic, true, false);
>>  	return ERR_PTR(ret);
>>  }
>>  
>>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
>> -		bool bypass_destroy_callback)
>> +		bool bypass_destroy_callback, bool late_free)
>>  {
>>  	int i;
>>  
>> @@ -1775,6 +1775,8 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
>>  	}
>>  
>>  	page_array_free(dic->inode, dic->rpages, dic->nr_rpages);
>> +	if (late_free)
>> +		iput(dic->inode);
>>  	kmem_cache_free(dic_entry_slab, dic);
>>  }
>>  
>> @@ -1783,16 +1785,18 @@ static void f2fs_late_free_dic(struct work_struct *work)
>>  	struct decompress_io_ctx *dic =
>>  		container_of(work, struct decompress_io_ctx, free_work);
>>  
>> -	f2fs_free_dic(dic, false);
>> +	f2fs_free_dic(dic, false, true);
>>  }
>>  
>>  static void f2fs_put_dic(struct decompress_io_ctx *dic, bool in_task)
>>  {
>>  	if (refcount_dec_and_test(&dic->refcnt)) {
>>  		if (in_task) {
>> -			f2fs_free_dic(dic, false);
>> +			f2fs_free_dic(dic, false, false);
>>  		} else {
>>  			INIT_WORK(&dic->free_work, f2fs_late_free_dic);
>> +			/* use igrab to avoid inode is evicted simultaneously */
>> +			f2fs_bug_on(F2FS_I_SB(dic->inode), !igrab(dic->inode));
>>  			queue_work(F2FS_I_SB(dic->inode)->post_read_wq,
>>  					&dic->free_work);
>>  		}
>> -- 
>> 1.9.1
Re: [PATCH v3] f2fs: compress: fix UAF of f2fs_inode_info in f2fs_free_dic
Posted by Zhiguo Niu 6 months, 1 week ago
Chao Yu <chao@kernel.org> 于2025年6月11日周三 14:07写道:
>
> On 6/11/25 00:08, Jaegeuk Kim wrote:
> > Hi Zhiguo,
> >
> > This patch causes CPU hang when running fsstress on compressed/non-compressed
> > files. Please check.
>
> Oh, seems it may cause below deadlock:
>
> CPU0
> process A
> - spin_lock(i_lock)
> software IRQ
> - end_io
>  - igrab
>   - spin_lock(i_lock)
>
> Thanks,
Hi Chao,
Thanks for pointing this out.
I have tested this patch locally about some basic cases before submission.
So it seems that should use the following method  to solve this problem?
" store i_compress_algorithm/sbi in dic to avoid inode access?"
thanks!


>
> >
> > On 06/05, Zhiguo Niu wrote:
> >> The decompress_io_ctx may be released asynchronously after
> >> I/O completion. If this file is deleted immediately after read,
> >> and the kworker of processing post_read_wq has not been executed yet
> >> due to high workloads, It is possible that the inode(f2fs_inode_info)
> >> is evicted and freed before it is used f2fs_free_dic.
> >>
> >>     The UAF case as below:
> >>     Thread A                                      Thread B
> >>     - f2fs_decompress_end_io
> >>      - f2fs_put_dic
> >>       - queue_work
> >>         add free_dic work to post_read_wq
> >>                                                    - do_unlink
> >>                                                     - iput
> >>                                                      - evict
> >>                                                       - call_rcu
> >>     This file is deleted after read.
> >>
> >>     Thread C                                 kworker to process post_read_wq
> >>     - rcu_do_batch
> >>      - f2fs_free_inode
> >>       - kmem_cache_free
> >>      inode is freed by rcu
> >>                                              - process_scheduled_works
> >>                                               - f2fs_late_free_dic
> >>                                                - f2fs_free_dic
> >>                                                 - f2fs_release_decomp_mem
> >>                                       read (dic->inode)->i_compress_algorithm
> >>
> >> This patch use igrab before f2fs_free_dic and iput after free the dic when dic free
> >> action is done by kworker.
> >>
> >> Cc: Daeho Jeong <daehojeong@google.com>
> >> Fixes: bff139b49d9f ("f2fs: handle decompress only post processing in softirq")
> >> Signed-off-by: Zhiguo Niu <zhiguo.niu@unisoc.com>
> >> Signed-off-by: Baocong Liu <baocong.liu@unisoc.com>
> >> ---
> >> v3: use igrab to replace __iget
> >> v2: use __iget/iput function
> >> ---
> >>  fs/f2fs/compress.c | 14 +++++++++-----
> >>  1 file changed, 9 insertions(+), 5 deletions(-)
> >>
> >> diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
> >> index b3c1df9..729ad16 100644
> >> --- a/fs/f2fs/compress.c
> >> +++ b/fs/f2fs/compress.c
> >> @@ -1687,7 +1687,7 @@ static void f2fs_release_decomp_mem(struct decompress_io_ctx *dic,
> >>  }
> >>
> >>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
> >> -            bool bypass_destroy_callback);
> >> +            bool bypass_destroy_callback, bool late_free);
> >>
> >>  struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
> >>  {
> >> @@ -1743,12 +1743,12 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
> >>      return dic;
> >>
> >>  out_free:
> >> -    f2fs_free_dic(dic, true);
> >> +    f2fs_free_dic(dic, true, false);
> >>      return ERR_PTR(ret);
> >>  }
> >>
> >>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
> >> -            bool bypass_destroy_callback)
> >> +            bool bypass_destroy_callback, bool late_free)
> >>  {
> >>      int i;
> >>
> >> @@ -1775,6 +1775,8 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
> >>      }
> >>
> >>      page_array_free(dic->inode, dic->rpages, dic->nr_rpages);
> >> +    if (late_free)
> >> +            iput(dic->inode);
> >>      kmem_cache_free(dic_entry_slab, dic);
> >>  }
> >>
> >> @@ -1783,16 +1785,18 @@ static void f2fs_late_free_dic(struct work_struct *work)
> >>      struct decompress_io_ctx *dic =
> >>              container_of(work, struct decompress_io_ctx, free_work);
> >>
> >> -    f2fs_free_dic(dic, false);
> >> +    f2fs_free_dic(dic, false, true);
> >>  }
> >>
> >>  static void f2fs_put_dic(struct decompress_io_ctx *dic, bool in_task)
> >>  {
> >>      if (refcount_dec_and_test(&dic->refcnt)) {
> >>              if (in_task) {
> >> -                    f2fs_free_dic(dic, false);
> >> +                    f2fs_free_dic(dic, false, false);
> >>              } else {
> >>                      INIT_WORK(&dic->free_work, f2fs_late_free_dic);
> >> +                    /* use igrab to avoid inode is evicted simultaneously */
> >> +                    f2fs_bug_on(F2FS_I_SB(dic->inode), !igrab(dic->inode));
> >>                      queue_work(F2FS_I_SB(dic->inode)->post_read_wq,
> >>                                      &dic->free_work);
> >>              }
> >> --
> >> 1.9.1
>
Re: [PATCH v3] f2fs: compress: fix UAF of f2fs_inode_info in f2fs_free_dic
Posted by Chao Yu 6 months, 1 week ago
On 6/11/25 14:41, Zhiguo Niu wrote:
> Chao Yu <chao@kernel.org> 于2025年6月11日周三 14:07写道:
>>
>> On 6/11/25 00:08, Jaegeuk Kim wrote:
>>> Hi Zhiguo,
>>>
>>> This patch causes CPU hang when running fsstress on compressed/non-compressed
>>> files. Please check.
>>
>> Oh, seems it may cause below deadlock:
>>
>> CPU0
>> process A
>> - spin_lock(i_lock)
>> software IRQ
>> - end_io
>>  - igrab
>>   - spin_lock(i_lock)
>>
>> Thanks,
> Hi Chao,
> Thanks for pointing this out.
> I have tested this patch locally about some basic cases before submission.
> So it seems that should use the following method  to solve this problem?
> " store i_compress_algorithm/sbi in dic to avoid inode access?"

Zhiguo,

Yeah, I guess so.

Thanks,

> thanks!
> 
> 
>>
>>>
>>> On 06/05, Zhiguo Niu wrote:
>>>> The decompress_io_ctx may be released asynchronously after
>>>> I/O completion. If this file is deleted immediately after read,
>>>> and the kworker of processing post_read_wq has not been executed yet
>>>> due to high workloads, It is possible that the inode(f2fs_inode_info)
>>>> is evicted and freed before it is used f2fs_free_dic.
>>>>
>>>>     The UAF case as below:
>>>>     Thread A                                      Thread B
>>>>     - f2fs_decompress_end_io
>>>>      - f2fs_put_dic
>>>>       - queue_work
>>>>         add free_dic work to post_read_wq
>>>>                                                    - do_unlink
>>>>                                                     - iput
>>>>                                                      - evict
>>>>                                                       - call_rcu
>>>>     This file is deleted after read.
>>>>
>>>>     Thread C                                 kworker to process post_read_wq
>>>>     - rcu_do_batch
>>>>      - f2fs_free_inode
>>>>       - kmem_cache_free
>>>>      inode is freed by rcu
>>>>                                              - process_scheduled_works
>>>>                                               - f2fs_late_free_dic
>>>>                                                - f2fs_free_dic
>>>>                                                 - f2fs_release_decomp_mem
>>>>                                       read (dic->inode)->i_compress_algorithm
>>>>
>>>> This patch use igrab before f2fs_free_dic and iput after free the dic when dic free
>>>> action is done by kworker.
>>>>
>>>> Cc: Daeho Jeong <daehojeong@google.com>
>>>> Fixes: bff139b49d9f ("f2fs: handle decompress only post processing in softirq")
>>>> Signed-off-by: Zhiguo Niu <zhiguo.niu@unisoc.com>
>>>> Signed-off-by: Baocong Liu <baocong.liu@unisoc.com>
>>>> ---
>>>> v3: use igrab to replace __iget
>>>> v2: use __iget/iput function
>>>> ---
>>>>  fs/f2fs/compress.c | 14 +++++++++-----
>>>>  1 file changed, 9 insertions(+), 5 deletions(-)
>>>>
>>>> diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
>>>> index b3c1df9..729ad16 100644
>>>> --- a/fs/f2fs/compress.c
>>>> +++ b/fs/f2fs/compress.c
>>>> @@ -1687,7 +1687,7 @@ static void f2fs_release_decomp_mem(struct decompress_io_ctx *dic,
>>>>  }
>>>>
>>>>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
>>>> -            bool bypass_destroy_callback);
>>>> +            bool bypass_destroy_callback, bool late_free);
>>>>
>>>>  struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
>>>>  {
>>>> @@ -1743,12 +1743,12 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
>>>>      return dic;
>>>>
>>>>  out_free:
>>>> -    f2fs_free_dic(dic, true);
>>>> +    f2fs_free_dic(dic, true, false);
>>>>      return ERR_PTR(ret);
>>>>  }
>>>>
>>>>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
>>>> -            bool bypass_destroy_callback)
>>>> +            bool bypass_destroy_callback, bool late_free)
>>>>  {
>>>>      int i;
>>>>
>>>> @@ -1775,6 +1775,8 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
>>>>      }
>>>>
>>>>      page_array_free(dic->inode, dic->rpages, dic->nr_rpages);
>>>> +    if (late_free)
>>>> +            iput(dic->inode);
>>>>      kmem_cache_free(dic_entry_slab, dic);
>>>>  }
>>>>
>>>> @@ -1783,16 +1785,18 @@ static void f2fs_late_free_dic(struct work_struct *work)
>>>>      struct decompress_io_ctx *dic =
>>>>              container_of(work, struct decompress_io_ctx, free_work);
>>>>
>>>> -    f2fs_free_dic(dic, false);
>>>> +    f2fs_free_dic(dic, false, true);
>>>>  }
>>>>
>>>>  static void f2fs_put_dic(struct decompress_io_ctx *dic, bool in_task)
>>>>  {
>>>>      if (refcount_dec_and_test(&dic->refcnt)) {
>>>>              if (in_task) {
>>>> -                    f2fs_free_dic(dic, false);
>>>> +                    f2fs_free_dic(dic, false, false);
>>>>              } else {
>>>>                      INIT_WORK(&dic->free_work, f2fs_late_free_dic);
>>>> +                    /* use igrab to avoid inode is evicted simultaneously */
>>>> +                    f2fs_bug_on(F2FS_I_SB(dic->inode), !igrab(dic->inode));
>>>>                      queue_work(F2FS_I_SB(dic->inode)->post_read_wq,
>>>>                                      &dic->free_work);
>>>>              }
>>>> --
>>>> 1.9.1
>>

Re: [PATCH v3] f2fs: compress: fix UAF of f2fs_inode_info in f2fs_free_dic
Posted by Zhiguo Niu 6 months, 1 week ago
Chao Yu <chao@kernel.org> 于2025年6月11日周三 14:47写道:
>
> On 6/11/25 14:41, Zhiguo Niu wrote:
> > Chao Yu <chao@kernel.org> 于2025年6月11日周三 14:07写道:
> >>
> >> On 6/11/25 00:08, Jaegeuk Kim wrote:
> >>> Hi Zhiguo,
> >>>
> >>> This patch causes CPU hang when running fsstress on compressed/non-compressed
> >>> files. Please check.
> >>
> >> Oh, seems it may cause below deadlock:
> >>
> >> CPU0
> >> process A
> >> - spin_lock(i_lock)
> >> software IRQ
> >> - end_io
> >>  - igrab
> >>   - spin_lock(i_lock)
> >>
> >> Thanks,
> > Hi Chao,
> > Thanks for pointing this out.
> > I have tested this patch locally about some basic cases before submission.
> > So it seems that should use the following method  to solve this problem?
> > " store i_compress_algorithm/sbi in dic to avoid inode access?"
>
> Zhiguo,
>
> Yeah, I guess so.
Hi Chao,
OK, I will prepare it .
Thanks a lot.
>
> Thanks,
>
> > thanks!
> >
> >
> >>
> >>>
> >>> On 06/05, Zhiguo Niu wrote:
> >>>> The decompress_io_ctx may be released asynchronously after
> >>>> I/O completion. If this file is deleted immediately after read,
> >>>> and the kworker of processing post_read_wq has not been executed yet
> >>>> due to high workloads, It is possible that the inode(f2fs_inode_info)
> >>>> is evicted and freed before it is used f2fs_free_dic.
> >>>>
> >>>>     The UAF case as below:
> >>>>     Thread A                                      Thread B
> >>>>     - f2fs_decompress_end_io
> >>>>      - f2fs_put_dic
> >>>>       - queue_work
> >>>>         add free_dic work to post_read_wq
> >>>>                                                    - do_unlink
> >>>>                                                     - iput
> >>>>                                                      - evict
> >>>>                                                       - call_rcu
> >>>>     This file is deleted after read.
> >>>>
> >>>>     Thread C                                 kworker to process post_read_wq
> >>>>     - rcu_do_batch
> >>>>      - f2fs_free_inode
> >>>>       - kmem_cache_free
> >>>>      inode is freed by rcu
> >>>>                                              - process_scheduled_works
> >>>>                                               - f2fs_late_free_dic
> >>>>                                                - f2fs_free_dic
> >>>>                                                 - f2fs_release_decomp_mem
> >>>>                                       read (dic->inode)->i_compress_algorithm
> >>>>
> >>>> This patch use igrab before f2fs_free_dic and iput after free the dic when dic free
> >>>> action is done by kworker.
> >>>>
> >>>> Cc: Daeho Jeong <daehojeong@google.com>
> >>>> Fixes: bff139b49d9f ("f2fs: handle decompress only post processing in softirq")
> >>>> Signed-off-by: Zhiguo Niu <zhiguo.niu@unisoc.com>
> >>>> Signed-off-by: Baocong Liu <baocong.liu@unisoc.com>
> >>>> ---
> >>>> v3: use igrab to replace __iget
> >>>> v2: use __iget/iput function
> >>>> ---
> >>>>  fs/f2fs/compress.c | 14 +++++++++-----
> >>>>  1 file changed, 9 insertions(+), 5 deletions(-)
> >>>>
> >>>> diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
> >>>> index b3c1df9..729ad16 100644
> >>>> --- a/fs/f2fs/compress.c
> >>>> +++ b/fs/f2fs/compress.c
> >>>> @@ -1687,7 +1687,7 @@ static void f2fs_release_decomp_mem(struct decompress_io_ctx *dic,
> >>>>  }
> >>>>
> >>>>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
> >>>> -            bool bypass_destroy_callback);
> >>>> +            bool bypass_destroy_callback, bool late_free);
> >>>>
> >>>>  struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
> >>>>  {
> >>>> @@ -1743,12 +1743,12 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
> >>>>      return dic;
> >>>>
> >>>>  out_free:
> >>>> -    f2fs_free_dic(dic, true);
> >>>> +    f2fs_free_dic(dic, true, false);
> >>>>      return ERR_PTR(ret);
> >>>>  }
> >>>>
> >>>>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
> >>>> -            bool bypass_destroy_callback)
> >>>> +            bool bypass_destroy_callback, bool late_free)
> >>>>  {
> >>>>      int i;
> >>>>
> >>>> @@ -1775,6 +1775,8 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
> >>>>      }
> >>>>
> >>>>      page_array_free(dic->inode, dic->rpages, dic->nr_rpages);
> >>>> +    if (late_free)
> >>>> +            iput(dic->inode);
> >>>>      kmem_cache_free(dic_entry_slab, dic);
> >>>>  }
> >>>>
> >>>> @@ -1783,16 +1785,18 @@ static void f2fs_late_free_dic(struct work_struct *work)
> >>>>      struct decompress_io_ctx *dic =
> >>>>              container_of(work, struct decompress_io_ctx, free_work);
> >>>>
> >>>> -    f2fs_free_dic(dic, false);
> >>>> +    f2fs_free_dic(dic, false, true);
> >>>>  }
> >>>>
> >>>>  static void f2fs_put_dic(struct decompress_io_ctx *dic, bool in_task)
> >>>>  {
> >>>>      if (refcount_dec_and_test(&dic->refcnt)) {
> >>>>              if (in_task) {
> >>>> -                    f2fs_free_dic(dic, false);
> >>>> +                    f2fs_free_dic(dic, false, false);
> >>>>              } else {
> >>>>                      INIT_WORK(&dic->free_work, f2fs_late_free_dic);
> >>>> +                    /* use igrab to avoid inode is evicted simultaneously */
> >>>> +                    f2fs_bug_on(F2FS_I_SB(dic->inode), !igrab(dic->inode));
> >>>>                      queue_work(F2FS_I_SB(dic->inode)->post_read_wq,
> >>>>                                      &dic->free_work);
> >>>>              }
> >>>> --
> >>>> 1.9.1
> >>
>
Re: [PATCH v3] f2fs: compress: fix UAF of f2fs_inode_info in f2fs_free_dic
Posted by Zhiguo Niu 6 months, 1 week ago
Zhiguo Niu <niuzhiguo84@gmail.com> 于2025年6月11日周三 14:52写道:
>
> Chao Yu <chao@kernel.org> 于2025年6月11日周三 14:47写道:
> >
> > On 6/11/25 14:41, Zhiguo Niu wrote:
> > > Chao Yu <chao@kernel.org> 于2025年6月11日周三 14:07写道:
> > >>
> > >> On 6/11/25 00:08, Jaegeuk Kim wrote:
> > >>> Hi Zhiguo,
> > >>>
> > >>> This patch causes CPU hang when running fsstress on compressed/non-compressed
> > >>> files. Please check.
> > >>
> > >> Oh, seems it may cause below deadlock:
> > >>
> > >> CPU0
> > >> process A
> > >> - spin_lock(i_lock)
> > >> software IRQ
> > >> - end_io
> > >>  - igrab
> > >>   - spin_lock(i_lock)
> > >>
> > >> Thanks,
> > > Hi Chao,
> > > Thanks for pointing this out.
> > > I have tested this patch locally about some basic cases before submission.
> > > So it seems that should use the following method  to solve this problem?
> > > " store i_compress_algorithm/sbi in dic to avoid inode access?"
> >
> > Zhiguo,
> >
> > Yeah, I guess so.
> Hi Chao,
> OK, I will prepare it .
> Thanks a lot.
> >
> > Thanks,
> >
> > > thanks!
> > >
> > >
> > >>
> > >>>
> > >>> On 06/05, Zhiguo Niu wrote:
> > >>>> The decompress_io_ctx may be released asynchronously after
> > >>>> I/O completion. If this file is deleted immediately after read,
> > >>>> and the kworker of processing post_read_wq has not been executed yet
> > >>>> due to high workloads, It is possible that the inode(f2fs_inode_info)
> > >>>> is evicted and freed before it is used f2fs_free_dic.
> > >>>>
> > >>>>     The UAF case as below:
> > >>>>     Thread A                                      Thread B
> > >>>>     - f2fs_decompress_end_io
> > >>>>      - f2fs_put_dic
> > >>>>       - queue_work
> > >>>>         add free_dic work to post_read_wq
> > >>>>                                                    - do_unlink
> > >>>>                                                     - iput
> > >>>>                                                      - evict
> > >>>>                                                       - call_rcu
> > >>>>     This file is deleted after read.
> > >>>>
> > >>>>     Thread C                                 kworker to process post_read_wq
> > >>>>     - rcu_do_batch
> > >>>>      - f2fs_free_inode
> > >>>>       - kmem_cache_free
> > >>>>      inode is freed by rcu
> > >>>>                                              - process_scheduled_works
> > >>>>                                               - f2fs_late_free_dic
> > >>>>                                                - f2fs_free_dic
> > >>>>                                                 - f2fs_release_decomp_mem
> > >>>>                                       read (dic->inode)->i_compress_algorithm
> > >>>>
> > >>>> This patch use igrab before f2fs_free_dic and iput after free the dic when dic free
> > >>>> action is done by kworker.
> > >>>>
> > >>>> Cc: Daeho Jeong <daehojeong@google.com>
> > >>>> Fixes: bff139b49d9f ("f2fs: handle decompress only post processing in softirq")
> > >>>> Signed-off-by: Zhiguo Niu <zhiguo.niu@unisoc.com>
> > >>>> Signed-off-by: Baocong Liu <baocong.liu@unisoc.com>
> > >>>> ---
> > >>>> v3: use igrab to replace __iget
> > >>>> v2: use __iget/iput function
> > >>>> ---
> > >>>>  fs/f2fs/compress.c | 14 +++++++++-----
> > >>>>  1 file changed, 9 insertions(+), 5 deletions(-)
> > >>>>
> > >>>> diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
> > >>>> index b3c1df9..729ad16 100644
> > >>>> --- a/fs/f2fs/compress.c
> > >>>> +++ b/fs/f2fs/compress.c
> > >>>> @@ -1687,7 +1687,7 @@ static void f2fs_release_decomp_mem(struct decompress_io_ctx *dic,
> > >>>>  }
> > >>>>
> > >>>>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
> > >>>> -            bool bypass_destroy_callback);
> > >>>> +            bool bypass_destroy_callback, bool late_free);
> > >>>>
> > >>>>  struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
> > >>>>  {
> > >>>> @@ -1743,12 +1743,12 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
> > >>>>      return dic;
> > >>>>
> > >>>>  out_free:
> > >>>> -    f2fs_free_dic(dic, true);
> > >>>> +    f2fs_free_dic(dic, true, false);
> > >>>>      return ERR_PTR(ret);
> > >>>>  }
> > >>>>
> > >>>>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
> > >>>> -            bool bypass_destroy_callback)
> > >>>> +            bool bypass_destroy_callback, bool late_free)
> > >>>>  {
> > >>>>      int i;
> > >>>>
> > >>>> @@ -1775,6 +1775,8 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
> > >>>>      }
> > >>>>
> > >>>>      page_array_free(dic->inode, dic->rpages, dic->nr_rpages);
> > >>>> +    if (late_free)
> > >>>> +            iput(dic->inode);
> > >>>>      kmem_cache_free(dic_entry_slab, dic);
> > >>>>  }
> > >>>>
> > >>>> @@ -1783,16 +1785,18 @@ static void f2fs_late_free_dic(struct work_struct *work)
> > >>>>      struct decompress_io_ctx *dic =
> > >>>>              container_of(work, struct decompress_io_ctx, free_work);
> > >>>>
> > >>>> -    f2fs_free_dic(dic, false);
> > >>>> +    f2fs_free_dic(dic, false, true);
> > >>>>  }
> > >>>>
> > >>>>  static void f2fs_put_dic(struct decompress_io_ctx *dic, bool in_task)
> > >>>>  {
> > >>>>      if (refcount_dec_and_test(&dic->refcnt)) {
> > >>>>              if (in_task) {
> > >>>> -                    f2fs_free_dic(dic, false);
> > >>>> +                    f2fs_free_dic(dic, false, false);
> > >>>>              } else {
> > >>>>                      INIT_WORK(&dic->free_work, f2fs_late_free_dic);
> > >>>> +                    /* use igrab to avoid inode is evicted simultaneously */
> > >>>> +                    f2fs_bug_on(F2FS_I_SB(dic->inode), !igrab(dic->inode));
> > >>>>                      queue_work(F2FS_I_SB(dic->inode)->post_read_wq,
> > >>>>                                      &dic->free_work);
> > >>>>              }
> > >>>> --
> > >>>> 1.9.1
> > >>
> >

Hi Chao,

The patch is about as follows, because dic->sbi is used directly in
f2fs_free_dic: page_array_free(dic->sbi, dic->tpages, dic->cluster_size);
so there are two points I want to confirm:
1. As a corresponding, the first parameter (inode) of page_array_alloc
is need to modify to sbi or not ?
2. As a corresponding, do we need to add the sbi field in compress_ctx
so that page_array_alloc/free called
in compress flow can use sbi directly?
Thanks!

diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
index b3c1df9..897d8ae 100644
--- a/fs/f2fs/compress.c
+++ b/fs/f2fs/compress.c
@@ -34,9 +34,8 @@ static void *page_array_alloc(struct inode *inode, int nr)
        return f2fs_kzalloc(sbi, size, GFP_NOFS);
 }

-static void page_array_free(struct inode *inode, void *pages, int nr)
+static void page_array_free(struct f2fs_sb_info *sbi, void *pages, int nr)
 {
-       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
        unsigned int size = sizeof(struct page *) * nr;

        if (!pages)
@@ -155,7 +154,7 @@ int f2fs_init_compress_ctx(struct compress_ctx *cc)

 void f2fs_destroy_compress_ctx(struct compress_ctx *cc, bool reuse)
 {
-       page_array_free(cc->inode, cc->rpages, cc->cluster_size);
+       page_array_free(F2FS_I_SB(cc->inode), cc->rpages, cc->cluster_size);
        cc->rpages = NULL;
        cc->nr_rpages = 0;
        cc->nr_cpages = 0;
@@ -716,7 +715,7 @@ static int f2fs_compress_pages(struct compress_ctx *cc)
                if (cc->cpages[i])
                        f2fs_compress_free_page(cc->cpages[i]);
        }
-       page_array_free(cc->inode, cc->cpages, cc->nr_cpages);
+       page_array_free(F2FS_I_SB(cc->inode), cc->cpages, cc->nr_cpages);
        cc->cpages = NULL;
 destroy_compress_ctx:
        if (cops->destroy_compress_ctx)
@@ -734,7 +733,7 @@ static void f2fs_release_decomp_mem(struct
decompress_io_ctx *dic,

 void f2fs_decompress_cluster(struct decompress_io_ctx *dic, bool in_task)
 {
-       struct f2fs_sb_info *sbi = F2FS_I_SB(dic->inode);
+       struct f2fs_sb_info *sbi = dic->sbi;
        struct f2fs_inode_info *fi = F2FS_I(dic->inode);
        const struct f2fs_compress_ops *cops =
                        f2fs_cops[fi->i_compress_algorithm];
@@ -1442,13 +1441,13 @@ static int f2fs_write_compressed_pages(struct
compress_ctx *cc,
        spin_unlock(&fi->i_size_lock);

        f2fs_put_rpages(cc);
-       page_array_free(cc->inode, cc->cpages, cc->nr_cpages);
+       page_array_free(F2FS_I_SB(cc->inode), cc->cpages, cc->nr_cpages);
        cc->cpages = NULL;
        f2fs_destroy_compress_ctx(cc, false);
        return 0;

 out_destroy_crypt:
-       page_array_free(cc->inode, cic->rpages, cc->cluster_size);
+       page_array_free(F2FS_I_SB(cc->inode), cic->rpages, cc->cluster_size);

        for (--i; i >= 0; i--) {
                if (!cc->cpages[i])
@@ -1469,7 +1468,7 @@ static int f2fs_write_compressed_pages(struct
compress_ctx *cc,
                f2fs_compress_free_page(cc->cpages[i]);
                cc->cpages[i] = NULL;
        }
-       page_array_free(cc->inode, cc->cpages, cc->nr_cpages);
+       page_array_free(F2FS_I_SB(cc->inode), cc->cpages, cc->nr_cpages);
        cc->cpages = NULL;
        return -EAGAIN;
 }
@@ -1499,7 +1498,7 @@ void f2fs_compress_write_end_io(struct bio *bio,
struct page *page)
                end_page_writeback(cic->rpages[i]);
        }

-       page_array_free(cic->inode, cic->rpages, cic->nr_rpages);
+       page_array_free(F2FS_I_SB(cic->inode), cic->rpages, cic->nr_rpages);
        kmem_cache_free(cic_entry_slab, cic);
 }

@@ -1637,7 +1636,7 @@ static int f2fs_prepare_decomp_mem(struct
decompress_io_ctx *dic,
                f2fs_cops[F2FS_I(dic->inode)->i_compress_algorithm];
        int i;

-       if (!allow_memalloc_for_decomp(F2FS_I_SB(dic->inode), pre_alloc))
+       if (!allow_memalloc_for_decomp(dic->sbi, pre_alloc))
                return 0;

        dic->tpages = page_array_alloc(dic->inode, dic->cluster_size);
@@ -1670,10 +1669,9 @@ static int f2fs_prepare_decomp_mem(struct
decompress_io_ctx *dic,
 static void f2fs_release_decomp_mem(struct decompress_io_ctx *dic,
                bool bypass_destroy_callback, bool pre_alloc)
 {
-       const struct f2fs_compress_ops *cops =
-               f2fs_cops[F2FS_I(dic->inode)->i_compress_algorithm];
+       const struct f2fs_compress_ops *cops =
f2fs_cops[dic->compress_algorithm];

-       if (!allow_memalloc_for_decomp(F2FS_I_SB(dic->inode), pre_alloc))
+       if (!allow_memalloc_for_decomp(dic->sbi, pre_alloc))
                return;

        if (!bypass_destroy_callback && cops->destroy_decompress_ctx)
@@ -1708,6 +1706,8 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct
compress_ctx *cc)

        dic->magic = F2FS_COMPRESSED_PAGE_MAGIC;
        dic->inode = cc->inode;
+       dic->sbi = sbi;
+       dic->compress_algorithm = F2FS_I(cc->inode)->i_compress_algorithm;
        atomic_set(&dic->remaining_pages, cc->nr_cpages);
        dic->cluster_idx = cc->cluster_idx;
        dic->cluster_size = cc->cluster_size;
@@ -1762,7 +1762,7 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
                                continue;
                        f2fs_compress_free_page(dic->tpages[i]);
                }
-               page_array_free(dic->inode, dic->tpages, dic->cluster_size);
+               page_array_free(dic->sbi, dic->tpages, dic->cluster_size);
        }

        if (dic->cpages) {
@@ -1771,10 +1771,10 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
                                continue;
                        f2fs_compress_free_page(dic->cpages[i]);
                }
-               page_array_free(dic->inode, dic->cpages, dic->nr_cpages);
+               page_array_free(dic->sbi, dic->cpages, dic->nr_cpages);
        }

-       page_array_free(dic->inode, dic->rpages, dic->nr_rpages);
+       page_array_free(dic->sbi, dic->rpages, dic->nr_rpages);
        kmem_cache_free(dic_entry_slab, dic);
 }

diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 9333a22b..da2137e 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -1536,6 +1536,7 @@ struct compress_io_ctx {
 struct decompress_io_ctx {
        u32 magic;                      /* magic number to indicate
page is compressed */
        struct inode *inode;            /* inode the context belong to */
+       struct f2fs_sb_info *sbi;       /* f2fs_sb_info pointer */
        pgoff_t cluster_idx;            /* cluster index number */
        unsigned int cluster_size;      /* page count in cluster */
        unsigned int log_cluster_size;  /* log of cluster size */
@@ -1576,6 +1577,7 @@ struct decompress_io_ctx {

        bool failed;                    /* IO error occurred before
decompression? */
        bool need_verity;               /* need fs-verity verification
after decompression? */
+       unsigned char compress_algorithm;       /* backup algorithm type */
        void *private;                  /* payload buffer for
specified decompression algorithm */
        void *private2;                 /* extra payload buffer */
        struct work_struct verity_work; /* work to verify the
decompressed pages */
Re: [PATCH v3] f2fs: compress: fix UAF of f2fs_inode_info in f2fs_free_dic
Posted by Chao Yu 6 months, 1 week ago
On 6/11/25 15:51, Zhiguo Niu wrote:
> Zhiguo Niu <niuzhiguo84@gmail.com> 于2025年6月11日周三 14:52写道:
>>
>> Chao Yu <chao@kernel.org> 于2025年6月11日周三 14:47写道:
>>>
>>> On 6/11/25 14:41, Zhiguo Niu wrote:
>>>> Chao Yu <chao@kernel.org> 于2025年6月11日周三 14:07写道:
>>>>>
>>>>> On 6/11/25 00:08, Jaegeuk Kim wrote:
>>>>>> Hi Zhiguo,
>>>>>>
>>>>>> This patch causes CPU hang when running fsstress on compressed/non-compressed
>>>>>> files. Please check.
>>>>>
>>>>> Oh, seems it may cause below deadlock:
>>>>>
>>>>> CPU0
>>>>> process A
>>>>> - spin_lock(i_lock)
>>>>> software IRQ
>>>>> - end_io
>>>>>  - igrab
>>>>>   - spin_lock(i_lock)
>>>>>
>>>>> Thanks,
>>>> Hi Chao,
>>>> Thanks for pointing this out.
>>>> I have tested this patch locally about some basic cases before submission.
>>>> So it seems that should use the following method  to solve this problem?
>>>> " store i_compress_algorithm/sbi in dic to avoid inode access?"
>>>
>>> Zhiguo,
>>>
>>> Yeah, I guess so.
>> Hi Chao,
>> OK, I will prepare it .
>> Thanks a lot.
>>>
>>> Thanks,
>>>
>>>> thanks!
>>>>
>>>>
>>>>>
>>>>>>
>>>>>> On 06/05, Zhiguo Niu wrote:
>>>>>>> The decompress_io_ctx may be released asynchronously after
>>>>>>> I/O completion. If this file is deleted immediately after read,
>>>>>>> and the kworker of processing post_read_wq has not been executed yet
>>>>>>> due to high workloads, It is possible that the inode(f2fs_inode_info)
>>>>>>> is evicted and freed before it is used f2fs_free_dic.
>>>>>>>
>>>>>>>     The UAF case as below:
>>>>>>>     Thread A                                      Thread B
>>>>>>>     - f2fs_decompress_end_io
>>>>>>>      - f2fs_put_dic
>>>>>>>       - queue_work
>>>>>>>         add free_dic work to post_read_wq
>>>>>>>                                                    - do_unlink
>>>>>>>                                                     - iput
>>>>>>>                                                      - evict
>>>>>>>                                                       - call_rcu
>>>>>>>     This file is deleted after read.
>>>>>>>
>>>>>>>     Thread C                                 kworker to process post_read_wq
>>>>>>>     - rcu_do_batch
>>>>>>>      - f2fs_free_inode
>>>>>>>       - kmem_cache_free
>>>>>>>      inode is freed by rcu
>>>>>>>                                              - process_scheduled_works
>>>>>>>                                               - f2fs_late_free_dic
>>>>>>>                                                - f2fs_free_dic
>>>>>>>                                                 - f2fs_release_decomp_mem
>>>>>>>                                       read (dic->inode)->i_compress_algorithm
>>>>>>>
>>>>>>> This patch use igrab before f2fs_free_dic and iput after free the dic when dic free
>>>>>>> action is done by kworker.
>>>>>>>
>>>>>>> Cc: Daeho Jeong <daehojeong@google.com>
>>>>>>> Fixes: bff139b49d9f ("f2fs: handle decompress only post processing in softirq")
>>>>>>> Signed-off-by: Zhiguo Niu <zhiguo.niu@unisoc.com>
>>>>>>> Signed-off-by: Baocong Liu <baocong.liu@unisoc.com>
>>>>>>> ---
>>>>>>> v3: use igrab to replace __iget
>>>>>>> v2: use __iget/iput function
>>>>>>> ---
>>>>>>>  fs/f2fs/compress.c | 14 +++++++++-----
>>>>>>>  1 file changed, 9 insertions(+), 5 deletions(-)
>>>>>>>
>>>>>>> diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
>>>>>>> index b3c1df9..729ad16 100644
>>>>>>> --- a/fs/f2fs/compress.c
>>>>>>> +++ b/fs/f2fs/compress.c
>>>>>>> @@ -1687,7 +1687,7 @@ static void f2fs_release_decomp_mem(struct decompress_io_ctx *dic,
>>>>>>>  }
>>>>>>>
>>>>>>>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
>>>>>>> -            bool bypass_destroy_callback);
>>>>>>> +            bool bypass_destroy_callback, bool late_free);
>>>>>>>
>>>>>>>  struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
>>>>>>>  {
>>>>>>> @@ -1743,12 +1743,12 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
>>>>>>>      return dic;
>>>>>>>
>>>>>>>  out_free:
>>>>>>> -    f2fs_free_dic(dic, true);
>>>>>>> +    f2fs_free_dic(dic, true, false);
>>>>>>>      return ERR_PTR(ret);
>>>>>>>  }
>>>>>>>
>>>>>>>  static void f2fs_free_dic(struct decompress_io_ctx *dic,
>>>>>>> -            bool bypass_destroy_callback)
>>>>>>> +            bool bypass_destroy_callback, bool late_free)
>>>>>>>  {
>>>>>>>      int i;
>>>>>>>
>>>>>>> @@ -1775,6 +1775,8 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
>>>>>>>      }
>>>>>>>
>>>>>>>      page_array_free(dic->inode, dic->rpages, dic->nr_rpages);
>>>>>>> +    if (late_free)
>>>>>>> +            iput(dic->inode);
>>>>>>>      kmem_cache_free(dic_entry_slab, dic);
>>>>>>>  }
>>>>>>>
>>>>>>> @@ -1783,16 +1785,18 @@ static void f2fs_late_free_dic(struct work_struct *work)
>>>>>>>      struct decompress_io_ctx *dic =
>>>>>>>              container_of(work, struct decompress_io_ctx, free_work);
>>>>>>>
>>>>>>> -    f2fs_free_dic(dic, false);
>>>>>>> +    f2fs_free_dic(dic, false, true);
>>>>>>>  }
>>>>>>>
>>>>>>>  static void f2fs_put_dic(struct decompress_io_ctx *dic, bool in_task)
>>>>>>>  {
>>>>>>>      if (refcount_dec_and_test(&dic->refcnt)) {
>>>>>>>              if (in_task) {
>>>>>>> -                    f2fs_free_dic(dic, false);
>>>>>>> +                    f2fs_free_dic(dic, false, false);
>>>>>>>              } else {
>>>>>>>                      INIT_WORK(&dic->free_work, f2fs_late_free_dic);
>>>>>>> +                    /* use igrab to avoid inode is evicted simultaneously */
>>>>>>> +                    f2fs_bug_on(F2FS_I_SB(dic->inode), !igrab(dic->inode));
>>>>>>>                      queue_work(F2FS_I_SB(dic->inode)->post_read_wq,
>>>>>>>                                      &dic->free_work);
>>>>>>>              }
>>>>>>> --
>>>>>>> 1.9.1
>>>>>
>>>
> 
> Hi Chao,
> 
> The patch is about as follows, because dic->sbi is used directly in
> f2fs_free_dic: page_array_free(dic->sbi, dic->tpages, dic->cluster_size);
> so there are two points I want to confirm:
> 1. As a corresponding, the first parameter (inode) of page_array_alloc
> is need to modify to sbi or not ?
> 2. As a corresponding, do we need to add the sbi field in compress_ctx
> so that page_array_alloc/free called
> in compress flow can use sbi directly?

Zhiguo, both 1) and 2) changes look fine to me.

Thanks,

> Thanks!
> 
> diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
> index b3c1df9..897d8ae 100644
> --- a/fs/f2fs/compress.c
> +++ b/fs/f2fs/compress.c
> @@ -34,9 +34,8 @@ static void *page_array_alloc(struct inode *inode, int nr)
>         return f2fs_kzalloc(sbi, size, GFP_NOFS);
>  }
> 
> -static void page_array_free(struct inode *inode, void *pages, int nr)
> +static void page_array_free(struct f2fs_sb_info *sbi, void *pages, int nr)
>  {
> -       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>         unsigned int size = sizeof(struct page *) * nr;
> 
>         if (!pages)
> @@ -155,7 +154,7 @@ int f2fs_init_compress_ctx(struct compress_ctx *cc)
> 
>  void f2fs_destroy_compress_ctx(struct compress_ctx *cc, bool reuse)
>  {
> -       page_array_free(cc->inode, cc->rpages, cc->cluster_size);
> +       page_array_free(F2FS_I_SB(cc->inode), cc->rpages, cc->cluster_size);
>         cc->rpages = NULL;
>         cc->nr_rpages = 0;
>         cc->nr_cpages = 0;
> @@ -716,7 +715,7 @@ static int f2fs_compress_pages(struct compress_ctx *cc)
>                 if (cc->cpages[i])
>                         f2fs_compress_free_page(cc->cpages[i]);
>         }
> -       page_array_free(cc->inode, cc->cpages, cc->nr_cpages);
> +       page_array_free(F2FS_I_SB(cc->inode), cc->cpages, cc->nr_cpages);
>         cc->cpages = NULL;
>  destroy_compress_ctx:
>         if (cops->destroy_compress_ctx)
> @@ -734,7 +733,7 @@ static void f2fs_release_decomp_mem(struct
> decompress_io_ctx *dic,
> 
>  void f2fs_decompress_cluster(struct decompress_io_ctx *dic, bool in_task)
>  {
> -       struct f2fs_sb_info *sbi = F2FS_I_SB(dic->inode);
> +       struct f2fs_sb_info *sbi = dic->sbi;
>         struct f2fs_inode_info *fi = F2FS_I(dic->inode);
>         const struct f2fs_compress_ops *cops =
>                         f2fs_cops[fi->i_compress_algorithm];
> @@ -1442,13 +1441,13 @@ static int f2fs_write_compressed_pages(struct
> compress_ctx *cc,
>         spin_unlock(&fi->i_size_lock);
> 
>         f2fs_put_rpages(cc);
> -       page_array_free(cc->inode, cc->cpages, cc->nr_cpages);
> +       page_array_free(F2FS_I_SB(cc->inode), cc->cpages, cc->nr_cpages);
>         cc->cpages = NULL;
>         f2fs_destroy_compress_ctx(cc, false);
>         return 0;
> 
>  out_destroy_crypt:
> -       page_array_free(cc->inode, cic->rpages, cc->cluster_size);
> +       page_array_free(F2FS_I_SB(cc->inode), cic->rpages, cc->cluster_size);
> 
>         for (--i; i >= 0; i--) {
>                 if (!cc->cpages[i])
> @@ -1469,7 +1468,7 @@ static int f2fs_write_compressed_pages(struct
> compress_ctx *cc,
>                 f2fs_compress_free_page(cc->cpages[i]);
>                 cc->cpages[i] = NULL;
>         }
> -       page_array_free(cc->inode, cc->cpages, cc->nr_cpages);
> +       page_array_free(F2FS_I_SB(cc->inode), cc->cpages, cc->nr_cpages);
>         cc->cpages = NULL;
>         return -EAGAIN;
>  }
> @@ -1499,7 +1498,7 @@ void f2fs_compress_write_end_io(struct bio *bio,
> struct page *page)
>                 end_page_writeback(cic->rpages[i]);
>         }
> 
> -       page_array_free(cic->inode, cic->rpages, cic->nr_rpages);
> +       page_array_free(F2FS_I_SB(cic->inode), cic->rpages, cic->nr_rpages);
>         kmem_cache_free(cic_entry_slab, cic);
>  }
> 
> @@ -1637,7 +1636,7 @@ static int f2fs_prepare_decomp_mem(struct
> decompress_io_ctx *dic,
>                 f2fs_cops[F2FS_I(dic->inode)->i_compress_algorithm];
>         int i;
> 
> -       if (!allow_memalloc_for_decomp(F2FS_I_SB(dic->inode), pre_alloc))
> +       if (!allow_memalloc_for_decomp(dic->sbi, pre_alloc))
>                 return 0;
> 
>         dic->tpages = page_array_alloc(dic->inode, dic->cluster_size);
> @@ -1670,10 +1669,9 @@ static int f2fs_prepare_decomp_mem(struct
> decompress_io_ctx *dic,
>  static void f2fs_release_decomp_mem(struct decompress_io_ctx *dic,
>                 bool bypass_destroy_callback, bool pre_alloc)
>  {
> -       const struct f2fs_compress_ops *cops =
> -               f2fs_cops[F2FS_I(dic->inode)->i_compress_algorithm];
> +       const struct f2fs_compress_ops *cops =
> f2fs_cops[dic->compress_algorithm];
> 
> -       if (!allow_memalloc_for_decomp(F2FS_I_SB(dic->inode), pre_alloc))
> +       if (!allow_memalloc_for_decomp(dic->sbi, pre_alloc))
>                 return;
> 
>         if (!bypass_destroy_callback && cops->destroy_decompress_ctx)
> @@ -1708,6 +1706,8 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct
> compress_ctx *cc)
> 
>         dic->magic = F2FS_COMPRESSED_PAGE_MAGIC;
>         dic->inode = cc->inode;
> +       dic->sbi = sbi;
> +       dic->compress_algorithm = F2FS_I(cc->inode)->i_compress_algorithm;
>         atomic_set(&dic->remaining_pages, cc->nr_cpages);
>         dic->cluster_idx = cc->cluster_idx;
>         dic->cluster_size = cc->cluster_size;
> @@ -1762,7 +1762,7 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
>                                 continue;
>                         f2fs_compress_free_page(dic->tpages[i]);
>                 }
> -               page_array_free(dic->inode, dic->tpages, dic->cluster_size);
> +               page_array_free(dic->sbi, dic->tpages, dic->cluster_size);
>         }
> 
>         if (dic->cpages) {
> @@ -1771,10 +1771,10 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
>                                 continue;
>                         f2fs_compress_free_page(dic->cpages[i]);
>                 }
> -               page_array_free(dic->inode, dic->cpages, dic->nr_cpages);
> +               page_array_free(dic->sbi, dic->cpages, dic->nr_cpages);
>         }
> 
> -       page_array_free(dic->inode, dic->rpages, dic->nr_rpages);
> +       page_array_free(dic->sbi, dic->rpages, dic->nr_rpages);
>         kmem_cache_free(dic_entry_slab, dic);
>  }
> 
> diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
> index 9333a22b..da2137e 100644
> --- a/fs/f2fs/f2fs.h
> +++ b/fs/f2fs/f2fs.h
> @@ -1536,6 +1536,7 @@ struct compress_io_ctx {
>  struct decompress_io_ctx {
>         u32 magic;                      /* magic number to indicate
> page is compressed */
>         struct inode *inode;            /* inode the context belong to */
> +       struct f2fs_sb_info *sbi;       /* f2fs_sb_info pointer */
>         pgoff_t cluster_idx;            /* cluster index number */
>         unsigned int cluster_size;      /* page count in cluster */
>         unsigned int log_cluster_size;  /* log of cluster size */
> @@ -1576,6 +1577,7 @@ struct decompress_io_ctx {
> 
>         bool failed;                    /* IO error occurred before
> decompression? */
>         bool need_verity;               /* need fs-verity verification
> after decompression? */
> +       unsigned char compress_algorithm;       /* backup algorithm type */
>         void *private;                  /* payload buffer for
> specified decompression algorithm */
>         void *private2;                 /* extra payload buffer */
>         struct work_struct verity_work; /* work to verify the
> decompressed pages */

Re: [PATCH v3] f2fs: compress: fix UAF of f2fs_inode_info in f2fs_free_dic
Posted by Chao Yu 6 months, 2 weeks ago
On 6/5/25 15:18, Zhiguo Niu wrote:
> The decompress_io_ctx may be released asynchronously after
> I/O completion. If this file is deleted immediately after read,
> and the kworker of processing post_read_wq has not been executed yet
> due to high workloads, It is possible that the inode(f2fs_inode_info)
> is evicted and freed before it is used f2fs_free_dic.
> 
>     The UAF case as below:
>     Thread A                                      Thread B
>     - f2fs_decompress_end_io
>      - f2fs_put_dic
>       - queue_work
>         add free_dic work to post_read_wq
>                                                    - do_unlink
>                                                     - iput
>                                                      - evict
>                                                       - call_rcu
>     This file is deleted after read.
> 
>     Thread C                                 kworker to process post_read_wq
>     - rcu_do_batch
>      - f2fs_free_inode
>       - kmem_cache_free
>      inode is freed by rcu
>                                              - process_scheduled_works
>                                               - f2fs_late_free_dic
>                                                - f2fs_free_dic
>                                                 - f2fs_release_decomp_mem
>                                       read (dic->inode)->i_compress_algorithm
> 
> This patch use igrab before f2fs_free_dic and iput after free the dic when dic free
> action is done by kworker.
> 
> Cc: Daeho Jeong <daehojeong@google.com>
> Fixes: bff139b49d9f ("f2fs: handle decompress only post processing in softirq")
> Signed-off-by: Zhiguo Niu <zhiguo.niu@unisoc.com>
> Signed-off-by: Baocong Liu <baocong.liu@unisoc.com>

Reviewed-by: Chao Yu <chao@kernel.org>

Thanks,