smb3: missing inode locks in zero range
authorDavid Howells <dhowells@redhat.com>
Tue, 23 Aug 2022 06:01:36 +0000 (01:01 -0500)
committerSteve French <stfrench@microsoft.com>
Tue, 23 Aug 2022 06:09:08 +0000 (01:09 -0500)
smb3 fallocate zero range was not grabbing the inode or filemap_invalidate
locks so could have race with pagemap reinstantiating the page.

Cc: stable@vger.kernel.org
Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/smb2ops.c

index 96f3b05..a6fe542 100644 (file)
@@ -3307,26 +3307,43 @@ get_smb2_acl(struct cifs_sb_info *cifs_sb,
        return pntsd;
 }
 
+static long smb3_zero_data(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,
+                         (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 = d_inode(cfile->dentry);
-       cifsi = CIFS_I(inode);
-
        trace_smb3_zero_enter(xid, cfile->fid.persistent_fid, tcon->tid,
                              ses->Suid, offset, len);
 
+       inode_lock(inode);
+       filemap_invalidate_lock(inode->i_mapping);
+
        /*
         * We zero the range through ioctl, so we need remove the page caches
         * first, otherwise the data may be inconsistent with the server.
@@ -3334,26 +3351,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,
-                       (char *)&fsctl_buf,
-                       sizeof(struct file_zero_data_information),
-                       0, NULL, NULL);
-       if (rc)
+       rc = smb3_zero_data(file, tcon, offset, len, xid);
+       if (rc < 0)
                goto zero_range_exit;
 
        /*
@@ -3366,6 +3369,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,