Re: [PATCH v6 2/2] badblocks: validate sector range and shift before rounding

"Coly Li" <[email protected]>
Newsgroups org.kernel.vger.linux-block,org.kernel.vger.stable
Message-ID <[email protected]>
On Thu, Jul 09, 2026 at 06:49:04PM +0800, Ramesh Adhikari wrote:
> _badblocks_set(), _badblocks_clear() and badblocks_check() round
> the caller-supplied [s, s+sectors) range to the current bb->shift
> block size before touching the bad block table. That rounding
> was not defensive against a few cases:
> 
> - s + sectors can overflow sector_t (u64), wrapping the range
>   end before it is ever compared against s.
> 
> - bb->shift is a plain 'int' field, populated in one case
>   (drivers/md/md.c, from the on-disk superblock's bblog_shift)
>   straight from an unvalidated byte with no upper bound. Shifting
>   by an amount >= the width of the shifted type is undefined
>   behaviour in C; "1 << bb->shift" was shifting an int literal,
>   so this was already undefined for bb->shift >= 32, let alone
>   the full 0-255 range bblog_shift allows.
> 
> - round_up()/round_down() rounding a value near ULLONG_MAX can
>   itself wrap back to a small value, so even with a valid shift
>   the rounded end of the range could end up smaller than the
>   rounded start, silently turning a small range into a huge one
>   (in _badblocks_clear()/badblocks_check(), which round the end
>   up) or losing the range entirely.
> 
> Add an explicit s+sectors overflow check, reject any bb->shift
> that is too large to shift a sector_t by, cast the shift operand
> to sector_t so the shift itself is never performed on a 32-bit
> int, and detect post-rounding wrap by comparing the rounded
> result back against the pre-rounding value.
> 
> Suggested-by: Coly Li <[email protected]>
> Fixes: aa511ff8218b ("badblocks: switch to the improved badblock handling code")
> Cc: [email protected]
> Signed-off-by: Ramesh Adhikari <[email protected]>
> ---
>  block/badblocks.c | 52 ++++++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 45 insertions(+), 7 deletions(-)
> 
> diff --git a/block/badblocks.c b/block/badblocks.c
> index 1f786b193fb..00a59729600 100644
> --- a/block/badblocks.c
> +++ b/block/badblocks.c
> @@ -853,12 +853,23 @@ static bool _badblocks_set(struct badblocks *bb, sector_t s, sector_t sectors,
>  		/* Invalid sectors number */
>  		return false;
>  
> +	if (s > ULLONG_MAX - sectors)
> +		/* Range wraps past the end of sector_t */
> +		return false;
> +

Yes, this is necessary. Hmm, if I write this, maybe I will use,
	if ((s + sectors) < s)
		return fasle;
This makes me more comfortable.

>  	if (bb->shift) {
>  		/* round the start down, and the end up */
>  		sector_t next = s + sectors;
>  
> -		s = round_down(s, 1 << bb->shift);
> -		next = round_up(next, 1 << bb->shift);
> +		if (bb->shift >= BITS_PER_LONG_LONG)
> +			/* Corrupt/unsanitised shift value */
> +			return false;

This bb->shift overflow check can be avoided. See my finally comments.

> +
> +		s = round_down(s, (sector_t)1 << bb->shift);
> +		next = round_up(next, (sector_t)1 << bb->shift);
> +		if (next <= s)
> +			/* Rounding wrapped past the end of sector_t */
> +			return false;
>  		sectors = next - s;
>  	}
>  
> @@ -1061,7 +1072,12 @@ static bool _badblocks_clear(struct badblocks *bb, sector_t s, sector_t sectors)
>  		/* Invalid sectors number */
>  		return false;
>  
> +	if (s > ULLONG_MAX - sectors)
> +		/* Range wraps past the end of sector_t */
> +		return false;
> +

same
	if ((s + sectors) < s)
		return fasle;

>  	if (bb->shift) {
> +		sector_t orig_s = s;
>  		sector_t target;
>  
>  		/* When clearing we round the start up and the end down.
> @@ -1070,10 +1086,21 @@ static bool _badblocks_clear(struct badblocks *bb, sector_t s, sector_t sectors)
>  		 * However it is better the think a block is bad when it
>  		 * isn't than to think a block is not bad when it is.
>  		 */
> +		if (bb->shift >= BITS_PER_LONG_LONG)
> +			/* Corrupt/unsanitised shift value */
> +			return false;
> 

This bb->shift overflow check can be avoided. See my finally comments.

>  		target = s + sectors;
> -		s = round_up(s, 1 << bb->shift);
> -		target = round_down(target, 1 << bb->shift);
> -		sectors = target - s;
> +		s = round_up(s, (sector_t)1 << bb->shift);
> +		target = round_down(target, (sector_t)1 << bb->shift);
> +		if (s < orig_s || target < s)
> +			/* Rounding wrapped, or range collapsed */
> +			sectors = 0;
> +		else
> +			sectors = target - s;
> +
> +		if (sectors == 0)
> +			return false;
>  	}
>  
>  	write_seqlock_irq(&bb->lock);
> @@ -1303,12 +1330,23 @@ int badblocks_check(struct badblocks *bb, sector_t s, sector_t sectors,
>  
>  	WARN_ON(bb->shift < 0 || sectors == 0);
>  
> +	if (s > ULLONG_MAX - sectors)
> +		/* Range wraps past the end of sector_t */
> +		return -EINVAL;
> +

same
	if ((s + sectors) < s)
		return fasle;

But I feel returning -EINVAL doesn't follow badblocks_check() api convention,
and has potential risk to break the caller's code.  IMHO it is better to
return 0.


>  	if (bb->shift > 0) {
>  		/* round the start down, and the end up */
>  		sector_t target = s + sectors;
>  
> -		s = round_down(s, 1 << bb->shift);
> -		target = round_up(target, 1 << bb->shift);
> +		if (bb->shift >= BITS_PER_LONG_LONG)
> +			/* Corrupt/unsanitised shift value */
> +			return -EINVAL;
> +
bb->shift checking here might not be necessary. See my finally comments.
And again, returning -EINVAL might be risky here.



> +		s = round_down(s, (sector_t)1 << bb->shift);
> +		target = round_up(target, (sector_t)1 << bb->shift);
> +		if (target <= s)
> +			/* Rounding wrapped past the end of sector_t */
> +			return 0;
>  		sectors = target - s;
>  	}
> 

I feel you can avoid to check bb->shift here. Except for md code, other places
all set bb->shift to 0. So add a code in md.c to check the shift bits when it
is loaded form on-disk super block, that should be enough. and one line
code comment can be added in badblocks API that caller should take the
responsibility to make sure bb->shift won't result overflow.

Thansk.

Coly Li
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.