[RHEL7,COMMIT] ms/fs: fix guard_bio_eod to check for real EOD errors

Submitted by Konstantin Khorenko on July 10, 2019, 8:25 a.m.

Details

Message ID 201907100825.x6A8PSYM027363@finist-ce7.sw.ru
State New
Series "Series without cover letter"
Headers show

Commit Message

Konstantin Khorenko July 10, 2019, 8:25 a.m.
The commit is pushed to "branch-rh7-3.10.0-957.21.3.vz7.106.x-ovz" and will appear at https://src.openvz.org/scm/ovz/vzkernel.git
after rh7-3.10.0-957.21.3.vz7.106.6
------>
commit 5f816f6345ad60fc2e07479798e85f83d5761ab8
Author: Carlos Maiolino <cmaiolino@redhat.com>
Date:   Tue Feb 26 11:51:50 2019 +0100

    ms/fs: fix guard_bio_eod to check for real EOD errors
    
    guard_bio_eod() can truncate a segment in bio to allow it to do IO on
    odd last sectors of a device.
    
    It already checks if the IO starts past EOD, but it does not consider
    the possibility of an IO request starting within device boundaries can
    contain more than one segment past EOD.
    
    In such cases, truncated_bytes can be bigger than PAGE_SIZE, and will
    underflow bvec->bv_len.
    
    Fix this by checking if truncated_bytes is lower than PAGE_SIZE.
    
    This situation has been found on filesystems such as isofs and vfat,
    which doesn't check the device size before mount, if the device is
    smaller than the filesystem itself, a readahead on such filesystem,
    which spans EOD, can trigger this situation, leading a call to
    zero_user() with a wrong size possibly corrupting memory.
    
    I didn't see any crash, or didn't let the system run long enough to
    check if memory corruption will be hit somewhere, but adding
    instrumentation to guard_bio_end() to check truncated_bytes size, was
    enough to see the error.
    
    The following script can trigger the error.
    
    MNT=/mnt
    IMG=./DISK.img
    DEV=/dev/loop0
    
    mkfs.vfat $IMG
    mount $IMG $MNT
    cp -R /etc $MNT &> /dev/null
    umount $MNT
    
    losetup -D
    
    losetup --find --show --sizelimit 16247280 $IMG
    mount $DEV $MNT
    
    find $MNT -type f -exec cat {} + >/dev/null
    
    Kudos to Eric Sandeen for coming up with the reproducer above
    
    Reviewed-by: Ming Lei <ming.lei@redhat.com>
    Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>
    Signed-off-by: Jens Axboe <axboe@kernel.dk>
    
    https://jira.sw.ru/browse/PSBM-96006
    (cherry picked from commit dce30ca9e3b676fb288c33c1f4725a0621361185)
    
    Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 fs/buffer.c | 7 +++++++
 1 file changed, 7 insertions(+)

Patch hide | download patch | download mbox

diff --git a/fs/buffer.c b/fs/buffer.c
index 2a0be4ad8ff1..e9a4c65eed73 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -3023,6 +3023,13 @@  void guard_bio_eod(int rw, struct bio *bio)
 	/* Uhhuh. We've got a bio that straddles the device size! */
 	truncated_bytes = bio->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_size -= truncated_bytes;
 	BUG_ON(truncated_bytes > bvec->bv_len);