Message ID | CAH2r5mtVOKN-8ET1oYC0L+ihAWpmwFY3=Q=-KP+qwcaAb002_A@mail.gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [SMB3] fix temporary data corruption in collapse range | expand |
Steve French <smfrench@gmail.com> wrote: > + truncate_pagecache_range(inode, off, old_eof); Upon further consideration, I think this should perhaps be before: > rc = smb2_copychunk_range(xid, cfile, cfile, off + len, so that any outstanding shared-writable mmap can be made to wait. Also the invalidation in smb3_zero_range() only covers the hole, so smb3_insert_range() also needs to invalidate from the bottom of the hole to the EOF - and, again, I think it needs to do this before making changes to the file contents. David
If it is moved earlier it will have the effect of throwing away these pages even if the copychunk fails (and thus if the collapse range fails we end up still discarding). Any thoughts? On Sat, Aug 20, 2022 at 10:23 AM David Howells <dhowells@redhat.com> wrote: > > Steve French <smfrench@gmail.com> wrote: > > > + truncate_pagecache_range(inode, off, old_eof); > > Upon further consideration, I think this should perhaps be before: > > > rc = smb2_copychunk_range(xid, cfile, cfile, off + len, > > so that any outstanding shared-writable mmap can be made to wait. > > Also the invalidation in smb3_zero_range() only covers the hole, so > smb3_insert_range() also needs to invalidate from the bottom of the hole to > the EOF - and, again, I think it needs to do this before making changes to the > file contents. > > David >
Hi Steve, I think we need something like the attached. I haven't tested it yet as I have to go out for a bit. I added inode locking and filemap-invalidation locking and put the invalidations before the ops to take care of mmap. David --- diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index f406af596887..5b267d4515d3 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -3316,22 +3316,39 @@ get_smb2_acl(struct cifs_sb_info *cifs_sb, return pntsd; } +static long smb3_erase_range(struct file *file, struct cifs_tcon *tcon, + loff_t offset, loff_t len, unsigned int xid) +{ + struct cifsFileInfo *cfile = file->private_data; + struct file_zero_data_information fsctl_buf; + + cifs_dbg(FYI, "Offset %lld len %lld\n", offset, len); + + fsctl_buf.FileOffset = cpu_to_le64(offset); + fsctl_buf.BeyondFinalZero = cpu_to_le64(offset + len); + + return SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid, + cfile->fid.volatile_fid, FSCTL_SET_ZERO_DATA, true, + (char *)&fsctl_buf, + sizeof(struct file_zero_data_information), + 0, NULL, NULL); +} + static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, loff_t offset, loff_t len, bool keep_size) { struct cifs_ses *ses = tcon->ses; - struct inode *inode; - struct cifsInodeInfo *cifsi; + struct inode *inode = file_inode(file); + struct cifsInodeInfo *cifsi = CIFS_I(inode); struct cifsFileInfo *cfile = file->private_data; - struct file_zero_data_information fsctl_buf; long rc; unsigned int xid; __le64 eof; - xid = get_xid(); + inode_lock(inode); + filemap_invalidate_lock(inode->i_mapping); - inode = d_inode(cfile->dentry); - cifsi = CIFS_I(inode); + xid = get_xid(); trace_smb3_zero_enter(xid, cfile->fid.persistent_fid, tcon->tid, ses->Suid, offset, len); @@ -3343,26 +3360,12 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, truncate_pagecache_range(inode, offset, offset + len - 1); /* if file not oplocked can't be sure whether asking to extend size */ - if (!CIFS_CACHE_READ(cifsi)) - if (keep_size == false) { - rc = -EOPNOTSUPP; - trace_smb3_zero_err(xid, cfile->fid.persistent_fid, - tcon->tid, ses->Suid, offset, len, rc); - free_xid(xid); - return rc; - } - - cifs_dbg(FYI, "Offset %lld len %lld\n", offset, len); - - fsctl_buf.FileOffset = cpu_to_le64(offset); - fsctl_buf.BeyondFinalZero = cpu_to_le64(offset + len); + rc = -EOPNOTSUPP; + if (keep_size == false && !CIFS_CACHE_READ(cifsi)) + goto zero_range_exit; - rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid, - cfile->fid.volatile_fid, FSCTL_SET_ZERO_DATA, true, - (char *)&fsctl_buf, - sizeof(struct file_zero_data_information), - 0, NULL, NULL); - if (rc) + rc = smb3_erase_range(file, tcon, offset, len, xid); + if (rc < 0) goto zero_range_exit; /* @@ -3375,6 +3378,8 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, } zero_range_exit: + filemap_invalidate_unlock(inode->i_mapping); + inode_unlock(inode); free_xid(xid); if (rc) trace_smb3_zero_err(xid, cfile->fid.persistent_fid, tcon->tid, @@ -3388,23 +3393,22 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon, loff_t offset, loff_t len) { - struct inode *inode; + struct inode *inode = file_inode(file); struct cifsFileInfo *cfile = file->private_data; struct file_zero_data_information fsctl_buf; long rc; unsigned int xid; __u8 set_sparse = 1; - xid = get_xid(); + inode_lock(inode); - inode = d_inode(cfile->dentry); + xid = get_xid(); /* Need to make file sparse, if not already, before freeing range. */ /* Consider adding equivalent for compressed since it could also work */ if (!smb2_set_sparse(xid, tcon, cfile, inode, set_sparse)) { rc = -EOPNOTSUPP; - free_xid(xid); - return rc; + goto out; } filemap_invalidate_lock(inode->i_mapping); @@ -3424,8 +3428,10 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon, true /* is_fctl */, (char *)&fsctl_buf, sizeof(struct file_zero_data_information), CIFSMaxBufSize, NULL, NULL); - free_xid(xid); filemap_invalidate_unlock(inode->i_mapping); +out: + inode_unlock(inode); + free_xid(xid); return rc; } @@ -3682,28 +3688,31 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, { int rc; unsigned int xid; - struct inode *inode; + struct inode *inode = file_inode(file); struct cifsFileInfo *cfile = file->private_data; - struct cifsInodeInfo *cifsi; + struct cifsInodeInfo *cifsi = CIFS_I(inode); __le64 eof; + loff_t old_eof; xid = get_xid(); - inode = d_inode(cfile->dentry); - cifsi = CIFS_I(inode); + inode_lock(inode); - if (off >= i_size_read(inode) || - off + len >= i_size_read(inode)) { + old_eof = i_size_read(inode); + if ((off >= old_eof) || + off + len >= old_eof) { rc = -EINVAL; goto out; } + truncate_pagecache_range(inode, off, old_eof); + rc = smb2_copychunk_range(xid, cfile, cfile, off + len, - i_size_read(inode) - off - len, off); + old_eof - off - len, off); if (rc < 0) goto out; - eof = cpu_to_le64(i_size_read(inode) - len); + eof = cpu_to_le64(old_eof - len); rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, &eof); if (rc < 0) @@ -3715,6 +3724,7 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, truncate_setsize(inode, cifsi->server_eof); fscache_resize_cookie(cifs_inode_cookie(inode), cifsi->server_eof); out: + inode_unlock(inode); free_xid(xid); return rc; } @@ -3725,18 +3735,24 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, int rc; unsigned int xid; struct cifsFileInfo *cfile = file->private_data; + struct inode *inode = file_inode(file); __le64 eof; - __u64 count; + __u64 count, old_eof; + + inode_lock(inode); xid = get_xid(); - if (off >= i_size_read(file->f_inode)) { + old_eof = i_size_read(inode); + if (off >= old_eof) { rc = -EINVAL; goto out; } - count = i_size_read(file->f_inode) - off; - eof = cpu_to_le64(i_size_read(file->f_inode) + len); + count = old_eof - off; + eof = cpu_to_le64(old_eof + len); + + truncate_pagecache_range(inode, off, old_eof); rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, &eof); @@ -3747,12 +3763,13 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, if (rc < 0) goto out; - rc = smb3_zero_range(file, tcon, off, len, 1); + rc = smb3_erase_range(file, tcon, off, len, xid); if (rc < 0) goto out; rc = 0; out: + inode_unlock(inode); free_xid(xid); return rc; }
From bce7aeed5e9263097209cf7cfb0c7ce1a2afcce1 Mon Sep 17 00:00:00 2001 From: Steve French <stfrench@microsoft.com> Date: Fri, 19 Aug 2022 18:57:05 -0500 Subject: [PATCH] smb3: fix collapse range temporary data corruption collapse range doesn't discard the affected cached region so can risk temporarily corrupting the file data. This fixes xfstest generic/031 Cc: stable@vger.kernel.org Fixes: 5476b5dd82c8b ("cifs: add support for FALLOC_FL_COLLAPSE_RANGE") Reported-by: David Howells <dhowells@redhat.com> Tested-by: David Howells <dhowells@redhat.com> Reviewed-by: David Howells <dhowells@redhat.com> Signed-off-by: Steve French <stfrench@microsoft.com> --- fs/cifs/smb2ops.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 96f3b0573606..cd9fa984538a 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -3677,24 +3677,25 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, struct cifsFileInfo *cfile = file->private_data; struct cifsInodeInfo *cifsi; __le64 eof; + loff_t old_eof; xid = get_xid(); inode = d_inode(cfile->dentry); cifsi = CIFS_I(inode); - - if (off >= i_size_read(inode) || - off + len >= i_size_read(inode)) { + old_eof = i_size_read(inode); + if ((off >= old_eof) || + off + len >= old_eof) { rc = -EINVAL; goto out; } rc = smb2_copychunk_range(xid, cfile, cfile, off + len, - i_size_read(inode) - off - len, off); + old_eof - off - len, off); if (rc < 0) goto out; - eof = cpu_to_le64(i_size_read(inode) - len); + eof = cpu_to_le64(old_eof - len); rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, &eof); if (rc < 0) @@ -3702,6 +3703,7 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, rc = 0; + truncate_pagecache_range(inode, off, old_eof); cifsi->server_eof = i_size_read(inode) - len; truncate_setsize(inode, cifsi->server_eof); fscache_resize_cookie(cifs_inode_cookie(inode), cifsi->server_eof); -- 2.34.1