* [PATCH] badblocks: fix infinite loop due to incorrect rounding and overflow
@ 2026-04-27 15:10 Ramesh Adhikari
2026-04-27 15:12 ` Greg KH
` (3 more replies)
0 siblings, 4 replies; 10+ messages in thread
From: Ramesh Adhikari @ 2026-04-27 15:10 UTC (permalink / raw)
To: axboe, gregkh; +Cc: linux-block, Ramesh Adhikari
The roundup() and rounddown() macros return the rounded value but
do not modify the input in place. In _badblocks_set(), _badblocks_clear(),
and badblocks_check(), the return values were being discarded, causing
s and target/next to remain unrounded. This resulted in sectors
being calculated from unrounded values, which could lead to sectors
being way too large (or zero), causing infinite loops in the
re_insert/re_clear/re_check loops.
Additionally, add integer overflow checks (s > ULLONG_MAX - sectors)
before the s + sectors calculation in all three functions to prevent
overflow-related issues. Also add early return when sectors becomes
zero after rounding in badblocks_check().
Root cause: When s and sectors have specific values (e.g., from
syzkaller fuzzing via nvdimm ioctl), the unrounded values cause
sectors to be incorrectly calculated. In _badblocks_clear(), this
could result in needing 2^46 iterations to process 2^55 sectors,
triggering RCU stall warnings and effectively hanging the kernel.
Fix by properly capturing the return values from roundup() and
rounddown(), adding overflow checks before sector arithmetic, and
handling the zero-sectors case in badblocks_check().
Signed-off-by: Ramesh Adhikari <adhikari.resume@gmail.com>
---
block/badblocks.c | 43 ++++++++++++++++++++++++++++++++++---------
1 file changed, 34 insertions(+), 9 deletions(-)
diff --git a/block/badblocks.c b/block/badblocks.c
index ece64e76fe8..a5ffae65a05 100644
--- a/block/badblocks.c
+++ b/block/badblocks.c
@@ -855,13 +855,21 @@ static bool _badblocks_set(struct badblocks *bb, sector_t s, sector_t sectors,
if (bb->shift) {
/* round the start down, and the end up */
+ if (s > ULLONG_MAX - sectors)
+ return false;
sector_t next = s + sectors;
- rounddown(s, 1 << bb->shift);
- roundup(next, 1 << bb->shift);
- sectors = next - s;
+ s = rounddown(s, 1 << bb->shift);
+ next = roundup(next, 1 << bb->shift);
+ if (next < s)
+ sectors = 0;
+ else
+ sectors = next - s;
}
+ if (sectors == 0)
+ return false;
+
write_seqlock_irqsave(&bb->lock, flags);
bad.ack = acknowledged;
@@ -1070,12 +1078,20 @@ 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 (s > ULLONG_MAX - sectors)
+ return false;
target = s + sectors;
- roundup(s, 1 << bb->shift);
- rounddown(target, 1 << bb->shift);
- sectors = target - s;
+ s = roundup(s, 1 << bb->shift);
+ target = rounddown(target, 1 << bb->shift);
+ if (target < s)
+ sectors = 0;
+ else
+ sectors = target - s;
}
+ if (sectors == 0)
+ return false;
+
write_seqlock_irq(&bb->lock);
bad.ack = true;
@@ -1305,11 +1321,20 @@ int badblocks_check(struct badblocks *bb, sector_t s, sector_t sectors,
if (bb->shift > 0) {
/* round the start down, and the end up */
+ if (s > ULLONG_MAX - sectors) {
+ return -EINVAL;
+ }
sector_t target = s + sectors;
- rounddown(s, 1 << bb->shift);
- roundup(target, 1 << bb->shift);
- sectors = target - s;
+ s = rounddown(s, 1 << bb->shift);
+ target = roundup(target, 1 << bb->shift);
+ if (target < s)
+ sectors = 0;
+ else
+ sectors = target - s;
+
+ if (sectors == 0)
+ return 0;
}
retry:
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH] badblocks: fix infinite loop due to incorrect rounding and overflow
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
` (2 subsequent siblings)
3 siblings, 0 replies; 10+ messages in thread
From: Greg KH @ 2026-04-27 15:12 UTC (permalink / raw)
To: Ramesh Adhikari; +Cc: axboe, linux-block
On Mon, Apr 27, 2026 at 08:40:48PM +0530, Ramesh Adhikari wrote:
> The roundup() and rounddown() macros return the rounded value but
> do not modify the input in place. In _badblocks_set(), _badblocks_clear(),
> and badblocks_check(), the return values were being discarded, causing
> s and target/next to remain unrounded. This resulted in sectors
> being calculated from unrounded values, which could lead to sectors
> being way too large (or zero), causing infinite loops in the
> re_insert/re_clear/re_check loops.
>
> Additionally, add integer overflow checks (s > ULLONG_MAX - sectors)
> before the s + sectors calculation in all three functions to prevent
> overflow-related issues. Also add early return when sectors becomes
> zero after rounding in badblocks_check().
>
> Root cause: When s and sectors have specific values (e.g., from
> syzkaller fuzzing via nvdimm ioctl), the unrounded values cause
> sectors to be incorrectly calculated. In _badblocks_clear(), this
> could result in needing 2^46 iterations to process 2^55 sectors,
> triggering RCU stall warnings and effectively hanging the kernel.
>
> Fix by properly capturing the return values from roundup() and
> rounddown(), adding overflow checks before sector arithmetic, and
> handling the zero-sectors case in badblocks_check().
>
> Signed-off-by: Ramesh Adhikari <adhikari.resume@gmail.com>
> ---
> block/badblocks.c | 43 ++++++++++++++++++++++++++++++++++---------
> 1 file changed, 34 insertions(+), 9 deletions(-)
>
> diff --git a/block/badblocks.c b/block/badblocks.c
> index ece64e76fe8..a5ffae65a05 100644
> --- a/block/badblocks.c
> +++ b/block/badblocks.c
> @@ -855,13 +855,21 @@ static bool _badblocks_set(struct badblocks *bb, sector_t s, sector_t sectors,
>
> if (bb->shift) {
> /* round the start down, and the end up */
> + if (s > ULLONG_MAX - sectors)
> + return false;
> sector_t next = s + sectors;
>
> - rounddown(s, 1 << bb->shift);
> - roundup(next, 1 << bb->shift);
> - sectors = next - s;
> + s = rounddown(s, 1 << bb->shift);
> + next = roundup(next, 1 << bb->shift);
> + if (next < s)
> + sectors = 0;
> + else
> + sectors = next - s;
> }
>
> + if (sectors == 0)
> + return false;
> +
> write_seqlock_irqsave(&bb->lock, flags);
>
> bad.ack = acknowledged;
> @@ -1070,12 +1078,20 @@ 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 (s > ULLONG_MAX - sectors)
> + return false;
> target = s + sectors;
> - roundup(s, 1 << bb->shift);
> - rounddown(target, 1 << bb->shift);
> - sectors = target - s;
> + s = roundup(s, 1 << bb->shift);
> + target = rounddown(target, 1 << bb->shift);
> + if (target < s)
> + sectors = 0;
> + else
> + sectors = target - s;
> }
>
> + if (sectors == 0)
> + return false;
> +
> write_seqlock_irq(&bb->lock);
>
> bad.ack = true;
> @@ -1305,11 +1321,20 @@ int badblocks_check(struct badblocks *bb, sector_t s, sector_t sectors,
>
> if (bb->shift > 0) {
> /* round the start down, and the end up */
> + if (s > ULLONG_MAX - sectors) {
> + return -EINVAL;
> + }
> sector_t target = s + sectors;
>
> - rounddown(s, 1 << bb->shift);
> - roundup(target, 1 << bb->shift);
> - sectors = target - s;
> + s = rounddown(s, 1 << bb->shift);
> + target = roundup(target, 1 << bb->shift);
> + if (target < s)
> + sectors = 0;
> + else
> + sectors = target - s;
> +
> + if (sectors == 0)
> + return 0;
> }
>
> retry:
> --
> 2.43.0
>
Hi,
This is the friendly patch-bot of Greg Kroah-Hartman. You have sent him
a patch that has triggered this response. He used to manually respond
to these common problems, but in order to save his sanity (he kept
writing the same thing over and over, yet to different people), I was
created. Hopefully you will not take offence and will fix the problem
in your patch and resubmit it so that it can be accepted into the Linux
kernel tree.
You are receiving this message because of the following common error(s)
as indicated below:
- This looks like a new version of a previously submitted patch, but you
did not list below the --- line any changes from the previous version.
Please read the section entitled "The canonical patch format" in the
kernel file, Documentation/process/submitting-patches.rst for what
needs to be done here to properly describe this.
If you wish to discuss this problem further, or you have questions about
how to resolve this issue, please feel free to respond to this email and
Greg will reply once he has dug out from the pending patches received
from other developers.
thanks,
greg k-h's patch email bot
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] badblocks: fix infinite loop due to incorrect rounding and overflow
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
3 siblings, 0 replies; 10+ messages in thread
From: kernel test robot @ 2026-04-29 23:06 UTC (permalink / raw)
To: Ramesh Adhikari, axboe, gregkh
Cc: oe-kbuild-all, linux-block, Ramesh Adhikari
Hi Ramesh,
kernel test robot noticed the following build errors:
[auto build test ERROR on axboe/for-next]
[also build test ERROR on linus/master v7.1-rc1 next-20260429]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Ramesh-Adhikari/badblocks-fix-infinite-loop-due-to-incorrect-rounding-and-overflow/20260429-044425
base: https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux.git for-next
patch link: https://lore.kernel.org/r/20260427151048.756072-1-adhikari.resume%40gmail.com
patch subject: [PATCH] badblocks: fix infinite loop due to incorrect rounding and overflow
config: arm-randconfig-002-20260430 (https://download.01.org/0day-ci/archive/20260430/202604300729.gKrAMFdE-lkp@intel.com/config)
compiler: arm-linux-gnueabi-gcc (GCC) 8.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260430/202604300729.gKrAMFdE-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604300729.gKrAMFdE-lkp@intel.com/
All errors (new ones prefixed by >>):
arm-linux-gnueabi-ld: block/badblocks.o: in function `badblocks_clear':
>> badblocks.c:(.text+0x1d18): undefined reference to `__aeabi_uldivmod'
>> arm-linux-gnueabi-ld: badblocks.c:(.text+0x1d54): undefined reference to `__aeabi_uldivmod'
arm-linux-gnueabi-ld: block/badblocks.o: in function `_badblocks_set':
badblocks.c:(.text+0x2c90): undefined reference to `__aeabi_uldivmod'
arm-linux-gnueabi-ld: badblocks.c:(.text+0x2ce0): undefined reference to `__aeabi_uldivmod'
arm-linux-gnueabi-ld: block/badblocks.o: in function `badblocks_check':
badblocks.c:(.text+0x3fb8): undefined reference to `__aeabi_uldivmod'
arm-linux-gnueabi-ld: block/badblocks.o:badblocks.c:(.text+0x4018): more undefined references to `__aeabi_uldivmod' follow
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] badblocks: fix infinite loop due to incorrect rounding and overflow
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
3 siblings, 0 replies; 10+ messages in thread
From: kernel test robot @ 2026-04-30 5:09 UTC (permalink / raw)
To: Ramesh Adhikari, axboe, gregkh
Cc: oe-kbuild-all, linux-block, Ramesh Adhikari
Hi Ramesh,
kernel test robot noticed the following build errors:
[auto build test ERROR on axboe/for-next]
[also build test ERROR on linus/master v7.1-rc1 next-20260429]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Ramesh-Adhikari/badblocks-fix-infinite-loop-due-to-incorrect-rounding-and-overflow/20260429-044425
base: https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux.git for-next
patch link: https://lore.kernel.org/r/20260427151048.756072-1-adhikari.resume%40gmail.com
patch subject: [PATCH] badblocks: fix infinite loop due to incorrect rounding and overflow
config: i386-allnoconfig (https://download.01.org/0day-ci/archive/20260430/202604301231.IpPh4AiH-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260430/202604301231.IpPh4AiH-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604301231.IpPh4AiH-lkp@intel.com/
All errors (new ones prefixed by >>):
ld: block/badblocks.o: in function `badblocks_check':
badblocks.c:(.text+0x10af): undefined reference to `__umoddi3'
>> ld: badblocks.c:(.text+0x1109): undefined reference to `__umoddi3'
ld: block/badblocks.o: in function `_badblocks_set':
badblocks.c:(.text+0x12b0): undefined reference to `__umoddi3'
ld: badblocks.c:(.text+0x12f4): undefined reference to `__umoddi3'
ld: block/badblocks.o: in function `_badblocks_clear':
badblocks.c:(.text+0x1bfc): undefined reference to `__umoddi3'
ld: block/badblocks.o:badblocks.c:(.text+0x1c1c): more undefined references to `__umoddi3' follow
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v5] badblocks: fix infinite loop due to incorrect rounding and overflow
2026-04-27 15:10 [PATCH] badblocks: fix infinite loop due to incorrect rounding and overflow Ramesh Adhikari
` (2 preceding siblings ...)
2026-04-30 5:09 ` kernel test robot
@ 2026-07-04 17:13 ` Ramesh Adhikari
2026-07-09 10:25 ` Coly Li
3 siblings, 1 reply; 10+ messages in thread
From: Ramesh Adhikari @ 2026-07-04 17:13 UTC (permalink / raw)
To: axboe, gregkh; +Cc: linux-block, lkp, Ramesh Adhikari
The roundup() and rounddown() macros return the rounded value but
do not modify their input in place. In _badblocks_set(),
_badblocks_clear(), and badblocks_check(), the return values were
being discarded, so s and target/next remained unrounded. Sectors
were then calculated from these unrounded values, which could make
sectors way too large (or zero), causing infinite loops in the
re_insert/re_clear/re_check loops.
This was confirmed with local syzkaller fuzzing against the nvdimm
ioctl path (ND_IOCTL_CLEAR_ERROR -> nvdimm_clear_badblocks_region()
-> badblocks_clear()), which reliably produces RCU stalls with the
looping task caught mid-loop:
rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
rcu: rcu_preempt kthread starved for 21001 jiffies! g40229 f0x0 RCU_GP_WAIT_FQS(5)
...
Call Trace:
badblocks_clear+0x259/0xb10
nvdimm_clear_badblocks_region+0x165/0x1e0 [libnvdimm]
device_for_each_child+0x11e/0x1a0
nd_ioctl+0x1413/0x1750 [libnvdimm]
__x64_sys_ioctl+0x18e/0x210
do_syscall_64+0x102/0x5a0
Fix this by properly capturing the return values of round_up()/
round_down() (the power-of-two variants, since 1 << bb->shift is
always a power of two -- roundup()/rounddown() use a division/
modulo internally, which is undesirable here). Also add overflow
checks (s > ULLONG_MAX - sectors) before the s + sectors addition
in all three functions, and handle the case where sectors becomes
zero after rounding.
The overflow check is done unconditionally, before the bb->shift
rounding block, rather than inside it. bb->shift == 0 is not just
an initial state -- __badblocks_init() sets it to 0 by default, and
drivers/md/md.c explicitly sets rdev->badblocks.shift back to 0 in
several paths -- so s + sectors needs the same overflow guard
whether or not rounding happens.
Signed-off-by: Ramesh Adhikari <adhikari.resume@gmail.com>
---
v1-v3 chased individual len==0 symptoms in _badblocks_clear()/
_badblocks_check() one call site at a time. Jens pointed out that
approach wasn't finding the actual bug, just papering over spots as
they were noticed. v4 was a full rewrite around the real root cause
(roundup()/rounddown() discarding their return values), covering all
three functions with proper overflow handling.
Changes in v5:
- Switch from roundup()/rounddown() (which use division/modulo on
sector_t, a u64) to round_up()/round_down() (bitmask-based, since
1 << bb->shift is always a power of two). Fixes the v4 build
failure kernel test robot reported on 32-bit (undefined reference
to __aeabi_uldivmod on ARM, __umoddi3 on i386).
- Move the s > ULLONG_MAX - sectors overflow check so it runs
unconditionally in all three functions, instead of only inside
the `if (bb->shift)` block. bb->shift == 0 is a real, common
state (default init value, and explicitly set by drivers/md/md.c
in several paths), so the overflow guard needs to apply there
too, not just when rounding is active.
- Build-tested locally (x86_64) and confirmed no residual div/mod
symbols in the object file.
Link to v4: https://lore.kernel.org/r/20260427151048.756072-1-adhikari.resume@gmail.com
block/badblocks.c | 40 ++++++++++++++++++++++++++++++++--------
1 file changed, 32 insertions(+), 8 deletions(-)
diff --git a/block/badblocks.c b/block/badblocks.c
index ece64e76fe8..e8484912532 100644
--- a/block/badblocks.c
+++ b/block/badblocks.c
@@ -853,15 +853,21 @@ static bool _badblocks_set(struct badblocks *bb, sector_t s, sector_t sectors,
/* Invalid sectors number */
return false;
+ if (s > ULLONG_MAX - sectors)
+ return false;
+
if (bb->shift) {
/* round the start down, and the end up */
sector_t next = s + sectors;
- rounddown(s, 1 << bb->shift);
- roundup(next, 1 << bb->shift);
+ s = round_down(s, 1 << bb->shift);
+ next = round_up(next, 1 << bb->shift);
sectors = next - s;
}
+ if (sectors == 0)
+ return false;
+
write_seqlock_irqsave(&bb->lock, flags);
bad.ack = acknowledged;
@@ -1061,6 +1067,9 @@ static bool _badblocks_clear(struct badblocks *bb, sector_t s, sector_t sectors)
/* Invalid sectors number */
return false;
+ if (s > ULLONG_MAX - sectors)
+ return false;
+
if (bb->shift) {
sector_t target;
@@ -1071,11 +1080,17 @@ 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;
- roundup(s, 1 << bb->shift);
- rounddown(target, 1 << bb->shift);
- sectors = target - s;
+ s = round_up(s, 1 << bb->shift);
+ target = round_down(target, 1 << bb->shift);
+ if (target < s)
+ sectors = 0;
+ else
+ sectors = target - s;
}
+ if (sectors == 0)
+ return false;
+
write_seqlock_irq(&bb->lock);
bad.ack = true;
@@ -1303,13 +1318,22 @@ int badblocks_check(struct badblocks *bb, sector_t s, sector_t sectors,
WARN_ON(bb->shift < 0 || sectors == 0);
+ if (s > ULLONG_MAX - sectors)
+ return -EINVAL;
+
if (bb->shift > 0) {
/* round the start down, and the end up */
sector_t target = s + sectors;
- rounddown(s, 1 << bb->shift);
- roundup(target, 1 << bb->shift);
- sectors = target - s;
+ s = round_down(s, 1 << bb->shift);
+ target = round_up(target, 1 << bb->shift);
+ if (target < s)
+ sectors = 0;
+ else
+ sectors = target - s;
+
+ if (sectors == 0)
+ return 0;
}
retry:
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v5] badblocks: fix infinite loop due to incorrect rounding and overflow
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
0 siblings, 1 reply; 10+ messages in thread
From: Coly Li @ 2026-07-09 10:25 UTC (permalink / raw)
To: Ramesh Adhikari; +Cc: axboe, gregkh, linux-block, lkp
On Sat, Jul 04, 2026 at 10:43:21PM +0800, Ramesh Adhikari wrote:
> The roundup() and rounddown() macros return the rounded value but
> do not modify their input in place. In _badblocks_set(),
> _badblocks_clear(), and badblocks_check(), the return values were
> being discarded, so s and target/next remained unrounded. Sectors
> were then calculated from these unrounded values, which could make
> sectors way too large (or zero), causing infinite loops in the
> re_insert/re_clear/re_check loops.
>
> This was confirmed with local syzkaller fuzzing against the nvdimm
> ioctl path (ND_IOCTL_CLEAR_ERROR -> nvdimm_clear_badblocks_region()
> -> badblocks_clear()), which reliably produces RCU stalls with the
> looping task caught mid-loop:
>
> rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
> rcu: rcu_preempt kthread starved for 21001 jiffies! g40229 f0x0 RCU_GP_WAIT_FQS(5)
> ...
> Call Trace:
> badblocks_clear+0x259/0xb10
> nvdimm_clear_badblocks_region+0x165/0x1e0 [libnvdimm]
> device_for_each_child+0x11e/0x1a0
> nd_ioctl+0x1413/0x1750 [libnvdimm]
> __x64_sys_ioctl+0x18e/0x210
> do_syscall_64+0x102/0x5a0
>
> Fix this by properly capturing the return values of round_up()/
> round_down() (the power-of-two variants, since 1 << bb->shift is
> always a power of two -- roundup()/rounddown() use a division/
> modulo internally, which is undesirable here). Also add overflow
> checks (s > ULLONG_MAX - sectors) before the s + sectors addition
> in all three functions, and handle the case where sectors becomes
> zero after rounding.
>
> The overflow check is done unconditionally, before the bb->shift
> rounding block, rather than inside it. bb->shift == 0 is not just
> an initial state -- __badblocks_init() sets it to 0 by default, and
> drivers/md/md.c explicitly sets rdev->badblocks.shift back to 0 in
> several paths -- so s + sectors needs the same overflow guard
> whether or not rounding happens.
>
> Signed-off-by: Ramesh Adhikari <adhikari.resume@gmail.com>
> ---
> v1-v3 chased individual len==0 symptoms in _badblocks_clear()/
> _badblocks_check() one call site at a time. Jens pointed out that
> approach wasn't finding the actual bug, just papering over spots as
> they were noticed. v4 was a full rewrite around the real root cause
> (roundup()/rounddown() discarding their return values), covering all
> three functions with proper overflow handling.
>
> Changes in v5:
> - Switch from roundup()/rounddown() (which use division/modulo on
> sector_t, a u64) to round_up()/round_down() (bitmask-based, since
> 1 << bb->shift is always a power of two). Fixes the v4 build
> failure kernel test robot reported on 32-bit (undefined reference
> to __aeabi_uldivmod on ARM, __umoddi3 on i386).
> - Move the s > ULLONG_MAX - sectors overflow check so it runs
> unconditionally in all three functions, instead of only inside
> the `if (bb->shift)` block. bb->shift == 0 is a real, common
> state (default init value, and explicitly set by drivers/md/md.c
> in several paths), so the overflow guard needs to apply there
> too, not just when rounding is active.
> - Build-tested locally (x86_64) and confirmed no residual div/mod
> symbols in the object file.
>
> Link to v4: https://lore.kernel.org/r/20260427151048.756072-1-adhikari.resume@gmail.com
>
Hi Ramesh,
Thanks for the fix up.
> block/badblocks.c | 40 ++++++++++++++++++++++++++++++++--------
> 1 file changed, 32 insertions(+), 8 deletions(-)
>
> diff --git a/block/badblocks.c b/block/badblocks.c
> index ece64e76fe8..e8484912532 100644
> --- a/block/badblocks.c
> +++ b/block/badblocks.c
> @@ -853,15 +853,21 @@ static bool _badblocks_set(struct badblocks *bb, sector_t s, sector_t sectors,
> /* Invalid sectors number */
> return false;
>
> + if (s > ULLONG_MAX - sectors)
> + return false;
> +
The original idea was to let caller to handle such overflow stuffs.
If you want to handle these things here, the above lines are not
enough indeed. You need to considering more conditions, I used to
unlike such codes, but now realize maybe such checks should be
added.
Here are some items in my brain,
- s + sectors overflow
- too big bb->shift
- 1 << bb->shift overflow
- the rounddown result even smaller then start s for large bb->shift
> if (bb->shift) {
> /* round the start down, and the end up */
> sector_t next = s + sectors;
>
> - rounddown(s, 1 << bb->shift);
> - roundup(next, 1 << bb->shift);
> + s = round_down(s, 1 << bb->shift);
> + next = round_up(next, 1 << bb->shift);
> sectors = next - s;
I suggest to divide the patch into two. The first one just contains
the above round_down and round_up fix. Another one check the rested
value ranges.
Also add Fixes tag to my origin badblocks rewrite commit, and add
a CC line to stable@vger.kernel.org
Thanks.
Coly Li
> }
>
> + if (sectors == 0)
> + return false;
> +
> write_seqlock_irqsave(&bb->lock, flags);
>
> bad.ack = acknowledged;
> @@ -1061,6 +1067,9 @@ static bool _badblocks_clear(struct badblocks *bb, sector_t s, sector_t sectors)
> /* Invalid sectors number */
> return false;
>
> + if (s > ULLONG_MAX - sectors)
> + return false;
> +
> if (bb->shift) {
> sector_t target;
>
> @@ -1071,11 +1080,17 @@ 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;
> - roundup(s, 1 << bb->shift);
> - rounddown(target, 1 << bb->shift);
> - sectors = target - s;
> + s = round_up(s, 1 << bb->shift);
> + target = round_down(target, 1 << bb->shift);
> + if (target < s)
> + sectors = 0;
> + else
> + sectors = target - s;
> }
>
> + if (sectors == 0)
> + return false;
> +
> write_seqlock_irq(&bb->lock);
>
> bad.ack = true;
> @@ -1303,13 +1318,22 @@ int badblocks_check(struct badblocks *bb, sector_t s, sector_t sectors,
>
> WARN_ON(bb->shift < 0 || sectors == 0);
>
> + if (s > ULLONG_MAX - sectors)
> + return -EINVAL;
> +
> if (bb->shift > 0) {
> /* round the start down, and the end up */
> sector_t target = s + sectors;
>
> - rounddown(s, 1 << bb->shift);
> - roundup(target, 1 << bb->shift);
> - sectors = target - s;
> + s = round_down(s, 1 << bb->shift);
> + target = round_up(target, 1 << bb->shift);
> + if (target < s)
> + sectors = 0;
> + else
> + sectors = target - s;
> +
> + if (sectors == 0)
> + return 0;
> }
>
> retry:
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v6 0/2] badblocks: fix infinite loop and validate sector range/shift
2026-07-09 10:25 ` Coly Li
@ 2026-07-09 13:19 ` 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 13:19 ` [PATCH v6 2/2] badblocks: validate sector range and shift before rounding Ramesh Adhikari
0 siblings, 2 replies; 10+ messages in thread
From: Ramesh Adhikari @ 2026-07-09 13:19 UTC (permalink / raw)
To: colyli, axboe; +Cc: gregkh, linux-block, stable, Ramesh Adhikari
This replaces the single-patch v5 with a two-patch series, per Coly's
review.
v1-v4 chased symptoms of the same underlying bug (an RCU stall found
by syzkaller through the nvdimm ioctl path, in _badblocks_check() /
badblocks_check() looping with a non-advancing range) before landing
on the root cause in v4: rounddown()/roundup() don't modify their
argument in place, so 's'/'next'/'target' were never actually
rounded.
v5 folded the round_down()/round_up() fix together with overflow and
zero-length guards into one patch. Coly's review on v5 pointed out
that:
- the overflow check that was there wasn't sufficient on its own
(only one of several range-validity conditions), and
- the round fix and the validation should be separate patches,
since they're independently useful and one is safe to backport
on its own.
This series:
1/2 is exactly the round_down()/round_up() fix, nothing else. This
is what actually stops the infinite loop and also fixes the
32-bit build breakage kernel test robot reported on v1
(rounddown()/roundup() do 64-bit division/modulo on sector_t,
requiring libgcc helpers not linked into the kernel).
2/2 adds the range/shift validation Coly asked for: s+sectors
overflow, bb->shift too large to shift a sector_t by (bb->shift
is populated in drivers/md/md.c straight from an unvalidated
on-disk superblock byte), and detecting when round_up()/
round_down() themselves wrap near ULLONG_MAX.
Both are tagged Fixes: aa511ff8218b ("badblocks: switch to the
improved badblock handling code") and Cc: stable, since that's the
commit that introduced this code path.
Ramesh Adhikari (2):
badblocks: fix in-place round_up/round_down usage bug
badblocks: validate sector range and shift before rounding
block/badblocks.c | 52 ++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 45 insertions(+), 7 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v6 1/2] badblocks: fix in-place round_up/round_down usage bug
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 ` 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
1 sibling, 1 reply; 10+ messages in thread
From: Ramesh Adhikari @ 2026-07-09 13:19 UTC (permalink / raw)
To: colyli, axboe
Cc: gregkh, linux-block, stable, Ramesh Adhikari, kernel test robot
rounddown() and roundup() do not modify their first argument in
place; they return the rounded value. _badblocks_set(),
_badblocks_clear() and badblocks_check() were calling them as
bare statements and discarding the result, so 's' (and 'next'/
'target') were never actually rounded. Depending on the caller's
alignment this can leave 'sectors' unchanged or, in the reported
case, produce a range whose end never advances, causing
_badblocks_check()/badblocks_check() to loop with a non-advancing
cursor and stall the CPU (RCU stall) when called through the
nvdimm ioctl path via nvdimm_clear_badblocks_region().
rounddown()/roundup() also do division/modulo on the sector_t
(u64) operand, which requires libgcc helpers (__aeabi_uldivmod,
__umoddi3) that are not linked into the kernel on 32-bit builds,
breaking the build on arm/i386 (reported by kernel test robot).
Switch to round_down()/round_up() (include/linux/math.h), which
are mask-based, assign their result back to the variable being
rounded, and require no 64-bit division, fixing both the
non-rounding bug and the 32-bit build breakage.
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202604301231.IpPh4AiH-lkp@intel.com/
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 | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/block/badblocks.c b/block/badblocks.c
index ece64e76fe8..1f786b193fb 100644
--- a/block/badblocks.c
+++ b/block/badblocks.c
@@ -857,8 +857,8 @@ static bool _badblocks_set(struct badblocks *bb, sector_t s, sector_t sectors,
/* round the start down, and the end up */
sector_t next = s + sectors;
- rounddown(s, 1 << bb->shift);
- roundup(next, 1 << bb->shift);
+ s = round_down(s, 1 << bb->shift);
+ next = round_up(next, 1 << bb->shift);
sectors = next - s;
}
@@ -1071,8 +1071,8 @@ 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;
- roundup(s, 1 << bb->shift);
- rounddown(target, 1 << bb->shift);
+ s = round_up(s, 1 << bb->shift);
+ target = round_down(target, 1 << bb->shift);
sectors = target - s;
}
@@ -1307,8 +1307,8 @@ int badblocks_check(struct badblocks *bb, sector_t s, sector_t sectors,
/* round the start down, and the end up */
sector_t target = s + sectors;
- rounddown(s, 1 << bb->shift);
- roundup(target, 1 << bb->shift);
+ s = round_down(s, 1 << bb->shift);
+ target = round_up(target, 1 << bb->shift);
sectors = target - s;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6 2/2] badblocks: validate sector range and shift before rounding
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 13:19 ` Ramesh Adhikari
1 sibling, 0 replies; 10+ messages in thread
From: Ramesh Adhikari @ 2026-07-09 13:19 UTC (permalink / raw)
To: colyli, axboe; +Cc: gregkh, linux-block, stable, Ramesh Adhikari
_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;
+
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;
+
+ 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;
+
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;
+
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;
+
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;
+
+ 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;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v6 1/2] badblocks: fix in-place round_up/round_down usage bug
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
0 siblings, 0 replies; 10+ messages in thread
From: Coly Li @ 2026-07-09 15:16 UTC (permalink / raw)
To: Ramesh Adhikari; +Cc: axboe, gregkh, linux-block, stable, kernel test robot
On Thu, Jul 09, 2026 at 06:49:03PM +0800, Ramesh Adhikari wrote:
> rounddown() and roundup() do not modify their first argument in
> place; they return the rounded value. _badblocks_set(),
> _badblocks_clear() and badblocks_check() were calling them as
> bare statements and discarding the result, so 's' (and 'next'/
> 'target') were never actually rounded. Depending on the caller's
> alignment this can leave 'sectors' unchanged or, in the reported
> case, produce a range whose end never advances, causing
> _badblocks_check()/badblocks_check() to loop with a non-advancing
> cursor and stall the CPU (RCU stall) when called through the
> nvdimm ioctl path via nvdimm_clear_badblocks_region().
>
> rounddown()/roundup() also do division/modulo on the sector_t
> (u64) operand, which requires libgcc helpers (__aeabi_uldivmod,
> __umoddi3) that are not linked into the kernel on 32-bit builds,
> breaking the build on arm/i386 (reported by kernel test robot).
>
> Switch to round_down()/round_up() (include/linux/math.h), which
> are mask-based, assign their result back to the variable being
> rounded, and require no 64-bit division, fixing both the
> non-rounding bug and the 32-bit build breakage.
>
> Reported-by: kernel test robot <lkp@intel.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202604301231.IpPh4AiH-lkp@intel.com/
> Fixes: aa511ff8218b ("badblocks: switch to the improved badblock handling code")
> Cc: stable@vger.kernel.org
> Signed-off-by: Ramesh Adhikari <adhikari.resume@gmail.com>
It looks good to me.
Reviewed-by: Coly Li <colyli@fygo.io>
Thanks.
Coly Li
> ---
> block/badblocks.c | 12 ++++++------
> 1 file changed, 6 insertions(+), 6 deletions(-)
>
> diff --git a/block/badblocks.c b/block/badblocks.c
> index ece64e76fe8..1f786b193fb 100644
> --- a/block/badblocks.c
> +++ b/block/badblocks.c
> @@ -857,8 +857,8 @@ static bool _badblocks_set(struct badblocks *bb, sector_t s, sector_t sectors,
> /* round the start down, and the end up */
> sector_t next = s + sectors;
>
> - rounddown(s, 1 << bb->shift);
> - roundup(next, 1 << bb->shift);
> + s = round_down(s, 1 << bb->shift);
> + next = round_up(next, 1 << bb->shift);
> sectors = next - s;
> }
>
> @@ -1071,8 +1071,8 @@ 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;
> - roundup(s, 1 << bb->shift);
> - rounddown(target, 1 << bb->shift);
> + s = round_up(s, 1 << bb->shift);
> + target = round_down(target, 1 << bb->shift);
> sectors = target - s;
> }
>
> @@ -1307,8 +1307,8 @@ int badblocks_check(struct badblocks *bb, sector_t s, sector_t sectors,
> /* round the start down, and the end up */
> sector_t target = s + sectors;
>
> - rounddown(s, 1 << bb->shift);
> - roundup(target, 1 << bb->shift);
> + s = round_down(s, 1 << bb->shift);
> + target = round_up(target, 1 << bb->shift);
> sectors = target - s;
> }
>
> --
> 2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-07-09 15:16 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox