Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

smb: client: fix race with fallocate(2) and AIO+DIO

AIO+DIO may extend the file size, hence we need to make sure ->i_size
is stable across the entire fallocate(2) operation, otherwise it would
become a truncate and then inode size reduced back down when it
finishes.

Fix this by calling netfs_wait_for_outstanding_io() right after
acquiring ->i_rwsem exclusively in cifs_fallocate() and then guarantee
a stable ->i_size across fallocate(2).

Also call netfs_wait_for_outstanding_io() after truncating pagecache
to avoid any potential races with writeback.

Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
Reviewed-by: David Howells <dhowells@redhat.com>
Fixes: 210627b0aca9 ("smb: client: fix missing timestamp updates with O_TRUNC")
Cc: Frank Sorenson <sorenson@redhat.com>
Cc: linux-cifs@vger.kernel.org
Signed-off-by: Steve French <stfrench@microsoft.com>

authored by

Paulo Alcantara and committed by
Steve French
dba9f997 b95cd1bd

+26 -15
+19 -3
fs/smb/client/cifsfs.c
··· 392 392 struct cifs_sb_info *cifs_sb = CIFS_FILE_SB(file); 393 393 struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); 394 394 struct TCP_Server_Info *server = tcon->ses->server; 395 + struct inode *inode = file_inode(file); 396 + int rc; 395 397 396 - if (server->ops->fallocate) 397 - return server->ops->fallocate(file, tcon, mode, off, len); 398 + if (!server->ops->fallocate) 399 + return -EOPNOTSUPP; 398 400 399 - return -EOPNOTSUPP; 401 + rc = inode_lock_killable(inode); 402 + if (rc) 403 + return rc; 404 + 405 + netfs_wait_for_outstanding_io(inode); 406 + 407 + rc = file_modified(file); 408 + if (rc) 409 + goto out_unlock; 410 + 411 + rc = server->ops->fallocate(file, tcon, mode, off, len); 412 + 413 + out_unlock: 414 + inode_unlock(inode); 415 + return rc; 400 416 } 401 417 402 418 static int cifs_permission(struct mnt_idmap *idmap,
+1
fs/smb/client/inode.c
··· 3012 3012 spin_unlock(&inode->i_lock); 3013 3013 inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); 3014 3014 truncate_pagecache(inode, offset); 3015 + netfs_wait_for_outstanding_io(inode); 3015 3016 } 3016 3017 3017 3018 int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
+6 -12
fs/smb/client/smb2ops.c
··· 3367 3367 trace_smb3_zero_enter(xid, cfile->fid.persistent_fid, tcon->tid, 3368 3368 ses->Suid, offset, len); 3369 3369 3370 - inode_lock(inode); 3371 3370 filemap_invalidate_lock(inode->i_mapping); 3372 3371 3373 3372 i_size = i_size_read(inode); ··· 3384 3385 * first, otherwise the data may be inconsistent with the server. 3385 3386 */ 3386 3387 truncate_pagecache_range(inode, offset, offset + len - 1); 3388 + netfs_wait_for_outstanding_io(inode); 3387 3389 3388 3390 /* if file not oplocked can't be sure whether asking to extend size */ 3389 3391 rc = -EOPNOTSUPP; ··· 3413 3413 3414 3414 zero_range_exit: 3415 3415 filemap_invalidate_unlock(inode->i_mapping); 3416 - inode_unlock(inode); 3417 3416 free_xid(xid); 3418 3417 if (rc) 3419 3418 trace_smb3_zero_err(xid, cfile->fid.persistent_fid, tcon->tid, ··· 3436 3437 3437 3438 xid = get_xid(); 3438 3439 3439 - inode_lock(inode); 3440 3440 /* Need to make file sparse, if not already, before freeing range. */ 3441 3441 /* Consider adding equivalent for compressed since it could also work */ 3442 3442 if (!smb2_set_sparse(xid, tcon, cfile, inode, set_sparse)) { ··· 3449 3451 * caches first, otherwise the data may be inconsistent with the server. 3450 3452 */ 3451 3453 truncate_pagecache_range(inode, offset, offset + len - 1); 3454 + netfs_wait_for_outstanding_io(inode); 3452 3455 3453 3456 cifs_dbg(FYI, "Offset %lld len %lld\n", offset, len); 3454 3457 ··· 3484 3485 unlock: 3485 3486 filemap_invalidate_unlock(inode->i_mapping); 3486 3487 out: 3487 - inode_unlock(inode); 3488 3488 free_xid(xid); 3489 3489 return rc; 3490 3490 } ··· 3747 3749 3748 3750 xid = get_xid(); 3749 3751 3750 - inode_lock(inode); 3751 - 3752 3752 old_eof = i_size_read(inode); 3753 3753 if ((off >= old_eof) || 3754 3754 off + len >= old_eof) { ··· 3761 3765 3762 3766 truncate_pagecache_range(inode, off, old_eof); 3763 3767 ictx->zero_point = old_eof; 3768 + netfs_wait_for_outstanding_io(inode); 3764 3769 3765 3770 rc = smb2_copychunk_range(xid, cfile, cfile, off + len, 3766 3771 old_eof - off - len, off); ··· 3782 3785 fscache_resize_cookie(cifs_inode_cookie(inode), new_eof); 3783 3786 out_2: 3784 3787 filemap_invalidate_unlock(inode->i_mapping); 3785 - out: 3786 - inode_unlock(inode); 3788 + out: 3787 3789 free_xid(xid); 3788 3790 return rc; 3789 3791 } ··· 3799 3803 3800 3804 xid = get_xid(); 3801 3805 3802 - inode_lock(inode); 3803 - 3804 3806 old_eof = i_size_read(inode); 3805 3807 if (off >= old_eof) { 3806 3808 rc = -EINVAL; ··· 3813 3819 if (rc < 0) 3814 3820 goto out_2; 3815 3821 truncate_pagecache_range(inode, off, old_eof); 3822 + netfs_wait_for_outstanding_io(inode); 3816 3823 3817 3824 rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, 3818 3825 cfile->fid.volatile_fid, cfile->pid, new_eof); ··· 3836 3841 rc = 0; 3837 3842 out_2: 3838 3843 filemap_invalidate_unlock(inode->i_mapping); 3839 - out: 3840 - inode_unlock(inode); 3844 + out: 3841 3845 free_xid(xid); 3842 3846 return rc; 3843 3847 }