[PATCH] ocfs2: validate bg_bits during freefrag scan

ZhengYuan Huang posted 1 patch 2 months, 1 week ago
There is a newer version of this series
fs/ocfs2/ioctl.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
[PATCH] ocfs2: validate bg_bits during freefrag scan
Posted by ZhengYuan Huang 2 months, 1 week ago
[BUG]
A crafted filesystem can trigger an out-of-bounds bitmap walk when
OCFS2_IOC_INFO is issued with OCFS2_INFO_FL_NON_COHERENT.

BUG: KASAN: use-after-free in instrument_atomic_read include/linux/instrumented.h:68 [inline]
BUG: KASAN: use-after-free in _test_bit include/asm-generic/bitops/instrumented-non-atomic.h:141 [inline]
BUG: KASAN: use-after-free in test_bit_le include/asm-generic/bitops/le.h:21 [inline]
BUG: KASAN: use-after-free in ocfs2_info_freefrag_scan_chain fs/ocfs2/ioctl.c:495 [inline]
BUG: KASAN: use-after-free in ocfs2_info_freefrag_scan_bitmap fs/ocfs2/ioctl.c:588 [inline]
BUG: KASAN: use-after-free in ocfs2_info_handle_freefrag fs/ocfs2/ioctl.c:662 [inline]
BUG: KASAN: use-after-free in ocfs2_info_handle_request+0x1c66/0x3370 fs/ocfs2/ioctl.c:754
Read of size 8 at addr ffff888031bce000 by task syz.0.636/1435
Call Trace:
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0xbe/0x130 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xd1/0x650 mm/kasan/report.c:482
 kasan_report+0xfb/0x140 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:186 [inline]
 kasan_check_range+0x11c/0x200 mm/kasan/generic.c:200
 __kasan_check_read+0x11/0x20 mm/kasan/shadow.c:31
 instrument_atomic_read include/linux/instrumented.h:68 [inline]
 _test_bit include/asm-generic/bitops/instrumented-non-atomic.h:141 [inline]
 test_bit_le include/asm-generic/bitops/le.h:21 [inline]
 ocfs2_info_freefrag_scan_chain fs/ocfs2/ioctl.c:495 [inline]
 ocfs2_info_freefrag_scan_bitmap fs/ocfs2/ioctl.c:588 [inline]
 ocfs2_info_handle_freefrag fs/ocfs2/ioctl.c:662 [inline]
 ocfs2_info_handle_request+0x1c66/0x3370 fs/ocfs2/ioctl.c:754
 ocfs2_info_handle+0x18d/0x2a0 fs/ocfs2/ioctl.c:828
 ocfs2_ioctl+0x632/0x6e0 fs/ocfs2/ioctl.c:913
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl fs/ioctl.c:583 [inline]
 __x64_sys_ioctl+0x197/0x1e0 fs/ioctl.c:583
 ...

[CAUSE]
ocfs2_info_freefrag_scan_chain() uses on-disk bg_bits directly as the
bitmap scan limit. The coherent path reads group descriptors through
ocfs2_read_group_descriptor(), which enforces bg_bits <= bg_size * 8.
The non-coherent path uses ocfs2_read_blocks_sync() instead and skips
that validation, so an impossible bg_bits value can drive the bitmap
walk past the end of the block.

[FIX]
Check bg_bits against the actual bitmap capacity before scanning the
group bitmap and fail the ioctl with -EIO if the descriptor is
impossible. This keeps the coherent path unchanged and gives the
non-coherent path the same bound needed to avoid walking beyond the
buffer.

Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
 fs/ocfs2/ioctl.c | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/fs/ocfs2/ioctl.c b/fs/ocfs2/ioctl.c
