Skip to content

Commit

Permalink
block: fix .bi_size overflow
Browse files Browse the repository at this point in the history
'bio->bi_iter.bi_size' is 'unsigned int', which at most hold 4G - 1
bytes.

Before 07173c3 ("block: enable multipage bvecs"), one bio can
include very limited pages, and usually at most 256, so the fs bio
size won't be bigger than 1M bytes most of times.

Since we support multi-page bvec, in theory one fs bio really can
be added > 1M pages, especially in case of hugepage, or big writeback
with too many dirty pages. Then there is chance in which .bi_size
is overflowed.

Fixes this issue by using bio_full() to check if the added segment may
overflow .bi_size.

Cc: Liu Yiding <[email protected]>
Cc: kernel test robot <[email protected]>
Cc: "Darrick J. Wong" <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Fixes: 07173c3 ("block: enable multipage bvecs")
Reviewed-by: Christoph Hellwig <[email protected]>
Signed-off-by: Ming Lei <[email protected]>
Signed-off-by: Jens Axboe <[email protected]>
  • Loading branch information
Ming Lei authored and axboe committed Jul 1, 2019
1 parent 5be1f9d commit 79d08f8
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 9 deletions.
10 changes: 5 additions & 5 deletions block/bio.c
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ static int __bio_add_pc_page(struct request_queue *q, struct bio *bio,
}
}

if (bio_full(bio))
if (bio_full(bio, len))
return 0;

if (bio->bi_vcnt >= queue_max_segments(q))
Expand Down Expand Up @@ -797,7 +797,7 @@ void __bio_add_page(struct bio *bio, struct page *page,
struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt];

WARN_ON_ONCE(bio_flagged(bio, BIO_CLONED));
WARN_ON_ONCE(bio_full(bio));
WARN_ON_ONCE(bio_full(bio, len));

bv->bv_page = page;
bv->bv_offset = off;
Expand All @@ -824,7 +824,7 @@ int bio_add_page(struct bio *bio, struct page *page,
bool same_page = false;

if (!__bio_try_merge_page(bio, page, len, offset, &same_page)) {
if (bio_full(bio))
if (bio_full(bio, len))
return 0;
__bio_add_page(bio, page, len, offset);
}
Expand Down Expand Up @@ -909,7 +909,7 @@ static int __bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter)
if (same_page)
put_page(page);
} else {
if (WARN_ON_ONCE(bio_full(bio)))
if (WARN_ON_ONCE(bio_full(bio, len)))
return -EINVAL;
__bio_add_page(bio, page, len, offset);
}
Expand Down Expand Up @@ -953,7 +953,7 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter)
ret = __bio_iov_bvec_add_pages(bio, iter);
else
ret = __bio_iov_iter_get_pages(bio, iter);
} while (!ret && iov_iter_count(iter) && !bio_full(bio));
} while (!ret && iov_iter_count(iter) && !bio_full(bio, 0));

if (is_bvec)
bio_set_flag(bio, BIO_NO_PAGE_REF);
Expand Down
2 changes: 1 addition & 1 deletion fs/iomap.c
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ iomap_readpage_actor(struct inode *inode, loff_t pos, loff_t length, void *data,
if (iop)
atomic_inc(&iop->read_count);

if (!ctx->bio || !is_contig || bio_full(ctx->bio)) {
if (!ctx->bio || !is_contig || bio_full(ctx->bio, plen)) {
gfp_t gfp = mapping_gfp_constraint(page->mapping, GFP_KERNEL);
int nr_vecs = (length + PAGE_SIZE - 1) >> PAGE_SHIFT;

Expand Down
2 changes: 1 addition & 1 deletion fs/xfs/xfs_aops.c
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,7 @@ xfs_add_to_ioend(
atomic_inc(&iop->write_count);

if (!merged) {
if (bio_full(wpc->ioend->io_bio))
if (bio_full(wpc->ioend->io_bio, len))
xfs_chain_bio(wpc->ioend, wbc, bdev, sector);
bio_add_page(wpc->ioend->io_bio, page, len, poff);
}
Expand Down
18 changes: 16 additions & 2 deletions include/linux/bio.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,23 @@ static inline void *bio_data(struct bio *bio)
return NULL;
}

static inline bool bio_full(struct bio *bio)
/**
* bio_full - check if the bio is full
* @bio: bio to check
* @len: length of one segment to be added
*
* Return true if @bio is full and one segment with @len bytes can't be
* added to the bio, otherwise return false
*/
static inline bool bio_full(struct bio *bio, unsigned len)
{
return bio->bi_vcnt >= bio->bi_max_vecs;
if (bio->bi_vcnt >= bio->bi_max_vecs)
return true;

if (bio->bi_iter.bi_size > UINT_MAX - len)
return true;

return false;
}

static inline bool bio_next_segment(const struct bio *bio,
Expand Down

0 comments on commit 79d08f8

Please sign in to comment.