Hello,

  it can happen there are files larger than s_maxbytes on a fs. Attached
patch tries to make VFS/mm handle this gracefully. More details in the
changelog. Any comments / objections?

                                                                Honza

-- 
Jan Kara <[EMAIL PROTECTED]>
SUSE Labs, CR
---

Although we don't allow writes over s_maxbytes, it can happen that a file's
size is larger than s_maxbytes. For example we can write the file from
a computer with a different architecture (which has larger s_maxbytes),
boot a kernel with a different set of config options (CONFIG_LBD...), etc.
Thus we have to make sure we don't crash / corrupt data when seeing such
file (page offset of the last page needn't fit into pgoff_t). Firstly, we
make read() and mmap() return error when user tries to access the file
above s_maxbytes, secondly we introduce a function i_size_read_trunc() which
returns min(i_size, s_maxbytes) and use it when determining maximal page
offset we are interested in.

Signed-off-by: Jan Kara <[EMAIL PROTECTED]>

diff --git a/fs/buffer.c b/fs/buffer.c
index 7249e01..3861118 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -1623,7 +1623,7 @@ static int __block_write_full_page(struct inode *inode, 
struct page *page,
 
        BUG_ON(!PageLocked(page));
 
-       last_block = (i_size_read(inode) - 1) >> inode->i_blkbits;
+       last_block = (i_size_read_trunc(inode) - 1) >> inode->i_blkbits;
 
        if (!page_has_buffers(page)) {
                create_empty_buffers(page, blocksize,
@@ -2084,7 +2084,7 @@ int block_read_full_page(struct page *page, get_block_t 
*get_block)
        head = page_buffers(page);
 
        iblock = (sector_t)page->index << (PAGE_CACHE_SHIFT - inode->i_blkbits);
-       lblock = (i_size_read(inode)+blocksize-1) >> inode->i_blkbits;
+       lblock = (i_size_read_trunc(inode)+blocksize-1) >> inode->i_blkbits;
        bh = head;
        nr = 0;
        i = 0;
@@ -2347,7 +2347,7 @@ block_page_mkwrite(struct vm_area_struct *vma, struct 
page *page,
        int ret = -EINVAL;
 
        lock_page(page);
-       size = i_size_read(inode);
+       size = i_size_read_trunc(inode);
        if ((page->mapping != inode->i_mapping) ||
            (page_offset(page) > size)) {
                /* page got truncated out from underneath us */
@@ -2603,7 +2603,7 @@ int nobh_writepage(struct page *page, get_block_t 
*get_block,
                        struct writeback_control *wbc)
 {
        struct inode * const inode = page->mapping->host;
-       loff_t i_size = i_size_read(inode);
+       loff_t i_size = i_size_read_trunc(inode);
        const pgoff_t end_index = i_size >> PAGE_CACHE_SHIFT;
        unsigned offset;
        int ret;
@@ -2803,7 +2803,7 @@ int block_write_full_page(struct page *page, get_block_t 
*get_block,
                        struct writeback_control *wbc)
 {
        struct inode * const inode = page->mapping->host;
-       loff_t i_size = i_size_read(inode);
+       loff_t i_size = i_size_read_trunc(inode);
        const pgoff_t end_index = i_size >> PAGE_CACHE_SHIFT;
        unsigned offset;
 
diff --git a/fs/direct-io.c b/fs/direct-io.c
index acf0da1..8223868 100644
--- a/fs/direct-io.c
+++ b/fs/direct-io.c
@@ -525,8 +525,8 @@ static int get_more_blocks(struct dio *dio)
 
                create = dio->rw & WRITE;
                if (dio->lock_type == DIO_LOCKING) {
-                       if (dio->block_in_file < (i_size_read(dio->inode) >>
-                                                       dio->blkbits))
+                       if (dio->block_in_file < (i_size_read_trunc(dio->inode)
+                                                       >> dio->blkbits))
                                create = 0;
                } else if (dio->lock_type == DIO_NO_LOCKING) {
                        create = 0;
@@ -870,7 +870,8 @@ do_holes:
                                 * Be sure to account for a partial block as the
                                 * last block in the file
                                 */
-                               i_size_aligned = ALIGN(i_size_read(dio->inode),
+                               i_size_aligned =
+                                       ALIGN(i_size_read_trunc(dio->inode),
                                                        1 << blkbits);
                                if (dio->block_in_file >=
                                                i_size_aligned >> blkbits) {
@@ -961,7 +962,7 @@ direct_io_worker(int rw, struct kiocb *iocb, struct inode 
*inode,
        dio->next_block_for_io = -1;
 
        dio->iocb = iocb;
-       dio->i_size = i_size_read(inode);
+       dio->i_size = i_size_read_trunc(inode);
 
        spin_lock_init(&dio->bio_lock);
        dio->refcount = 1;
diff --git a/fs/mpage.c b/fs/mpage.c
index d54f8f8..c666089 100644
--- a/fs/mpage.c
+++ b/fs/mpage.c
@@ -190,7 +190,8 @@ do_mpage_readpage(struct bio *bio, struct page *page, 
unsigned nr_pages,
 
        block_in_file = (sector_t)page->index << (PAGE_CACHE_SHIFT - blkbits);
        last_block = block_in_file + nr_pages * blocks_per_page;
-       last_block_in_file = (i_size_read(inode) + blocksize - 1) >> blkbits;
+       last_block_in_file = (i_size_read_trunc(inode) + blocksize - 1) >>
+                               blkbits;
        if (last_block > last_block_in_file)
                last_block = last_block_in_file;
        page_block = 0;
@@ -468,7 +469,7 @@ static int __mpage_writepage(struct page *page, struct 
writeback_control *wbc,
        struct block_device *boundary_bdev = NULL;
        int length;
        struct buffer_head map_bh;
-       loff_t i_size = i_size_read(inode);
+       loff_t i_size = i_size_read_trunc(inode);
        int ret = 0;
 
        if (page_has_buffers(page)) {
diff --git a/fs/read_write.c b/fs/read_write.c
index ea1f94c..ed91acc 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -16,6 +16,7 @@
 #include <linux/syscalls.h>
 #include <linux/pagemap.h>
 #include <linux/splice.h>
+#include <linux/mount.h>
 #include "read_write.h"
 
 #include <asm/uaccess.h>
@@ -263,6 +264,11 @@ ssize_t vfs_read(struct file *file, char __user *buf, 
size_t count, loff_t *pos)
                return -EINVAL;
        if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
                return -EFAULT;
+       if (unlikely(*pos + count > file->f_vfsmnt->mnt_sb->s_maxbytes)) {
+               if (*pos >= file->f_vfsmnt->mnt_sb->s_maxbytes)
+                       return -EOVERFLOW;
+               count = file->f_vfsmnt->mnt_sb->s_maxbytes - *pos;
+       }
 
        ret = rw_verify_area(READ, file, pos, count);
        if (ret >= 0) {
diff --git a/include/linux/fs.h b/include/linux/fs.h
index b3ec4a4..d24ef6c 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1394,6 +1394,16 @@ static inline void inode_dec_link_count(struct inode 
*inode)
        mark_inode_dirty(inode);
 }
 
+/* Truncate i_size at s_maxbytes so that pagecache doesn't have problems */
+static inline loff_t i_size_read_trunc(const struct inode *inode)
+{
+       loff_t i_size = i_size_read(inode);
+
+       if (unlikely(inode->i_sb->s_maxbytes < i_size))
+               return inode->i_sb->s_maxbytes;
+       return i_size;
+}
+
 extern void touch_atime(struct vfsmount *mnt, struct dentry *dentry);
 static inline void file_accessed(struct file *file)
 {
diff --git a/mm/filemap.c b/mm/filemap.c
index 188cf5f..eb97b9e 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -362,7 +362,7 @@ EXPORT_SYMBOL(sync_page_range_nolock);
  */
 int filemap_fdatawait(struct address_space *mapping)
 {
-       loff_t i_size = i_size_read(mapping->host);
+       loff_t i_size = i_size_read_trunc(mapping->host);
 
        if (i_size == 0)
                return 0;
@@ -912,7 +912,7 @@ page_ok:
                 * another truncate extends the file - this is desired though).
                 */
 
-               isize = i_size_read(inode);
+               isize = i_size_read_trunc(inode);
                end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
                if (unlikely(!isize || index > end_index)) {
                        page_cache_release(page);
@@ -1298,7 +1298,8 @@ int filemap_fault(struct vm_area_struct *vma, struct 
vm_fault *vmf)
        int did_readaround = 0;
        int ret = 0;
 
-       size = (i_size_read(inode) + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
+       size = (i_size_read_trunc(inode) + PAGE_CACHE_SIZE - 1) >>
+               PAGE_CACHE_SHIFT;
        if (vmf->pgoff >= size)
                return VM_FAULT_SIGBUS;
 
@@ -1373,7 +1374,7 @@ retry_find:
                goto page_not_uptodate;
 
        /* Must recheck i_size under page lock */
-       size = (i_size_read(inode) + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
+       size = (i_size_read_trunc(inode) + PAGE_CACHE_SIZE - 1) >> 
PAGE_CACHE_SHIFT;
        if (unlikely(vmf->pgoff >= size)) {
                unlock_page(page);
                page_cache_release(page);
diff --git a/mm/filemap_xip.c b/mm/filemap_xip.c
index 32132f3..df26ef5 100644
--- a/mm/filemap_xip.c
+++ b/mm/filemap_xip.c
@@ -63,7 +63,7 @@ do_xip_mapping_read(struct address_space *mapping,
        index = *ppos >> PAGE_CACHE_SHIFT;
        offset = *ppos & ~PAGE_CACHE_MASK;
 
-       isize = i_size_read(inode);
+       isize = i_size_read_trunc(inode);
        if (!isize)
                goto out;
 
@@ -219,7 +219,7 @@ static int xip_file_fault(struct vm_area_struct *area, 
struct vm_fault *vmf)
 
        /* XXX: are VM_FAULT_ codes OK? */
 
-       size = (i_size_read(inode) + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
+       size = (i_size_read_trunc(inode) + PAGE_CACHE_SIZE - 1) >> 
PAGE_CACHE_SHIFT;
        if (vmf->pgoff >= size)
                return VM_FAULT_SIGBUS;
 
diff --git a/mm/mmap.c b/mm/mmap.c
index facc1a7..b2ee331 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -980,6 +980,13 @@ unsigned long do_mmap_pgoff(struct file * file, unsigned 
long addr,
                        if (locks_verify_locked(inode))
                                return -EAGAIN;
 
+                       /*
+                        * Make sure we don't map more than fs is able to handle
+                        */
+                       if ((((loff_t)pgoff) << PAGE_SHIFT) + len >
+                           inode->i_sb->s_maxbytes)
+                               return -EINVAL;
+
                        vm_flags |= VM_SHARED | VM_MAYSHARE;
                        if (!(file->f_mode & FMODE_WRITE))
                                vm_flags &= ~(VM_MAYWRITE | VM_SHARED);
diff --git a/mm/readahead.c b/mm/readahead.c
index c9c50ca..8ec8d4a 100644
--- a/mm/readahead.c
+++ b/mm/readahead.c
@@ -131,7 +131,7 @@ __do_page_cache_readahead(struct address_space *mapping, 
struct file *filp,
        LIST_HEAD(page_pool);
        int page_idx;
        int ret = 0;
-       loff_t isize = i_size_read(inode);
+       loff_t isize = i_size_read_trunc(inode);
 
        if (isize == 0)
                goto out;
diff --git a/mm/swapfile.c b/mm/swapfile.c
index f071648..88d3bb1 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1082,7 +1082,7 @@ static int setup_swap_extents(struct swap_info_struct 
*sis, sector_t *span)
         */
        probe_block = 0;
        page_no = 0;
-       last_block = i_size_read(inode) >> blkbits;
+       last_block = i_size_read_trunc(inode) >> blkbits;
        while ((probe_block + blocks_per_page) <= last_block &&
                        page_no < sis->max) {
                unsigned block_in_page;
@@ -1517,7 +1517,7 @@ asmlinkage long sys_swapon(const char __user * 
specialfile, int swap_flags)
                goto bad_swap;
        }
 
-       swapfilesize = i_size_read(inode) >> PAGE_SHIFT;
+       swapfilesize = i_size_read_trunc(inode) >> PAGE_SHIFT;
 
        /*
         * Read the swap header.
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to