Skip to content

Commit b1817b1

Browse files
author
Trond Myklebust
committed
NFS: Protect against 'eof page pollution'
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 <[email protected]>
1 parent 5a46d23 commit b1817b1

File tree

5 files changed

+54
-5
lines changed

5 files changed

+54
-5
lines changed

fs/nfs/file.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <linux/mm.h>
2929
#include <linux/pagemap.h>
3030
#include <linux/gfp.h>
31+
#include <linux/rmap.h>
3132
#include <linux/swap.h>
3233
#include <linux/compaction.h>
3334

@@ -280,6 +281,37 @@ nfs_file_fsync(struct file *file, loff_t start, loff_t end, int datasync)
280281
}
281282
EXPORT_SYMBOL_GPL(nfs_file_fsync);
282283

284+
void nfs_truncate_last_folio(struct address_space *mapping, loff_t from,
285+
loff_t to)
286+
{
287+
struct folio *folio;
288+
289+
if (from >= to)
290+
return;
291+
292+
folio = filemap_lock_folio(mapping, from >> PAGE_SHIFT);
293+
if (IS_ERR(folio))
294+
return;
295+
296+
if (folio_mkclean(folio))
297+
folio_mark_dirty(folio);
298+
299+
if (folio_test_uptodate(folio)) {
300+
loff_t fpos = folio_pos(folio);
301+
size_t offset = from - fpos;
302+
size_t end = folio_size(folio);
303+
304+
if (to - fpos < end)
305+
end = to - fpos;
306+
folio_zero_segment(folio, offset, end);
307+
trace_nfs_size_truncate_folio(mapping->host, to);
308+
}
309+
310+
folio_unlock(folio);
311+
folio_put(folio);
312+
}
313+
EXPORT_SYMBOL_GPL(nfs_truncate_last_folio);
314+
283315
/*
284316
* Decide whether a read/modify/write cycle may be more efficient
285317
* 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,
356388

357389
dfprintk(PAGECACHE, "NFS: write_begin(%pD2(%lu), %u@%lld)\n",
358390
file, mapping->host->i_ino, len, (long long) pos);
391+
nfs_truncate_last_folio(mapping, i_size_read(mapping->host), pos);
359392

360393
fgp |= fgf_set_order(len);
361394
start:

fs/nfs/inode.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,7 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
716716
{
717717
struct inode *inode = d_inode(dentry);
718718
struct nfs_fattr *fattr;
719+
loff_t oldsize = i_size_read(inode);
719720
int error = 0;
720721

721722
nfs_inc_stats(inode, NFSIOS_VFSSETATTR);
@@ -731,7 +732,7 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
731732
if (error)
732733
return error;
733734

734-
if (attr->ia_size == i_size_read(inode))
735+
if (attr->ia_size == oldsize)
735736
attr->ia_valid &= ~ATTR_SIZE;
736737
}
737738

@@ -777,8 +778,12 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
777778
}
778779

779780
error = NFS_PROTO(inode)->setattr(dentry, fattr, attr);
780-
if (error == 0)
781+
if (error == 0) {
782+
if (attr->ia_valid & ATTR_SIZE)
783+
nfs_truncate_last_folio(inode->i_mapping, oldsize,
784+
attr->ia_size);
781785
error = nfs_refresh_inode(inode, fattr);
786+
}
782787
nfs_free_fattr(fattr);
783788
out:
784789
trace_nfs_setattr_exit(inode, error);

fs/nfs/internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ int nfs_file_release(struct inode *, struct file *);
437437
int nfs_lock(struct file *, int, struct file_lock *);
438438
int nfs_flock(struct file *, int, struct file_lock *);
439439
int nfs_check_flags(int);
440+
void nfs_truncate_last_folio(struct address_space *mapping, loff_t from,
441+
loff_t to);
440442

441443
/* inode.c */
442444
extern struct workqueue_struct *nfsiod_workqueue;

fs/nfs/nfs42proc.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ int nfs42_proc_allocate(struct file *filep, loff_t offset, loff_t len)
137137
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ALLOCATE],
138138
};
139139
struct inode *inode = file_inode(filep);
140+
loff_t oldsize = i_size_read(inode);
140141
int err;
141142

142143
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)
145146
inode_lock(inode);
146147

147148
err = nfs42_proc_fallocate(&msg, filep, offset, len);
148-
if (err == -EOPNOTSUPP)
149+
150+
if (err == 0)
151+
nfs_truncate_last_folio(inode->i_mapping, oldsize,
152+
offset + len);
153+
else if (err == -EOPNOTSUPP)
149154
NFS_SERVER(inode)->caps &= ~(NFS_CAP_ALLOCATE |
150155
NFS_CAP_ZERO_RANGE);
151156

@@ -183,6 +188,7 @@ int nfs42_proc_zero_range(struct file *filep, loff_t offset, loff_t len)
183188
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ZERO_RANGE],
184189
};
185190
struct inode *inode = file_inode(filep);
191+
loff_t oldsize = i_size_read(inode);
186192
int err;
187193

188194
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)
191197
inode_lock(inode);
192198

193199
err = nfs42_proc_fallocate(&msg, filep, offset, len);
194-
if (err == 0)
200+
if (err == 0) {
201+
nfs_truncate_last_folio(inode->i_mapping, oldsize,
202+
offset + len);
195203
truncate_pagecache_range(inode, offset, (offset + len) -1);
196-
if (err == -EOPNOTSUPP)
204+
} else if (err == -EOPNOTSUPP)
197205
NFS_SERVER(inode)->caps &= ~NFS_CAP_ZERO_RANGE;
198206

199207
inode_unlock(inode);

fs/nfs/nfstrace.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ DECLARE_EVENT_CLASS(nfs_update_size_class,
272272
TP_ARGS(inode, new_size))
273273

274274
DEFINE_NFS_UPDATE_SIZE_EVENT(truncate);
275+
DEFINE_NFS_UPDATE_SIZE_EVENT(truncate_folio);
275276
DEFINE_NFS_UPDATE_SIZE_EVENT(wcc);
276277
DEFINE_NFS_UPDATE_SIZE_EVENT(update);
277278
DEFINE_NFS_UPDATE_SIZE_EVENT(grow);

0 commit comments

Comments
 (0)