index b6864602814c..a341a599e440 100644
--- a/fs/ocfs2/ioctl.c
+++ b/fs/ocfs2/ioctl.c
@@ -441,7 +441,7 @@ static int ocfs2_info_freefrag_scan_chain(struct ocfs2_super *osb,
 	struct buffer_head *bh = NULL;
 	struct ocfs2_group_desc *bg = NULL;
 
-	unsigned int max_bits, num_clusters;
+	unsigned int max_bits, max_bitmap_bits, num_clusters;
 	unsigned int offset = 0, cluster, chunk;
 	unsigned int chunk_free, last_chunksize = 0;
 
@@ -474,11 +474,27 @@ static int ocfs2_info_freefrag_scan_chain(struct ocfs2_super *osb,
 		}
 
 		bg = (struct ocfs2_group_desc *)bh->b_data;
+		max_bits = le16_to_cpu(bg->bg_bits);
+		max_bitmap_bits = 8U * le16_to_cpu(bg->bg_size);
+
+		/*
+		 * Non-coherent scans read raw blocks and do not get the
+		 * bg_bits <= bg_size * 8 validation from
+		 * ocfs2_read_group_descriptor().
+		 */
+		if (max_bits > max_bitmap_bits) {
+			mlog(ML_ERROR,
+			     "Group desc #%llu has %u bits but bitmap holds %u\n",
+			     (unsigned long long)blkno,
+			     max_bits,
+			     max_bitmap_bits);
+			status = -EIO;
+			goto bail;
+		}
 
 		if (!le16_to_cpu(bg->bg_free_bits_count))
 			continue;
 
-		max_bits = le16_to_cpu(bg->bg_bits);
 		offset = 0;
 
 		for (chunk = 0; chunk < chunks_in_group; chunk++) {
-- 
2.43.0
Re: [PATCH] ocfs2: validate bg_bits during freefrag scan
Posted by Heming Zhao 2 months, 1 week ago
On Thu, Apr 09, 2026 at 02:02:41PM +0800, ZhengYuan Huang wrote:
> [BUG]
> A crafted filesystem can trigger an out-of-bounds bitmap walk when
> OCFS2_IOC_INFO is issued with OCFS2_INFO_FL_NON_COHERENT.
> 
> BUG: KASAN: use-after-free in instrument_atomic_read include/linux/instrumented.h:68 [inline]
> BUG: KASAN: use-after-free in _test_bit include/asm-generic/bitops/instrumented-non-atomic.h:141 [inline]
> BUG: KASAN: use-after-free in test_bit_le include/asm-generic/bitops/le.h:21 [inline]
> BUG: KASAN: use-after-free in ocfs2_info_freefrag_scan_chain fs/ocfs2/ioctl.c:495 [inline]
> BUG: KASAN: use-after-free in ocfs2_info_freefrag_scan_bitmap fs/ocfs2/ioctl.c:588 [inline]
> BUG: KASAN: use-after-free in ocfs2_info_handle_freefrag fs/ocfs2/ioctl.c:662 [inline]
> BUG: KASAN: use-after-free in ocfs2_info_handle_request+0x1c66/0x3370 fs/ocfs2/ioctl.c:754
> Read of size 8 at addr ffff888031bce000 by task syz.0.636/1435
> Call Trace:
>  __dump_stack lib/dump_stack.c:94 [inline]
>  dump_stack_lvl+0xbe/0x130 lib/dump_stack.c:120
>  print_address_description mm/kasan/report.c:378 [inline]
>  print_report+0xd1/0x650 mm/kasan/report.c:482
>  kasan_report+0xfb/0x140 mm/kasan/report.c:595
>  check_region_inline mm/kasan/generic.c:186 [inline]
>  kasan_check_range+0x11c/0x200 mm/kasan/generic.c:200
>  __kasan_check_read+0x11/0x20 mm/kasan/shadow.c:31
>  instrument_atomic_read include/linux/instrumented.h:68 [inline]
>  _test_bit include/asm-generic/bitops/instrumented-non-atomic.h:141 [inline]
>  test_bit_le include/asm-generic/bitops/le.h:21 [inline]
>  ocfs2_info_freefrag_scan_chain fs/ocfs2/ioctl.c:495 [inline]
>  ocfs2_info_freefrag_scan_bitmap fs/ocfs2/ioctl.c:588 [inline]
>  ocfs2_info_handle_freefrag fs/ocfs2/ioctl.c:662 [inline]
>  ocfs2_info_handle_request+0x1c66/0x3370 fs/ocfs2/ioctl.c:754
>  ocfs2_info_handle+0x18d/0x2a0 fs/ocfs2/ioctl.c:828
>  ocfs2_ioctl+0x632/0x6e0 fs/ocfs2/ioctl.c:913
>  vfs_ioctl fs/ioctl.c:51 [inline]
>  __do_sys_ioctl fs/ioctl.c:597 [inline]
>  __se_sys_ioctl fs/ioctl.c:583 [inline]
>  __x64_sys_ioctl+0x197/0x1e0 fs/ioctl.c:583
>  ...
> 
> [CAUSE]
> ocfs2_info_freefrag_scan_chain() uses on-disk bg_bits directly as the
> bitmap scan limit. The coherent path reads group descriptors through
> ocfs2_read_group_descriptor(), which enforces bg_bits <= bg_size * 8.
> The non-coherent path uses ocfs2_read_blocks_sync() instead and skips
> that validation, so an impossible bg_bits value can drive the bitmap
> walk past the end of the block.
> 
> [FIX]
> Check bg_bits against the actual bitmap capacity before scanning the
> group bitmap and fail the ioctl with -EIO if the descriptor is
> impossible. This keeps the coherent path unchanged and gives the
> non-coherent path the same bound needed to avoid walking beyond the
> buffer.
> 
> Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
> ---
>  fs/ocfs2/ioctl.c | 20 ++++++++++++++++++--
>  1 file changed, 18 insertions(+), 2 deletions(-)
> 
> diff --git a/fs/ocfs2/ioctl.c b/fs/ocfs2/ioctl.c
> index b6864602814c..a341a599e440 100644
> --- a/fs/ocfs2/ioctl.c
> +++ b/fs/ocfs2/ioctl.c
> @@ -441,7 +441,7 @@ static int ocfs2_info_freefrag_scan_chain(struct ocfs2_super *osb,
>  	struct buffer_head *bh = NULL;
>  	struct ocfs2_group_desc *bg = NULL;
>  
> -	unsigned int max_bits, num_clusters;
> +	unsigned int max_bits, max_bitmap_bits, num_clusters;
>  	unsigned int offset = 0, cluster, chunk;
>  	unsigned int chunk_free, last_chunksize = 0;
>  
> @@ -474,11 +474,27 @@ static int ocfs2_info_freefrag_scan_chain(struct ocfs2_super *osb,
>  		}
>  
>  		bg = (struct ocfs2_group_desc *)bh->b_data;
> +		max_bits = le16_to_cpu(bg->bg_bits);
> +		max_bitmap_bits = 8U * le16_to_cpu(bg->bg_size);

The bg->bg_size can also change.
It is better to use ocfs2_group_bitmap_size(osb->sb, 1, osb->s_feature_incompat)*8
to retrieve the max_bitmap_bits value.

> +
> +		/*
> +		 * Non-coherent scans read raw blocks and do not get the
> +		 * bg_bits <= bg_size * 8 validation from
> +		 * ocfs2_read_group_descriptor().
> +		 */
> +		if (max_bits > max_bitmap_bits) {
> +			mlog(ML_ERROR,
> +			     "Group desc #%llu has %u bits but bitmap holds %u\n",
> +			     (unsigned long long)blkno,
> +			     max_bits,
> +			     max_bitmap_bits);
> +			status = -EIO;
> +			goto bail;

Since this function is used to report chain states, it's better to continue
instead of breaking. In my view, reporting the error message and overwriting
max_bits with max_bitmap_bits is sufficient.

Thanks,
Heming
> +		}
>  
>  		if (!le16_to_cpu(bg->bg_free_bits_count))
>  			continue;
>  
> -		max_bits = le16_to_cpu(bg->bg_bits);
>  		offset = 0;
>  
>  		for (chunk = 0; chunk < chunks_in_group; chunk++) {
> -- 
> 2.43.0
> 
>
Re: [PATCH] ocfs2: validate bg_bits during freefrag scan
Posted by ZhengYuan Huang 2 months, 1 week ago
On Thu, Apr 9, 2026 at 2:50 PM Heming Zhao <heming.zhao@suse.com> wrote:
> > diff --git a/fs/ocfs2/ioctl.c b/fs/ocfs2/ioctl.c
> > index b6864602814c..a341a599e440 100644
> > --- a/fs/ocfs2/ioctl.c
> > +++ b/fs/ocfs2/ioctl.c
> > @@ -441,7 +441,7 @@ static int ocfs2_info_freefrag_scan_chain(struct ocfs2_super *osb,
> >       struct buffer_head *bh = NULL;
> >       struct ocfs2_group_desc *bg = NULL;
> >
> > -     unsigned int max_bits, num_clusters;
> > +     unsigned int max_bits, max_bitmap_bits, num_clusters;
> >       unsigned int offset = 0, cluster, chunk;
> >       unsigned int chunk_free, last_chunksize = 0;
> >
> > @@ -474,11 +474,27 @@ static int ocfs2_info_freefrag_scan_chain(struct ocfs2_super *osb,
> >               }
> >
> >               bg = (struct ocfs2_group_desc *)bh->b_data;
> > +             max_bits = le16_to_cpu(bg->bg_bits);
> > +             max_bitmap_bits = 8U * le16_to_cpu(bg->bg_size);
>
> The bg->bg_size can also change.
> It is better to use ocfs2_group_bitmap_size(osb->sb, 1, osb->s_feature_incompat)*8
> to retrieve the max_bitmap_bits value.
>
> > +
> > +             /*
> > +              * Non-coherent scans read raw blocks and do not get the
> > +              * bg_bits <= bg_size * 8 validation from
> > +              * ocfs2_read_group_descriptor().
> > +              */
> > +             if (max_bits > max_bitmap_bits) {
> > +                     mlog(ML_ERROR,
> > +                          "Group desc #%llu has %u bits but bitmap holds %u\n",
> > +                          (unsigned long long)blkno,
> > +                          max_bits,
> > +                          max_bitmap_bits);
> > +                     status = -EIO;
> > +                     goto bail;
>
> Since this function is used to report chain states, it's better to continue
> instead of breaking. In my view, reporting the error message and overwriting
> max_bits with max_bitmap_bits is sufficient.
>
> Thanks,
> Heming

Since this function is mainly used for reporting chain states, it makes sense
to continue instead of failing hard here.

I'll change it to log the error and clamp max_bits to max_bitmap_bits,
so we can still report as much information as possible.

Thanks,
ZhengYuan Huang