diff mbox series

[SRU,focal:azure,1/1] cifs: prevent updating file size from server if we have a read/write lease

Message ID 20240927193051.1552498-3-john.cabaj@canonical.com
State New
Headers show
Series Missing fix in linux kernel cifs/smb client module | expand

Commit Message

John Cabaj Sept. 27, 2024, 7:30 p.m. UTC
From: Bharath SM <bharathsm@microsoft.com>

BugLink: https://bugs.launchpad.net/bugs/2071744

In cases of large directories, the readdir operation may span multiple
round trips to retrieve contents. This introduces a potential race
condition in case of concurrent write and readdir operations. If the
readdir operation initiates before a write has been processed by the
server, it may update the file size attribute to an older value.
Address this issue by avoiding file size updates from readdir when we
have read/write lease.

Scenario:
1) process1: open dir xyz
2) process1: readdir instance 1 on xyz
3) process2: create file.txt for write
4) process2: write x bytes to file.txt
5) process2: close file.txt
6) process2: open file.txt for read
7) process1: readdir 2 - overwrites file.txt inode size to 0
8) process2: read contents of file.txt - bug, short read with 0 bytes

Cc: stable@vger.kernel.org
Reviewed-by: Shyam Prasad N <sprasad@microsoft.com>
Signed-off-by: Bharath SM <bharathsm@microsoft.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
(backported from commit e4b61f3b1c67f5068590965f64ea6e8d5d5bd961)
[john-cabaj: context changes between the 6.9 release and 5.4 kernel]
Signed-off-by: John Cabaj <john.cabaj@canonical.com>
---
 fs/cifs/cifsproto.h |  6 ++++--
 fs/cifs/file.c      |  8 +++++---
 fs/cifs/inode.c     | 16 +++++++++-------
 fs/cifs/readdir.c   |  2 +-
 4 files changed, 19 insertions(+), 13 deletions(-)
diff mbox series

Patch

diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index 2dde83a96968..60170d8bab18 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -130,7 +130,8 @@  extern int cifs_reconnect(struct TCP_Server_Info *server);
 extern int checkSMB(char *buf, unsigned int len, struct TCP_Server_Info *srvr);
 extern bool is_valid_oplock_break(char *, struct TCP_Server_Info *);
 extern bool backup_cred(struct cifs_sb_info *);
-extern bool is_size_safe_to_change(struct cifsInodeInfo *, __u64 eof);
+extern bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 eof,
+				   bool from_readdir);
 extern void cifs_update_eof(struct cifsInodeInfo *cifsi, loff_t offset,
 			    unsigned int bytes_written);
 extern struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *, int);
@@ -186,7 +187,8 @@  extern void cifs_unix_basic_to_fattr(struct cifs_fattr *fattr,
 				     struct cifs_sb_info *cifs_sb);
 extern void cifs_dir_info_to_fattr(struct cifs_fattr *, FILE_DIRECTORY_INFO *,
 					struct cifs_sb_info *);
-extern void cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr);
+extern void cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr,
+			       bool from_readdir);
 extern struct inode *cifs_iget(struct super_block *sb,
 			       struct cifs_fattr *fattr);
 
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 9b5928696f33..5d9191c97151 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
@@ -165,7 +165,7 @@  int cifs_posix_open(char *full_path, struct inode **pinode,
 		}
 	} else {
 		cifs_revalidate_mapping(*pinode);
-		cifs_fattr_to_inode(*pinode, &fattr);
+		cifs_fattr_to_inode(*pinode, &fattr, false);
 	}
 
 posix_open_ret:
@@ -4562,12 +4562,14 @@  static int is_inode_writable(struct cifsInodeInfo *cifs_inode)
    refreshing the inode only on increases in the file size
    but this is tricky to do without racing with writebehind
    page caching in the current Linux kernel design */
-bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file)
+bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file,
+			    bool from_readdir)
 {
 	if (!cifsInode)
 		return true;
 
-	if (is_inode_writable(cifsInode)) {
+	if (is_inode_writable(cifsInode) ||
+		((cifsInode->oplock & CIFS_CACHE_RW_FLG) != 0 && from_readdir)) {
 		/* This inode is open for write at least once */
 		struct cifs_sb_info *cifs_sb;
 
diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
index af0980c720c7..85e9cdc2bf97 100644
--- a/fs/cifs/inode.c
+++ b/fs/cifs/inode.c
@@ -154,7 +154,8 @@  cifs_nlink_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr)
 
 /* populate an inode with info from a cifs_fattr struct */
 void
-cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr)
+cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr,
+		    bool from_readdir)
 {
 	struct cifsInodeInfo *cifs_i = CIFS_I(inode);
 	struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
@@ -196,7 +197,7 @@  cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr)
 	 * Can't safely change the file size here if the client is writing to
 	 * it due to potential races.
 	 */
-	if (is_size_safe_to_change(cifs_i, fattr->cf_eof)) {
+	if (is_size_safe_to_change(cifs_i, fattr->cf_eof, from_readdir)) {
 		i_size_write(inode, fattr->cf_eof);
 
 		/*
@@ -357,7 +358,7 @@  cifs_get_file_info_unix(struct file *filp)
 		rc = 0;
 	}
 
-	cifs_fattr_to_inode(inode, &fattr);
+	cifs_fattr_to_inode(inode, &fattr, false);
 	free_xid(xid);
 	return rc;
 }
@@ -427,7 +428,7 @@  int cifs_get_inode_info_unix(struct inode **pinode,
 			goto cgiiu_exit;
 		}
 
-		cifs_fattr_to_inode(*pinode, &fattr);
+		cifs_fattr_to_inode(*pinode, &fattr, false);
 	}
 
 cgiiu_exit:
@@ -707,7 +708,7 @@  cifs_get_file_info(struct file *filp)
 	 */
 	fattr.cf_uniqueid = CIFS_I(inode)->uniqueid;
 	fattr.cf_flags |= CIFS_FATTR_NEED_REVAL;
-	cifs_fattr_to_inode(inode, &fattr);
+	cifs_fattr_to_inode(inode, &fattr, false);
 cgfi_exit:
 	free_xid(xid);
 	return rc;
@@ -950,7 +951,7 @@  cifs_get_inode_info(struct inode **inode, const char *full_path,
 			goto cgii_exit;
 		}
 
-		cifs_fattr_to_inode(*inode, &fattr);
+		cifs_fattr_to_inode(*inode, &fattr, false);
 	}
 
 cgii_exit:
@@ -1048,7 +1049,8 @@  cifs_iget(struct super_block *sb, struct cifs_fattr *fattr)
 			}
 		}
 
-		cifs_fattr_to_inode(inode, fattr);
+		/* can't fail - see cifs_find_inode() */
+		cifs_fattr_to_inode(inode, fattr, false);
 		if (sb->s_flags & SB_NOATIME)
 			inode->i_flags |= S_NOATIME | S_NOCMTIME;
 		if (inode->i_state & I_NEW) {
diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
index 3925a7bfc74d..4ff87e1ead2a 100644
--- a/fs/cifs/readdir.c
+++ b/fs/cifs/readdir.c
@@ -119,7 +119,7 @@  cifs_prime_dcache(struct dentry *parent, struct qstr *name,
 			if (CIFS_I(inode)->uniqueid == fattr->cf_uniqueid &&
 			    (inode->i_mode & S_IFMT) ==
 			    (fattr->cf_mode & S_IFMT)) {
-				cifs_fattr_to_inode(inode, fattr);
+				cifs_fattr_to_inode(inode, fattr, true);
 				dput(dentry);
 				return;
 			}