From: Eric Sandeen <sand...@redhat.com>

[ Upstream commit 3cc31fa65d85610574c0f6a474e89f4c419923d5 ]

iomap_is_partially_uptodate() is intended to check wither blocks within
the selected range of a not-uptodate page are uptodate; if the range we
care about is up to date, it's an optimization.

However, the iomap implementation continues to check all blocks up to
from+count, which is beyond the page, and can even be well beyond the
iop->uptodate bitmap.

I think the worst that will happen is that we may eventually find a zero
bit and return "not partially uptodate" when it would have otherwise
returned true, and skip the optimization.  Still, it's clearly an invalid
memory access that must be fixed.

So: fix this by limiting the search to within the page as is done in the
non-iomap variant, block_is_partially_uptodate().

Zorro noticed thiswhen KASAN went off for 512 byte blocks on a 64k
page system:

 BUG: KASAN: slab-out-of-bounds in iomap_is_partially_uptodate+0x1a0/0x1e0
 Read of size 8 at addr ffff800120c3a318 by task fsstress/22337

Reported-by: Zorro Lang <zl...@redhat.com>
Signed-off-by: Eric Sandeen <sand...@redhat.com>
Signed-off-by: Eric Sandeen <sand...@sandeen.net>
Reviewed-by: Darrick J. Wong <darrick.w...@oracle.com>
Signed-off-by: Darrick J. Wong <darrick.w...@oracle.com>
Signed-off-by: Sasha Levin <sas...@kernel.org>
---
 fs/iomap.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/fs/iomap.c b/fs/iomap.c
index ec15cf2ec696..e57fb1e534c5 100644
--- a/fs/iomap.c
+++ b/fs/iomap.c
@@ -488,16 +488,29 @@ iomap_readpages(struct address_space *mapping, struct 
list_head *pages,
 }
 EXPORT_SYMBOL_GPL(iomap_readpages);
 
+/*
+ * iomap_is_partially_uptodate checks whether blocks within a page are
+ * uptodate or not.
+ *
+ * Returns true if all blocks which correspond to a file portion
+ * we want to read within the page are uptodate.
+ */
 int
 iomap_is_partially_uptodate(struct page *page, unsigned long from,
                unsigned long count)
 {
        struct iomap_page *iop = to_iomap_page(page);
        struct inode *inode = page->mapping->host;
-       unsigned first = from >> inode->i_blkbits;
-       unsigned last = (from + count - 1) >> inode->i_blkbits;
+       unsigned len, first, last;
        unsigned i;
 
+       /* Limit range to one page */
+       len = min_t(unsigned, PAGE_SIZE - from, count);
+
+       /* First and last blocks in range within page */
+       first = from >> inode->i_blkbits;
+       last = (from + len - 1) >> inode->i_blkbits;
+
        if (iop) {
                for (i = first; i <= last; i++)
                        if (!test_bit(i, iop->uptodate))
-- 
2.19.1

Reply via email to