__write() grows the file when the write end would pass EOF, but based
the decision on the same target dependent mixed sign comparison that
__read() had: f->f_pos + count > f->f_size compares the signed 64-bit
position and size (loff_t) against count (size_t), which is 32-bit on
32-bit arches and 64-bit on 64-bit arches. For negative file sizes
except for the FILE_SIZE_STREAM sentinel this is broken:

- On 32-bit arches count is converted to the signed 64-bit type of
  f->f_pos, so for e.g. f->f_size = -512 the comparison evaluated true.
  fsdev_truncate() was then called for the corrupted file size,
  attempting to grow the file to f->f_pos + count.

- On 64-bit arches size_t cannot be represented by signed 64-bit, so the
  usual arithmetic conversions turned the whole comparison unsigned:
  f->f_size = -512 was reinterpreted as a value near 2^64, the
  comparison stayed false, the file was never grown and the write
  proceeded unclamped against the bogus size.

Additionally, when writing at a position past the end of the file
(reachable via pwrite() with a large offset) and fsdev_truncate() failed
with -ENOSPC, the fallback count f->f_size - f->f_pos was negative and
wrapped to a huge value in the unsigned count.

__write() now rejects negative file sizes with -EINVAL, like __read()
does. The write end f->f_pos + count is computed in u64, making the
growth check target independent, and extending the file beyond
MAX_LFS_FILESIZE is rejected with -EFBIG. On -ENOSPC the write is now
limited to the bytes remaining until EOF, or aborted when f->f_pos is at
or past the end of the file, instead of wrapping the negative remainder.

Signed-off-by: Stefan Kerkmann <[email protected]>
---
 fs/fs.c | 42 +++++++++++++++++++++++++++++++-----------
 1 file changed, 31 insertions(+), 11 deletions(-)

diff --git a/fs/fs.c b/fs/fs.c
index a8f2b78294..3803decc2a 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -482,7 +482,10 @@ EXPORT_SYMBOL(read);
 
 static ssize_t __write(struct file *f, const void *buf, size_t count)
 {
+       u64 size = (u64)f->f_size;
+       u64 pos = (u64)f->f_pos;
        struct fs_driver *fsdrv;
+       u64 end;
        int ret;
 
        fsdrv = f->fsdev->driver;
@@ -495,18 +498,35 @@ static ssize_t __write(struct file *f, const void *buf, 
size_t count)
        if (fsdrv != ramfs_driver)
                assert_command_context();
 
-       if (f->f_size != FILE_SIZE_STREAM && f->f_pos + count > f->f_size) {
-               ret = fsdev_truncate(f, f->f_pos + count);
-               if (ret) {
-                       if (ret == -EPERM)
-                               ret = -ENOSPC;
-                       if (ret != -ENOSPC)
+       if (f->f_size != FILE_SIZE_STREAM) {
+               if (f->f_size < 0) {
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               /* Writing past the end of the file requires growing it first */
+               end = pos + count;
+               if (end > size) {
+                       /* New file size must be representable as loff_t */
+                       if (end > (u64)MAX_LFS_FILESIZE ||
+                           (f->f_pos >= 0 && end < pos)) {
+                               ret = -EFBIG;
                                goto out;
-                       count = f->f_size - f->f_pos;
-                       if (!count)
-                               goto out;
-               } else {
-                       f->f_size = f->f_pos + count;
+                       }
+
+                       ret = fsdev_truncate(f, end);
+                       if (ret) {
+                               if (ret == -EPERM)
+                                       ret = -ENOSPC;
+                               if (ret != -ENOSPC)
+                                       goto out;
+                               /* Truncate failed; write what fits into the 
file */
+                               count = pos < size ? size - pos : 0;
+                               if (!count)
+                                       goto out;
+                       } else {
+                               f->f_size = end;
+                       }
                }
        }
 

-- 
2.47.3


Reply via email to