]>
Commit | Line | Data |
---|---|---|
ba172962 SL |
1 | From c5a82a95fb919c4c32009bb32bff3abcb9534d0e Mon Sep 17 00:00:00 2001 |
2 | From: Carlos Maiolino <cmaiolino@redhat.com> | |
3 | Date: Tue, 26 Feb 2019 11:51:50 +0100 | |
4 | Subject: fs: fix guard_bio_eod to check for real EOD errors | |
5 | ||
6 | [ Upstream commit dce30ca9e3b676fb288c33c1f4725a0621361185 ] | |
7 | ||
8 | guard_bio_eod() can truncate a segment in bio to allow it to do IO on | |
9 | odd last sectors of a device. | |
10 | ||
11 | It already checks if the IO starts past EOD, but it does not consider | |
12 | the possibility of an IO request starting within device boundaries can | |
13 | contain more than one segment past EOD. | |
14 | ||
15 | In such cases, truncated_bytes can be bigger than PAGE_SIZE, and will | |
16 | underflow bvec->bv_len. | |
17 | ||
18 | Fix this by checking if truncated_bytes is lower than PAGE_SIZE. | |
19 | ||
20 | This situation has been found on filesystems such as isofs and vfat, | |
21 | which doesn't check the device size before mount, if the device is | |
22 | smaller than the filesystem itself, a readahead on such filesystem, | |
23 | which spans EOD, can trigger this situation, leading a call to | |
24 | zero_user() with a wrong size possibly corrupting memory. | |
25 | ||
26 | I didn't see any crash, or didn't let the system run long enough to | |
27 | check if memory corruption will be hit somewhere, but adding | |
28 | instrumentation to guard_bio_end() to check truncated_bytes size, was | |
29 | enough to see the error. | |
30 | ||
31 | The following script can trigger the error. | |
32 | ||
33 | MNT=/mnt | |
34 | IMG=./DISK.img | |
35 | DEV=/dev/loop0 | |
36 | ||
37 | mkfs.vfat $IMG | |
38 | mount $IMG $MNT | |
39 | cp -R /etc $MNT &> /dev/null | |
40 | umount $MNT | |
41 | ||
42 | losetup -D | |
43 | ||
44 | losetup --find --show --sizelimit 16247280 $IMG | |
45 | mount $DEV $MNT | |
46 | ||
47 | find $MNT -type f -exec cat {} + >/dev/null | |
48 | ||
49 | Kudos to Eric Sandeen for coming up with the reproducer above | |
50 | ||
51 | Reviewed-by: Ming Lei <ming.lei@redhat.com> | |
52 | Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com> | |
53 | Signed-off-by: Jens Axboe <axboe@kernel.dk> | |
54 | Signed-off-by: Sasha Levin <sashal@kernel.org> | |
55 | --- | |
56 | fs/buffer.c | 7 +++++++ | |
57 | 1 file changed, 7 insertions(+) | |
58 | ||
59 | diff --git a/fs/buffer.c b/fs/buffer.c | |
60 | index c083c4b3c1e7..a550e0d8e965 100644 | |
61 | --- a/fs/buffer.c | |
62 | +++ b/fs/buffer.c | |
63 | @@ -3027,6 +3027,13 @@ void guard_bio_eod(int op, struct bio *bio) | |
64 | /* Uhhuh. We've got a bio that straddles the device size! */ | |
65 | truncated_bytes = bio->bi_iter.bi_size - (maxsector << 9); | |
66 | ||
67 | + /* | |
68 | + * The bio contains more than one segment which spans EOD, just return | |
69 | + * and let IO layer turn it into an EIO | |
70 | + */ | |
71 | + if (truncated_bytes > bvec->bv_len) | |
72 | + return; | |
73 | + | |
74 | /* Truncate the bio.. */ | |
75 | bio->bi_iter.bi_size -= truncated_bytes; | |
76 | bvec->bv_len -= truncated_bytes; | |
77 | -- | |
78 | 2.19.1 | |
79 |