All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH V2] block: add bio_truncate to fix guard_bio_eod
@ 2019-12-27 23:05 Ming Lei
  2019-12-28 16:45 ` Jens Axboe
  2020-01-08 13:37 ` Christoph Hellwig
  0 siblings, 2 replies; 5+ messages in thread
From: Ming Lei @ 2019-12-27 23:05 UTC (permalink / raw)
  To: Jens Axboe
  Cc: linux-block, Ming Lei, Carlos Maiolino, linux-fsdevel,
	syzbot+2b9e54155c8c25d8d165

Some filesystem, such as vfat, may send bio which crosses device boundary,
and the worse thing is that the IO request starting within device boundaries
can contain more than one segment past EOD.

Commit dce30ca9e3b6 ("fs: fix guard_bio_eod to check for real EOD errors")
tries to fix this issue by returning -EIO for this situation. However,
this way lets fs user code lose chance to handle -EIO, then sync_inodes_sb()
may hang for ever.

Also the current truncating on last segment is dangerous by updating the
last bvec, given bvec table becomes not immutable any more, and fs bio
users may not retrieve the truncated pages via bio_for_each_segment_all() in
its .end_io callback.

Fixes this issue by supporting multi-segment truncating. And the
approach is simpler:

- just update bio size since block layer can make correct bvec with
the updated bio size. Then bvec table becomes really immutable.

- zero all truncated segments for read bio

Cc: Carlos Maiolino <cmaiolino@redhat.com>
Cc: linux-fsdevel@vger.kernel.org
Fixed-by: dce30ca9e3b6 ("fs: fix guard_bio_eod to check for real EOD errors")
Reported-by: syzbot+2b9e54155c8c25d8d165@syzkaller.appspotmail.com
Signed-off-by: Ming Lei <ming.lei@redhat.com>
---
V2:
	- not export bio_truncate as suggested by Jens

 block/bio.c         | 39 +++++++++++++++++++++++++++++++++++++++
 fs/buffer.c         | 25 +------------------------
 include/linux/bio.h |  1 +
 3 files changed, 41 insertions(+), 24 deletions(-)

diff --git a/block/bio.c b/block/bio.c
index a5d75f6bf4c7..006bcc52a77e 100644
--- a/block/bio.c
+++ b/block/bio.c
@@ -538,6 +538,45 @@ void zero_fill_bio_iter(struct bio *bio, struct bvec_iter start)
 }
 EXPORT_SYMBOL(zero_fill_bio_iter);
 
+void bio_truncate(struct bio *bio, unsigned new_size)
+{
+	struct bio_vec bv;
+	struct bvec_iter iter;
+	unsigned int done = 0;
+	bool truncated = false;
+
+	if (new_size >= bio->bi_iter.bi_size)
+		return;
+
+	if (bio_data_dir(bio) != READ)
+		goto exit;
+
+	bio_for_each_segment(bv, bio, iter) {
+		if (done + bv.bv_len > new_size) {
+			unsigned offset;
+
+			if (!truncated)
+				offset = new_size - done;
+			else
+				offset = 0;
+			zero_user(bv.bv_page, offset, bv.bv_len - offset);
+			truncated = true;
+		}
+		done += bv.bv_len;
+	}
+
+ exit:
+	/*
+	 * Don't touch bvec table here and make it really immutable, since
+	 * fs bio user has to retrieve all pages via bio_for_each_segment_all
+	 * in its .end_bio() callback.
+	 *
+	 * It is enough to truncate bio by updating .bi_size since we can make
+	 * correct bvec with the updated .bi_size for drivers.
+	 */
+	bio->bi_iter.bi_size = new_size;
+}
+
 /**
  * bio_put - release a reference to a bio
  * @bio:   bio to release reference to
diff --git a/fs/buffer.c b/fs/buffer.c
index d8c7242426bb..e94a6619464c 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -3034,8 +3034,6 @@ static void end_bio_bh_io_sync(struct bio *bio)
 void guard_bio_eod(int op, struct bio *bio)
 {
 	sector_t maxsector;
-	struct bio_vec *bvec = bio_last_bvec_all(bio);
-	unsigned truncated_bytes;
 	struct hd_struct *part;
 
 	rcu_read_lock();
@@ -3061,28 +3059,7 @@ void guard_bio_eod(int op, struct bio *bio)
 	if (likely((bio->bi_iter.bi_size >> 9) <= maxsector))
 		return;
 
-	/* Uhhuh. We've got a bio that straddles the device size! */
-	truncated_bytes = bio->bi_iter.bi_size - (maxsector << 9);
-
-	/*
-	 * The bio contains more than one segment which spans EOD, just return
-	 * and let IO layer turn it into an EIO
-	 */
-	if (truncated_bytes > bvec->bv_len)
-		return;
-
-	/* Truncate the bio.. */
-	bio->bi_iter.bi_size -= truncated_bytes;
-	bvec->bv_len -= truncated_bytes;
-
-	/* ..and clear the end of the buffer for reads */
-	if (op == REQ_OP_READ) {
-		struct bio_vec bv;
-
-		mp_bvec_last_segment(bvec, &bv);
-		zero_user(bv.bv_page, bv.bv_offset + bv.bv_len,
-				truncated_bytes);
-	}
+	bio_truncate(bio, maxsector << 9);
 }
 
 static int submit_bh_wbc(int op, int op_flags, struct buffer_head *bh,
diff --git a/include/linux/bio.h b/include/linux/bio.h
index 3cdb84cdc488..853d92ceee64 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -470,6 +470,7 @@ extern struct bio *bio_copy_user_iov(struct request_queue *,
 				     gfp_t);
 extern int bio_uncopy_user(struct bio *);
 void zero_fill_bio_iter(struct bio *bio, struct bvec_iter iter);
+void bio_truncate(struct bio *bio, unsigned new_size);
 
 static inline void zero_fill_bio(struct bio *bio)
 {
-- 
2.20.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH V2] block: add bio_truncate to fix guard_bio_eod
  2019-12-27 23:05 [PATCH V2] block: add bio_truncate to fix guard_bio_eod Ming Lei
@ 2019-12-28 16:45 ` Jens Axboe
  2020-01-08 13:37 ` Christoph Hellwig
  1 sibling, 0 replies; 5+ messages in thread
From: Jens Axboe @ 2019-12-28 16:45 UTC (permalink / raw)
  To: Ming Lei
  Cc: linux-block, Carlos Maiolino, linux-fsdevel, syzbot+2b9e54155c8c25d8d165

On 12/27/19 4:05 PM, Ming Lei wrote:
> Some filesystem, such as vfat, may send bio which crosses device boundary,
> and the worse thing is that the IO request starting within device boundaries
> can contain more than one segment past EOD.
> 
> Commit dce30ca9e3b6 ("fs: fix guard_bio_eod to check for real EOD errors")
> tries to fix this issue by returning -EIO for this situation. However,
> this way lets fs user code lose chance to handle -EIO, then sync_inodes_sb()
> may hang for ever.
> 
> Also the current truncating on last segment is dangerous by updating the
> last bvec, given bvec table becomes not immutable any more, and fs bio
> users may not retrieve the truncated pages via bio_for_each_segment_all() in
> its .end_io callback.
> 
> Fixes this issue by supporting multi-segment truncating. And the
> approach is simpler:
> 
> - just update bio size since block layer can make correct bvec with
> the updated bio size. Then bvec table becomes really immutable.
> 
> - zero all truncated segments for read bio

Applied, thanks.

-- 
Jens Axboe


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH V2] block: add bio_truncate to fix guard_bio_eod
  2019-12-27 23:05 [PATCH V2] block: add bio_truncate to fix guard_bio_eod Ming Lei
  2019-12-28 16:45 ` Jens Axboe
@ 2020-01-08 13:37 ` Christoph Hellwig
  2020-01-09  2:05   ` Ming Lei
  1 sibling, 1 reply; 5+ messages in thread
From: Christoph Hellwig @ 2020-01-08 13:37 UTC (permalink / raw)
  To: Ming Lei
  Cc: Jens Axboe, linux-block, Carlos Maiolino, linux-fsdevel,
	syzbot+2b9e54155c8c25d8d165


> +void bio_truncate(struct bio *bio, unsigned new_size)

This function really needs a kerneldoc or similar comment describing
what it does in detail.

> +	if (bio_data_dir(bio) != READ)
> +		goto exit;

This really should check the passed in op for REQ_OP_READ directly instead
of just the direction on the potentially not fully set up bio.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH V2] block: add bio_truncate to fix guard_bio_eod
  2020-01-08 13:37 ` Christoph Hellwig
