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

smb3 client: fix open hardlink on deferred close file error

The following Python script results in unexpected behaviour when run on
a CIFS filesystem against a Windows Server:

# Create file
fd = os.open('test', os.O_WRONLY|os.O_CREAT)
os.write(fd, b'foo')
os.close(fd)

# Open and close the file to leave a pending deferred close
fd = os.open('test', os.O_RDONLY|os.O_DIRECT)
os.close(fd)

# Try to open the file via a hard link
os.link('test', 'new')
newfd = os.open('new', os.O_RDONLY|os.O_DIRECT)

The final open returns EINVAL due to the server returning
STATUS_INVALID_PARAMETER. The root cause of this is that the client
caches lease keys per inode, but the spec requires them to be related to
the filename which causes problems when hard links are involved:

From MS-SMB2 section 3.3.5.9.11:

"The server MUST attempt to locate a Lease by performing a lookup in the
LeaseTable.LeaseList using the LeaseKey in the
SMB2_CREATE_REQUEST_LEASE_V2 as the lookup key. If a lease is found,
Lease.FileDeleteOnClose is FALSE, and Lease.Filename does not match the
file name for the incoming request, the request MUST be failed with
STATUS_INVALID_PARAMETER"

On client side, we first check the context of file open, if it hits above
conditions, we first close all opening files which are belong to the same
inode, then we do open the hard link file.

Cc: stable@vger.kernel.org
Signed-off-by: Chunjie Zhu <chunjie.zhu@cloud.com>
Signed-off-by: Steve French <stfrench@microsoft.com>

authored by

Chunjie Zhu and committed by
Steve French
262b73ef 8ffd015d

+30
+2
fs/smb/client/cifsproto.h
··· 163 163 extern struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *, bool); 164 164 extern int cifs_get_readable_path(struct cifs_tcon *tcon, const char *name, 165 165 struct cifsFileInfo **ret_file); 166 + extern int cifs_get_hardlink_path(struct cifs_tcon *tcon, struct inode *inode, 167 + struct file *file); 166 168 extern unsigned int smbCalcSize(void *buf); 167 169 extern int decode_negTokenInit(unsigned char *security_blob, int length, 168 170 struct TCP_Server_Info *server);
+28
fs/smb/client/file.c
··· 1007 1007 } else { 1008 1008 _cifsFileInfo_put(cfile, true, false); 1009 1009 } 1010 + } else { 1011 + /* hard link on the defeered close file */ 1012 + rc = cifs_get_hardlink_path(tcon, inode, file); 1013 + if (rc) 1014 + cifs_close_deferred_file(CIFS_I(inode)); 1010 1015 } 1011 1016 1012 1017 if (server->oplocks) ··· 2074 2069 struct list_head *li, *tmp; 2075 2070 list_for_each_safe(li, tmp, source) 2076 2071 list_move(li, dest); 2072 + } 2073 + 2074 + int 2075 + cifs_get_hardlink_path(struct cifs_tcon *tcon, struct inode *inode, 2076 + struct file *file) 2077 + { 2078 + struct cifsFileInfo *open_file = NULL; 2079 + struct cifsInodeInfo *cinode = CIFS_I(inode); 2080 + int rc = 0; 2081 + 2082 + spin_lock(&tcon->open_file_lock); 2083 + spin_lock(&cinode->open_file_lock); 2084 + 2085 + list_for_each_entry(open_file, &cinode->openFileList, flist) { 2086 + if (file->f_flags == open_file->f_flags) { 2087 + rc = -EINVAL; 2088 + break; 2089 + } 2090 + } 2091 + 2092 + spin_unlock(&cinode->open_file_lock); 2093 + spin_unlock(&tcon->open_file_lock); 2094 + return rc; 2077 2095 } 2078 2096 2079 2097 void