]> www.infradead.org Git - users/hch/misc.git/commitdiff
NFS: Protect against 'eof page pollution'
authorTrond Myklebust <trond.myklebust@hammerspace.com>
Thu, 4 Sep 2025 22:46:16 +0000 (18:46 -0400)
committerTrond Myklebust <trond.myklebust@hammerspace.com>
Sat, 6 Sep 2025 20:51:25 +0000 (16:51 -0400)
This commit fixes the failing xfstest 'generic/363'.

When the user mmaps() an area that extends beyond the end of file, and
proceeds to write data into the folio that straddles that eof, we're
required to discard that folio data if the user calls some function that
extends the file length.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
fs/nfs/file.c
fs/nfs/inode.c
fs/nfs/internal.h
fs/nfs/nfs42proc.c
fs/nfs/nfstrace.h

index 86e36c630f09eac84f42bbee18dcace415d680ca..a3105f944a0ee3ed9a31a99ec1b7fd66f7e6eec4 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/mm.h>
 #include <linux/pagemap.h>
 #include <linux/gfp.h>
+#include <linux/rmap.h>
 #include <linux/swap.h>
 #include <linux/compaction.h>
 
@@ -280,6 +281,37 @@ nfs_file_fsync(struct file *file, loff_t start, loff_t end, int datasync)
 }
 EXPORT_SYMBOL_GPL(nfs_file_fsync);
 
+void nfs_truncate_last_folio(struct address_space *mapping, loff_t from,
+                            loff_t to)
+{
+       struct folio *folio;
+
+       if (from >= to)
+               return;
+
+       folio = filemap_lock_folio(mapping, from >> PAGE_SHIFT);
+       if (IS_ERR(folio))
+               return;
+
+       if (folio_mkclean(folio))
+               folio_mark_dirty(folio);
+
+       if (folio_test_uptodate(folio)) {
+               loff_t fpos = folio_pos(folio);
+               size_t offset = from - fpos;
+               size_t end = folio_size(folio);
+
+               if (to - fpos < end)
+                       end = to - fpos;
+               folio_zero_segment(folio, offset, end);
+               trace_nfs_size_truncate_folio(mapping->host, to);
+       }
+
+       folio_unlock(folio);
+       folio_put(folio);
+}
+EXPORT_SYMBOL_GPL(nfs_truncate_last_folio);
+
 /*
  * Decide whether a read/modify/write cycle may be more efficient
  * then a modify/write/read cycle when writing to a page in the
@@ -356,6 +388,7 @@ static int nfs_write_begin(const struct kiocb *iocb,
 
        dfprintk(PAGECACHE, "NFS: write_begin(%pD2(%lu), %u@%lld)\n",
                file, mapping->host->i_ino, len, (long long) pos);
+       nfs_truncate_last_folio(mapping, i_size_read(mapping->host), pos);
 
        fgp |= fgf_set_order(len);
 start:
index 338ef77ae4230812b198dcce47ffeab5f5b2b40e..0b141feacc525be9c4ce49b1464e75f8b248a40c 100644 (file)
@@ -716,6 +716,7 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 {
        struct inode *inode = d_inode(dentry);
        struct nfs_fattr *fattr;
+       loff_t oldsize = i_size_read(inode);
        int error = 0;
 
        nfs_inc_stats(inode, NFSIOS_VFSSETATTR);
@@ -731,7 +732,7 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
                if (error)
                        return error;
 
-               if (attr->ia_size == i_size_read(inode))
+               if (attr->ia_size == oldsize)
                        attr->ia_valid &= ~ATTR_SIZE;
        }
 
@@ -777,8 +778,12 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
        }
 
        error = NFS_PROTO(inode)->setattr(dentry, fattr, attr);
-       if (error == 0)
+       if (error == 0) {
+               if (attr->ia_valid & ATTR_SIZE)
+                       nfs_truncate_last_folio(inode->i_mapping, oldsize,
+                                               attr->ia_size);
                error = nfs_refresh_inode(inode, fattr);
+       }
        nfs_free_fattr(fattr);
 out:
        trace_nfs_setattr_exit(inode, error);
index 74d712b584238d9b3c02232d6f8aaf9d5d714f4a..1433ae13dba045877d3722f5c576fa44d3285742 100644 (file)
@@ -437,6 +437,8 @@ int nfs_file_release(struct inode *, struct file *);
 int nfs_lock(struct file *, int, struct file_lock *);
 int nfs_flock(struct file *, int, struct file_lock *);
 int nfs_check_flags(int);
+void nfs_truncate_last_folio(struct address_space *mapping, loff_t from,
+                            loff_t to);
 
 /* inode.c */
 extern struct workqueue_struct *nfsiod_workqueue;
index 01c01f45358b7cf6b29455b447a9f059049ca68b..4420b8740e2ff70a1df6fc802d5ffe0041bada02 100644 (file)
@@ -137,6 +137,7 @@ int nfs42_proc_allocate(struct file *filep, loff_t offset, loff_t len)
                .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ALLOCATE],
        };
        struct inode *inode = file_inode(filep);
+       loff_t oldsize = i_size_read(inode);
        int err;
 
        if (!nfs_server_capable(inode, NFS_CAP_ALLOCATE))
@@ -145,7 +146,11 @@ int nfs42_proc_allocate(struct file *filep, loff_t offset, loff_t len)
        inode_lock(inode);
 
        err = nfs42_proc_fallocate(&msg, filep, offset, len);
-       if (err == -EOPNOTSUPP)
+
+       if (err == 0)
+               nfs_truncate_last_folio(inode->i_mapping, oldsize,
+                                       offset + len);
+       else if (err == -EOPNOTSUPP)
                NFS_SERVER(inode)->caps &= ~(NFS_CAP_ALLOCATE |
                                             NFS_CAP_ZERO_RANGE);
 
@@ -183,6 +188,7 @@ int nfs42_proc_zero_range(struct file *filep, loff_t offset, loff_t len)
                .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ZERO_RANGE],
        };
        struct inode *inode = file_inode(filep);
+       loff_t oldsize = i_size_read(inode);
        int err;
 
        if (!nfs_server_capable(inode, NFS_CAP_ZERO_RANGE))
@@ -191,9 +197,11 @@ int nfs42_proc_zero_range(struct file *filep, loff_t offset, loff_t len)
        inode_lock(inode);
 
        err = nfs42_proc_fallocate(&msg, filep, offset, len);
-       if (err == 0)
+       if (err == 0) {
+               nfs_truncate_last_folio(inode->i_mapping, oldsize,
+                                       offset + len);
                truncate_pagecache_range(inode, offset, (offset + len) -1);
-       if (err == -EOPNOTSUPP)
+       } else if (err == -EOPNOTSUPP)
                NFS_SERVER(inode)->caps &= ~NFS_CAP_ZERO_RANGE;
 
        inode_unlock(inode);
index 96b1323318c2f54251dfd104183c47929f08c801..627115179795fc4eb8e9807bbaca0232ca4a3b36 100644 (file)
@@ -272,6 +272,7 @@ DECLARE_EVENT_CLASS(nfs_update_size_class,
                        TP_ARGS(inode, new_size))
 
 DEFINE_NFS_UPDATE_SIZE_EVENT(truncate);
+DEFINE_NFS_UPDATE_SIZE_EVENT(truncate_folio);
 DEFINE_NFS_UPDATE_SIZE_EVENT(wcc);
 DEFINE_NFS_UPDATE_SIZE_EVENT(update);
 DEFINE_NFS_UPDATE_SIZE_EVENT(grow);