@ 2020-01-09  2:05   ` Ming Lei
  2020-01-09  7:17     ` Christoph Hellwig
  0 siblings, 1 reply; 5+ messages in thread
From: Ming Lei @ 2020-01-09  2:05 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Jens Axboe, linux-block, Carlos Maiolino, linux-fsdevel,
	syzbot+2b9e54155c8c25d8d165

On Wed, Jan 08, 2020 at 05:37:35AM -0800, Christoph Hellwig wrote:
> 
> > +void bio_truncate(struct bio *bio, unsigned new_size)
> 
> This function really needs a kerneldoc or similar comment describing
> what it does in detail.

OK, will do that.

> 
> > +	if (bio_data_dir(bio) != READ)
> > +		goto exit;
> 
> This really should check the passed in op for REQ_OP_READ directly instead
> of just the direction on the potentially not fully set up bio.

It has been addressed in:

https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux-block.git/commit/?h=block-5.5&id=802ca0381befe29ba0783e08e3369f9e87ef9d0d


Thanks,
Ming


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH V2] block: add bio_truncate to fix guard_bio_eod
  2020-01-09  2:05   ` Ming Lei
@ 2020-01-09  7:17     ` Christoph Hellwig
  0 siblings, 0 replies; 5+ messages in thread
From: Christoph Hellwig @ 2020-01-09  7:17 UTC (permalink / raw)
  To: Ming Lei
  Cc: Christoph Hellwig, Jens Axboe, linux-block, Carlos Maiolino,
	linux-fsdevel, syzbot+2b9e54155c8c25d8d165

On Thu, Jan 09, 2020 at 10:05:24AM +0800, Ming Lei wrote:
> OK, will do that.
> 
> > 
> > > +	if (bio_data_dir(bio) != READ)
> > > +		goto exit;
> > 
> > This really should check the passed in op for REQ_OP_READ directly instead
> > of just the direction on the potentially not fully set up bio.
> 
> It has been addressed in:
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux-block.git/commit/?h=block-5.5&id=802ca0381befe29ba0783e08e3369f9e87ef9d0d

Well, it fixes the bug introduced.  But it still uses bio_data_dir
instead of the explicit REQ_OP_READ check, and still uses a calling
convention that leads to such errors.

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2020-01-09  7:17 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-12-27 23:05 [PATCH V2] block: add bio_truncate to fix guard_bio_eod Ming Lei
2019-12-28 16:45 ` Jens Axboe
2020-01-08 13:37 ` Christoph Hellwig
2020-01-09  2:05   ` Ming Lei
2020-01-09  7:17     ` Christoph Hellwig

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.