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 v7 2/2] badblocks: validate sector range and shift before rounding
Date: Thu, 6 Aug 2026 15:22:25 +0800 [thread overview]
Message-ID: <anQdWeHd2EDZtkzp@studio.local> (raw)
In-Reply-To: <20260721164027.637078-3-adhikari.resume@gmail.com>
On Tue, Jul 21, 2026 at 10:10:24PM +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 and 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.
>
> badblocks.c does not itself bound bb->shift: every caller except
> drivers/md/md.c always leaves it at 0, so the one caller that
> populates it from untrusted on-disk data is responsible for
> bounding it before assigning it, per struct badblocks's shift
> field documentation in include/linux/badblocks.h. That md.c-side
> bound is being sent as a separate patch.
>
> badblocks_check() returns 0 rather than -EINVAL on the wrap case,
> matching its existing "0: no known bad blocks in the range"
> return convention instead of introducing a new error path callers
> don't expect.
>
> 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>
The patch looks good to me, thanks.
Reviewed-by: Coly Li <colyli@fygo.io>
BTW, the shift overflow checking in md raid code is accepted by
md maintainer, so you may submit these 2 patches with my Reviewed-by
to Jens. That's enough.
Coly Li
> ---
> Changes in v7 (per Coly Li's review of v6):
> - Simplify the overflow check in all three call sites from
> "s > ULLONG_MAX - sectors" to "(s + sectors) < s".
> - Drop the "bb->shift >= BITS_PER_LONG_LONG" guards in badblocks.c.
> Only drivers/md/md.c ever sets a nonzero bb->shift, so the bound
> belongs where bblog_shift is read off the on-disk superblock, not
> scattered through the badblocks API. Documented the caller
> requirement on struct badblocks.shift in badblocks.h instead; the
> md.c-side bound follows as a separate patch.
> - badblocks_check() now returns 0 instead of -EINVAL on the wrap
> case, consistent with its existing return convention.
>
> block/badblocks.c | 40 ++++++++++++++++++++++++++++++++-------
> include/linux/badblocks.h | 6 +++++-
> 2 files changed, 38 insertions(+), 8 deletions(-)
>
> diff --git a/block/badblocks.c b/block/badblocks.c
> index 1f786b193fb..728b59a1a5d 100644
> --- a/block/badblocks.c
> +++ b/block/badblocks.c
> @@ -853,12 +853,19 @@ static bool _badblocks_set(struct badblocks *bb, sector_t s, sector_t sectors,
> /* Invalid sectors number */
> return false;
>
> + if ((s + sectors) < s)
> + /* Range wraps past the end of sector_t */
> + return false;
> +
> 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);
> + 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 +1068,12 @@ static bool _badblocks_clear(struct badblocks *bb, sector_t s, sector_t sectors)
> /* Invalid sectors number */
> return false;
>
> + if ((s + sectors) < s)
> + /* Range wraps past the end of sector_t */
> + return false;
> +
> if (bb->shift) {
> + sector_t orig_s = s;
> sector_t target;
>
> /* When clearing we round the start up and the end down.
> @@ -1071,9 +1083,16 @@ static bool _badblocks_clear(struct badblocks *bb, sector_t s, sector_t sectors)
> * isn't than to think a block is not bad when it is.
> */
> 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 +1322,19 @@ int badblocks_check(struct badblocks *bb, sector_t s, sector_t sectors,
>
> WARN_ON(bb->shift < 0 || sectors == 0);
>
> + if ((s + sectors) < s)
> + /* Range wraps past the end of sector_t */
> + 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);
> + 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;
> }
>
> diff --git a/include/linux/badblocks.h b/include/linux/badblocks.h
> index 996493917f3..5d88992b55a 100644
> --- a/include/linux/badblocks.h
> +++ b/include/linux/badblocks.h
> @@ -34,7 +34,11 @@ struct badblocks {
> */
> int shift; /* shift from sectors to block size
> * a -ve shift means badblocks are
> - * disabled.*/
> + * disabled. Callers that set this from
> + * untrusted/on-disk data are responsible
> + * for bounding it so 1 << shift does not
> + * overflow a sector_t.
> + */
> u64 *page; /* badblock list */
> int changed;
> seqlock_t lock;
> --
> 2.43.0
next prev parent reply other threads:[~2026-08-06 7:23 UTC|newest]
Thread overview: 17+ 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
2026-07-21 16:40 ` [PATCH v7 0/2] badblocks: fix rounding bug and validate input range Ramesh Adhikari
2026-07-21 16:40 ` [PATCH v7 1/2] badblocks: fix in-place round_up/round_down usage bug Ramesh Adhikari
2026-07-21 16:40 ` [PATCH v7 2/2] badblocks: validate sector range and shift before rounding Ramesh Adhikari
2026-08-06 7:22 ` Coly Li [this message]
2026-08-04 5:41 ` [PATCH v7 0/2] badblocks: fix rounding bug and validate input range Ramesh Adhikari
2026-08-04 5:49 ` Coly Li
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=anQdWeHd2EDZtkzp@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.