From 9abbee491285303d78704e937a13ddcbb6cd0f79 Mon Sep 17 00:00:00 2001 From: Tomohiro Kusumi Date: Thu, 21 Feb 2019 03:14:36 +0900 Subject: [PATCH] Don't enter zvol's rangelock for read bio with size 0 The SCST driver (SCSI target driver implementation) and possibly others may issue read bio's with a length of zero bytes. Although this is unusual, such bio's issued under certain condition can cause kernel oops, due to how rangelock is implemented. rangelock_add_reader() is not made to handle overlap of two (or more) ranges from read bio's with the same offset when one of them has size of 0, even though they conceptually overlap. Allowing them to enter rangelock results in kernel oops by dereferencing invalid pointer, or assertion failure on AVL tree manipulation with debug enabled kernel module. For example, this happens when read bio whose (offset, size) is (0, 0) enters rangelock followed by another read bio with (0, 4096) when (0, 0) rangelock is still locked, when there are no pending write bio's. It can also happen with reverse order, which is (0, N) followed by (0, 0) when (0, N) is still locked. More details mentioned in #8379. Kernel Oops on ->make_request_fn() of ZFS volume https://github.com/zfsonlinux/zfs/issues/8379 Prevent this by returning bio with size 0 as success without entering rangelock. This has been done for write bio after checking flusher bio case (though not for the same reason), but not for read bio. Reviewed-by: Alek Pinchuk Reviewed-by: Brian Behlendorf Signed-off-by: Tomohiro Kusumi Closes #8379 Closes #8401 --- module/zfs/zvol.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/module/zfs/zvol.c b/module/zfs/zvol.c index cc0f1d1e03..5ca4f4670a 100644 --- a/module/zfs/zvol.c +++ b/module/zfs/zvol.c @@ -1008,6 +1008,16 @@ zvol_request(struct request_queue *q, struct bio *bio) zvol_write(zvr); } } else { + /* + * The SCST driver, and possibly others, may issue READ I/Os + * with a length of zero bytes. These empty I/Os contain no + * data and require no additional handling. + */ + if (size == 0) { + BIO_END_IO(bio, 0); + goto out; + } + zvr = kmem_alloc(sizeof (zv_request_t), KM_SLEEP); zvr->zv = zv; zvr->bio = bio;