From: "Coly Li" <colyli@fygo.io>
To: "Ramesh Adhikari" <adhikari.resume@gmail.com>
Cc: <axboe@kernel.dk>, <gregkh@linuxfoundation.org>,
<linux-block@vger.kernel.org>, <stable@vger.kernel.org>
Subject: Re: [PATCH v6 2/2] badblocks: validate sector range and shift before rounding
Date: Mon, 20 Jul 2026 15:40:52 +0800 [thread overview]
Message-ID: <al2n2XTby-qqTH53@studio.local> (raw)
In-Reply-To: <20260709131904.596684-3-adhikari.resume@gmail.com>
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 <colyli@fygo.io>
> Fixes: aa511ff8218b ("badblocks: switch to the improved badblock handling code")
> Cc: stable@vger.kernel.org
> Signed-off-by: Ramesh Adhikari <adhikari.resume@gmail.com>
> ---
> 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
prev parent reply other threads:[~2026-07-20 7:41 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-27 15:10 [PATCH] badblocks: fix infinite loop due to incorrect rounding and overflow Ramesh Adhikari
2026-04-27 15:12 ` Greg KH
2026-04-29 23:06 ` kernel test robot
2026-04-30 5:09 ` kernel test robot
2026-07-04 17:13 ` [PATCH v5] " Ramesh Adhikari
2026-07-09 10:25 ` Coly Li
2026-07-09 13:19 ` [PATCH v6 0/2] badblocks: fix infinite loop and validate sector range/shift Ramesh Adhikari
2026-07-09 13:19 ` [PATCH v6 1/2] badblocks: fix in-place round_up/round_down usage bug Ramesh Adhikari
2026-07-09 15:16 ` Coly Li
2026-07-09 13:19 ` [PATCH v6 2/2] badblocks: validate sector range and shift before rounding Ramesh Adhikari
2026-07-20 7:40 ` Coly Li [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=al2n2XTby-qqTH53@studio.local \
--to=colyli@fygo.io \
--cc=adhikari.resume@gmail.com \
--cc=axboe@kernel.dk \
--cc=gregkh@linuxfoundation.org \
--cc=linux-block@vger.kernel.org \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.