[PATCH RFC 3/5] block: validate bio bounds in the queue entered context

Chris S coshi036 at gmail.com
Mon Aug 10 14:12:45 PDT 2026


On 2026/05/20 9:25, Damien Le Moal wrote:
>> Should this be a pr_err_ratelimited() ?

FWIW, I think pr_info is deliberate and worth keeping.  The comment this
patch removes together with bio_check_eod() gave the reason:

  /*
   * Check whether this bio extends beyond the end of the device or
   * partition.  This may well happen - the kernel calls bread() without
   * checking the size of the device, e.g., when mounting a file system.
   */

So it fires on a legitimate and expected pattern, and pr_err would be
noisy for it.  It may be worth carrying that comment over to the new
location so the reason for the level does not get lost.

Keith, I checked this patch against the nvme_setup_rw() WARN I reported
(the second link in your cover letter).  __bio_split_to_limits() is
called from blk_mq_submit_bio() after bio_queue_enter(), on both the
cached-request path and the newly-allocated-request path, so the bounds
check is now serialized against the freeze window in
nvme_update_ns_info_block().  With the capacity forced to 0, the bio is
failed here and can no longer reach nvme_queue_rq(), which is exactly
the race I hit.  Thanks for picking it up, and sorry for the slow reply.

I'll drop my own "nvme: don't WARN on I/O to a namespace revalidated to
unusable metadata" patch in favour of this.

Reviewed-by: Chao Shi <coshi036 at gmail.com>

Best,
Chao

On Tue, May 19, 2026 at 1:23 PM Keith Busch <kbusch at meta.com> wrote:
>
> From: Keith Busch <kbusch at kernel.org>
>
> bio_check_eod() in submit_bio_noacct() validates that a bio does not
> extend beyond the partition's available sectors. This check runs before
> bio_queue_enter(), so it is not serialized against queue limit updates.
> A driver that freezes the queue, updates limits, changes the capacity,
> and unfreezes can race with a bio that passed the early check under the
> old capacity.
>
> Remove bio_check_eod() and replace it with a bounds check in
> __bio_split_to_limits(), which runs after the queue usage reference has
> been acquired. The check uses partition-aware arithmetic to validate
> both partition bounds and disk capacity in a single comparison that
> works correctly on the post-remap sector values.
>
> Signed-off-by: Keith Busch <kbusch at kernel.org>
> ---
>  block/blk-core.c | 26 --------------------------
>  block/blk.h      | 14 ++++++++++++++
>  2 files changed, 14 insertions(+), 26 deletions(-)
>
> diff --git a/block/blk-core.c b/block/blk-core.c
> index 92a802dc8042c..c200d0fc44fe7 100644
> --- a/block/blk-core.c
> +++ b/block/blk-core.c
> @@ -547,30 +547,6 @@ int should_fail_bio(struct bio *bio)
>  }
>  ALLOW_ERROR_INJECTION(should_fail_bio, ERRNO);
>
> -/*
> - * Check whether this bio extends beyond the end of the device or partition.
> - * This may well happen - the kernel calls bread() without checking the size of
> - * the device, e.g., when mounting a file system.
> - */
> -static inline int bio_check_eod(struct bio *bio)
> -{
> -       sector_t maxsector = bdev_nr_sectors(bio->bi_bdev);
> -       unsigned int nr_sectors = bio_sectors(bio);
> -
> -       if (nr_sectors &&
> -           (nr_sectors > maxsector ||
> -            bio->bi_iter.bi_sector > maxsector - nr_sectors)) {
> -               if (!maxsector)
> -                       return -EIO;
> -               pr_info_ratelimited("%s: attempt to access beyond end of device\n"
> -                                   "%pg: rw=%d, sector=%llu, nr_sectors = %u limit=%llu\n",
> -                                   current->comm, bio->bi_bdev, bio->bi_opf,
> -                                   bio->bi_iter.bi_sector, nr_sectors, maxsector);
> -               return -EIO;
> -       }
> -       return 0;
> -}
> -
>  /*
>   * Remap block n of partition p to block n+start(p) of the disk.
>   */
> @@ -802,8 +778,6 @@ void submit_bio_noacct(struct bio *bio)
>                 goto end_io;
>         bio_check_ro(bio);
>         if (!bio_flagged(bio, BIO_REMAPPED)) {
> -               if (unlikely(bio_check_eod(bio)))
> -                       goto end_io;
>                 if (bdev_is_partition(bdev) &&
>                     unlikely(blk_partition_remap(bio)))
>                         goto end_io;
> diff --git a/block/blk.h b/block/blk.h
> index bf1a80493ff1c..e70acb2d358e3 100644
> --- a/block/blk.h
> +++ b/block/blk.h
> @@ -423,6 +423,17 @@ static inline bool bio_may_need_split(struct bio *bio,
>  static inline struct bio *__bio_split_to_limits(struct bio *bio,
>                 const struct queue_limits *lim, unsigned int *nr_segs)
>  {
> +       if (unlikely(bio_end_sector(bio) > bdev_nr_sectors(bio->bi_bdev) +
> +                                          bio->bi_bdev->bd_start_sect)) {
> +               pr_info_ratelimited("%s: attempt to access beyond end of device\n"
> +                                   "%pg: rw=%d, sector=%llu, nr_sectors = %u limit=%llu\n",
> +                                   current->comm, bio->bi_bdev, bio->bi_opf,
> +                                   bio->bi_iter.bi_sector, bio_sectors(bio),
> +                                   bdev_nr_sectors(bio->bi_bdev) +
> +                                       bio->bi_bdev->bd_start_sect);
> +               goto ioerr;
> +       }
> +
>         switch (bio_op(bio)) {
>         case REQ_OP_READ:
>         case REQ_OP_WRITE:
> @@ -442,6 +453,9 @@ static inline struct bio *__bio_split_to_limits(struct bio *bio,
>                 *nr_segs = 0;
>                 return bio;
>         }
> +ioerr:
> +       bio_io_error(bio);
> +       return NULL;
>  }
>
>  /**
> --
> 2.53.0-Meta
>



More information about the Linux-nvme mailing list