fs: move falloc collapse range check into the filesystem methods
authorLukas Czerner <lczerner@redhat.com>
Sat, 12 Apr 2014 13:56:41 +0000 (09:56 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Sat, 12 Apr 2014 13:56:41 +0000 (09:56 -0400)
Currently in do_fallocate in collapse range case we're checking
whether offset + len is not bigger than i_size.  However there is
nothing which would prevent i_size from changing so the check is
pointless.  It should be done in the file system itself and the file
system needs to make sure that i_size is not going to change.  The
i_size check for the other fallocate modes are also done in the
filesystems.

As it is now we can easily crash the kernel by having two processes
doing truncate and fallocate collapse range at the same time.  This
can be reproduced on ext4 and it is theoretically possible on xfs even
though I was not able to trigger it with this simple test.

This commit removes the check from do_fallocate and adds it to the
file system.

Signed-off-by: Lukas Czerner <lczerner@redhat.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Acked-by: Dave Chinner <david@fromorbit.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
fs/ext4/extents.c
fs/open.c
fs/xfs/xfs_file.c

index ed4ec48239b6b3672755d68079fa8131ec51acec..ac5460d0d133706d4efd7a1e2affbb43e0c9b46c 100644 (file)
@@ -5368,8 +5368,6 @@ int ext4_collapse_range(struct inode *inode, loff_t offset, loff_t len)
        loff_t new_size;
        int ret;
 
-       BUG_ON(offset + len > i_size_read(inode));
-
        /* Collapse range works only on fs block size aligned offsets. */
        if (offset & (EXT4_BLOCK_SIZE(sb) - 1) ||
            len & (EXT4_BLOCK_SIZE(sb) - 1))
@@ -5398,6 +5396,15 @@ int ext4_collapse_range(struct inode *inode, loff_t offset, loff_t len)
        /* Take mutex lock */
        mutex_lock(&inode->i_mutex);
 
+       /*
+        * There is no need to overlap collapse range with EOF, in which case
+        * it is effectively a truncate operation
+        */
+       if (offset + len >= i_size_read(inode)) {
+               ret = -EINVAL;
+               goto out_mutex;
+       }
+
        if (IS_SWAPFILE(inode)) {
                ret = -ETXTBSY;
                goto out_mutex;
index 3a83253d33731e65436ea5965f1645c658789aac..adf34202213a8bc6e5cb72e6546afebf595f82f7 100644 (file)
--- a/fs/open.c
+++ b/fs/open.c
@@ -284,14 +284,6 @@ int do_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
        if (((offset + len) > inode->i_sb->s_maxbytes) || ((offset + len) < 0))
                return -EFBIG;
 
-       /*
-        * There is no need to overlap collapse range with EOF, in which case
-        * it is effectively a truncate operation
-        */
-       if ((mode & FALLOC_FL_COLLAPSE_RANGE) &&
-           (offset + len >= i_size_read(inode)))
-               return -EINVAL;
-
        if (!file->f_op->fallocate)
                return -EOPNOTSUPP;
 
index f7abff8c16ca7361d3e32f1842ae1daed15e651e..3cb528c4f27cbfb3e62a7edd160afe5003091944 100644 (file)
@@ -840,7 +840,15 @@ xfs_file_fallocate(
                        goto out_unlock;
                }
 
-               ASSERT(offset + len < i_size_read(inode));
+               /*
+                * There is no need to overlap collapse range with EOF,
+                * in which case it is effectively a truncate operation
+                */
+               if (offset + len >= i_size_read(inode)) {
+                       error = -EINVAL;
+                       goto out_unlock;
+               }
+
                new_size = i_size_read(inode) - len;
 
                error = xfs_collapse_file_space(ip, offset, len);