mirror of
https://github.com/torvalds/linux.git
synced 2026-09-14 08:01:12 +02:00
smb: client: fix data corruption with concurrent writes and O_TRUNC
cifs_do_truncate() flushes dirty pages with filemap_write_and_wait()
and truncates the file on the server, but in the old code both
operations ran without holding i_rwsem or invalidate_lock. A
concurrent buffered write via netfs_perform_write() -- which only
needs i_rwsem shared -- could dirty new pages after the flush but
before the local truncation, and those pages would be silently
discarded by cifs_setsize() -> truncate_pagecache().
Fix by acquiring inode_lock (exclusive i_rwsem) and
filemap_invalidate_lock at the top of cifs_do_truncate(), so the
entire flush-truncate-resize sequence is atomic with respect to:
- buffered writes (blocked by exclusive i_rwsem, since
netfs_start_io_write takes i_rwsem shared),
- read page faults (blocked by exclusive invalidate_lock, since
filemap_fault takes it shared),
- writeback collection (blocked by netfs_wb_begin/netfs_wb_end
around the server truncate and local resize, since
netfs_writepages also acquires the wb lock).
Fixes: 110fee6b9b ("smb: client: fix missing timestamp updates with O_TRUNC")
Signed-off-by: Paulo Alcantara <pc@manguebit.org>
Reviewed-by: Namjae Jeon <linkinjeon@kernel.org>
Cc: Ronnie Sahlberg <ronniesahlberg@gmail.com>
Cc: Shyam Prasad N <sprasad@microsoft.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Bharath SM <bharathsm@microsoft.com>
Cc: stable@vger.kernel.org
This commit is contained in:
parent
fe39cd9d48
commit
a8603b52b3
|
|
@ -999,42 +999,50 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry)
|
|||
struct cifs_tcon *tcon;
|
||||
int rc;
|
||||
|
||||
rc = filemap_write_and_wait(inode->i_mapping);
|
||||
if (is_interrupt_error(rc))
|
||||
rc = inode_lock_killable(inode);
|
||||
if (rc)
|
||||
return -ERESTARTSYS;
|
||||
|
||||
filemap_invalidate_lock(inode->i_mapping);
|
||||
|
||||
rc = filemap_write_and_wait(inode->i_mapping);
|
||||
if (is_interrupt_error(rc)) {
|
||||
rc = -ERESTARTSYS;
|
||||
goto out;
|
||||
}
|
||||
mapping_set_error(inode->i_mapping, rc);
|
||||
|
||||
cfile = find_writable_file(cinode, FIND_FSUID_ONLY);
|
||||
rc = cifs_file_flush(xid, inode, cfile);
|
||||
if (!rc) {
|
||||
if (cfile) {
|
||||
struct netfs_inode *ictx = netfs_inode(inode);
|
||||
|
||||
tcon = tlink_tcon(cfile->tlink);
|
||||
server = tcon->ses->server;
|
||||
netfs_wb_begin(ictx, false);
|
||||
rc = server->ops->set_file_size(xid, tcon,
|
||||
cfile, 0, false);
|
||||
if (!rc) {
|
||||
inode_lock(inode);
|
||||
filemap_invalidate_lock(inode->i_mapping);
|
||||
netfs_resize_file(&cinode->netfs, 0, true);
|
||||
cifs_setsize(inode, 0);
|
||||
filemap_invalidate_unlock(inode->i_mapping);
|
||||
inode_unlock(inode);
|
||||
cifs_invalidate_cache(inode, 0);
|
||||
}
|
||||
netfs_wb_end(ictx);
|
||||
} else {
|
||||
/*
|
||||
* No cached handle; evict stale pages so they can't
|
||||
* be served after the file is later extended; let
|
||||
* the server's O_TRUNC open response set the i_size
|
||||
*/
|
||||
inode_lock(inode);
|
||||
filemap_invalidate_lock(inode->i_mapping);
|
||||
truncate_inode_pages(inode->i_mapping, 0);
|
||||
filemap_invalidate_unlock(inode->i_mapping);
|
||||
inode_unlock(inode);
|
||||
cifs_invalidate_cache(inode, 0);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
filemap_invalidate_unlock(inode->i_mapping);
|
||||
inode_unlock(inode);
|
||||
if (cfile)
|
||||
cifsFileInfo_put(cfile);
|
||||
return rc;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user