mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01: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;
|
struct cifs_tcon *tcon;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
rc = filemap_write_and_wait(inode->i_mapping);
|
rc = inode_lock_killable(inode);
|
||||||
if (is_interrupt_error(rc))
|
if (rc)
|
||||||
return -ERESTARTSYS;
|
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);
|
mapping_set_error(inode->i_mapping, rc);
|
||||||
|
|
||||||
cfile = find_writable_file(cinode, FIND_FSUID_ONLY);
|
cfile = find_writable_file(cinode, FIND_FSUID_ONLY);
|
||||||
rc = cifs_file_flush(xid, inode, cfile);
|
rc = cifs_file_flush(xid, inode, cfile);
|
||||||
if (!rc) {
|
if (!rc) {
|
||||||
if (cfile) {
|
if (cfile) {
|
||||||
|
struct netfs_inode *ictx = netfs_inode(inode);
|
||||||
|
|
||||||
tcon = tlink_tcon(cfile->tlink);
|
tcon = tlink_tcon(cfile->tlink);
|
||||||
server = tcon->ses->server;
|
server = tcon->ses->server;
|
||||||
|
netfs_wb_begin(ictx, false);
|
||||||
rc = server->ops->set_file_size(xid, tcon,
|
rc = server->ops->set_file_size(xid, tcon,
|
||||||
cfile, 0, false);
|
cfile, 0, false);
|
||||||
if (!rc) {
|
if (!rc) {
|
||||||
inode_lock(inode);
|
|
||||||
filemap_invalidate_lock(inode->i_mapping);
|
|
||||||
netfs_resize_file(&cinode->netfs, 0, true);
|
netfs_resize_file(&cinode->netfs, 0, true);
|
||||||
cifs_setsize(inode, 0);
|
cifs_setsize(inode, 0);
|
||||||
filemap_invalidate_unlock(inode->i_mapping);
|
|
||||||
inode_unlock(inode);
|
|
||||||
cifs_invalidate_cache(inode, 0);
|
cifs_invalidate_cache(inode, 0);
|
||||||
}
|
}
|
||||||
|
netfs_wb_end(ictx);
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* No cached handle; evict stale pages so they can't
|
* No cached handle; evict stale pages so they can't
|
||||||
* be served after the file is later extended; let
|
* be served after the file is later extended; let
|
||||||
* the server's O_TRUNC open response set the i_size
|
* 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);
|
truncate_inode_pages(inode->i_mapping, 0);
|
||||||
filemap_invalidate_unlock(inode->i_mapping);
|
|
||||||
inode_unlock(inode);
|
|
||||||
cifs_invalidate_cache(inode, 0);
|
cifs_invalidate_cache(inode, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
filemap_invalidate_unlock(inode->i_mapping);
|
||||||
|
inode_unlock(inode);
|
||||||
if (cfile)
|
if (cfile)
|
||||||
cifsFileInfo_put(cfile);
|
cifsFileInfo_put(cfile);
|
||||||
return rc;
|
return rc;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user