block: validate user space vectors during extraction

The bio-based drivers don't necessarily check the alignment split, and
stacking block drivers don't always handle a misalignment detected after
submitting the bio. Validate user vectors against the device's
dma_alignment as the bio is built from the iov_iter, rejecting
misaligned early with -EINVAL.

Cc: stable@vger.kernel.org
Fixes: 5ff3f74e14 ("block: simplify direct io validity check")
Fixes: 7eac331869 ("iomap: simplify direct io validity check")
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Keith Busch <kbusch@kernel.org>
Link: https://patch.msgid.link/20260720201057.1862857-6-kbusch@meta.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Keith Busch
2026-07-31 08:10:49 -06:00
committed by Jens Axboe
parent c5059c1af2
commit 14b007e178
7 changed files with 74 additions and 8 deletions
+53 -3
View File
@@ -1221,10 +1221,45 @@ static int bio_iov_iter_align_down(struct bio *bio, struct iov_iter *iter,
return 0;
}
#ifdef CONFIG_DEBUG_KERNEL
static inline bool bio_iov_bvec_aligned(const struct bio *bio,
unsigned mem_align_mask)
{
struct bvec_iter iter;
struct bio_vec bv;
/*
* Correct callers never break the alignment requirements, so this
* exhaustive check is only paid for in debug builds.
*/
for_each_mp_bvec(bv, bio->bi_io_vec, iter, bio->bi_iter)
if ((bv.bv_offset | bv.bv_len) & mem_align_mask)
return false;
return true;
}
#else
static inline bool bio_iov_bvec_aligned(const struct bio *bio,
unsigned mem_align_mask)
{
/*
* We forward the bio_vec as-is, so ITER_BVEC callers must provide
* segments already aligned to the device's DMA alignment. The only
* unchecked user-controllable offset that reaches here is an io_uring
* registered buffer where just the first segment can be unaligned
* (the rest is virtually contiguous), so checking only that one is
* sufficient to know if the entire vector is valid.
*/
return !(mp_bvec_iter_offset(bio->bi_io_vec, bio->bi_iter) &
mem_align_mask);
}
#endif
/**
* bio_iov_iter_get_pages - add user or kernel pages to a bio
* @bio: bio to add pages to
* @iter: iov iterator describing the region to be added
* @mem_align_mask: the mask the source address and length must be aligned to,
* 0 for no requirement
* @len_align_mask: the mask to align the total size to, 0 for any length
*
* This takes either an iterator pointing to user memory, or one pointing to
@@ -1243,7 +1278,7 @@ static int bio_iov_iter_align_down(struct bio *bio, struct iov_iter *iter,
* is returned only if 0 pages could be pinned.
*/
int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,
unsigned len_align_mask)
unsigned mem_align_mask, unsigned len_align_mask)
{
iov_iter_extraction_t flags = 0;
@@ -1252,6 +1287,10 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,
if (iov_iter_is_bvec(iter)) {
bio_iov_bvec_set(bio, iter);
if (!bio_iov_bvec_aligned(bio, mem_align_mask))
return -EINVAL;
iov_iter_advance(iter, bio->bi_iter.bi_size);
return 0;
}
@@ -1266,8 +1305,19 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,
ret = iov_iter_extract_bvecs(iter, bio->bi_io_vec,
BIO_MAX_SIZE - bio->bi_iter.bi_size,
&bio->bi_vcnt, bio->bi_max_vecs, flags);
&bio->bi_vcnt, bio->bi_max_vecs,
mem_align_mask, flags);
if (ret <= 0) {
/*
* A misaligned vector fails the whole I/O. Release any
* pages pinned by earlier iterations before returning
* since this bio won't be submitted to release them.
*/
if (ret == -EINVAL) {
bio_release_pages(bio, false);
bio_clear_flag(bio, BIO_PAGE_PINNED);
bio->bi_vcnt = 0;
}
if (!bio->bi_vcnt)
return ret;
break;
@@ -1380,7 +1430,7 @@ static int bio_iov_iter_bounce_read(struct bio *bio, struct iov_iter *iter,
do {
ret = iov_iter_extract_bvecs(iter, bio->bi_io_vec + 1, len,
&bio->bi_vcnt, bio->bi_max_vecs - 1, 0);
&bio->bi_vcnt, bio->bi_max_vecs - 1, 0, 0);
if (ret <= 0) {
if (!bio->bi_vcnt)
goto out_folio_put;
+1 -1
View File
@@ -274,7 +274,7 @@ static int bio_map_user_iov(struct request *rq, struct iov_iter *iter,
* No alignment requirements on our part to support arbitrary
* passthrough commands.
*/
ret = bio_iov_iter_get_pages(bio, iter, 0);
ret = bio_iov_iter_get_pages(bio, iter, 0, 0);
if (ret)
goto out_put;
ret = blk_rq_append_bio(rq, bio);
+1 -1
View File
@@ -46,7 +46,7 @@ static bool blkdev_dio_invalid(struct block_device *bdev, struct kiocb *iocb,
static inline int blkdev_iov_iter_get_pages(struct bio *bio,
struct iov_iter *iter, struct block_device *bdev)
{
return bio_iov_iter_get_pages(bio, iter,
return bio_iov_iter_get_pages(bio, iter, bdev_dma_alignment(bdev),
bdev_logical_block_size(bdev) - 1);
}