From a958fff2429525692c571bb6421b606fb0fef50a Mon Sep 17 00:00:00 2001 From: Steve French Date: Mon, 19 Sep 2016 11:14:06 -0500 Subject: [PATCH 01/21] Add way to query file attributes via cifs xattr Add parsing for new pseudo-xattr user.cifs.dosattrib file attribute so tools can recognize what kind of file it is, and verify if common SMB3 attributes (system, hidden, archive, sparse, indexed etc.) are set. Signed-off-by: Steve French Reviewed-by: Pavel Shilovsky --- fs/cifs/xattr.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/fs/cifs/xattr.c b/fs/cifs/xattr.c index 5e23f64c0804..c4237b8ad9c6 100644 --- a/fs/cifs/xattr.c +++ b/fs/cifs/xattr.c @@ -33,6 +33,7 @@ #define MAX_EA_VALUE_SIZE 65535 #define CIFS_XATTR_CIFS_ACL "system.cifs_acl" +#define CIFS_XATTR_ATTRIB "cifs.dosattrib" /* full name: user.cifs.dosattrib */ /* BB need to add server (Samba e.g) support for security and trusted prefix */ @@ -144,6 +145,31 @@ out: return rc; } +static int cifs_attrib_get(struct dentry *dentry, + struct inode *inode, void *value, + size_t size) +{ + ssize_t rc; + __u32 *pattribute; + + rc = cifs_revalidate_dentry_attr(dentry); + + if (rc) + return rc; + + if ((value == NULL) || (size == 0)) + return sizeof(__u32); + else if (size < sizeof(__u32)) + return -ERANGE; + + /* return dos attributes as pseudo xattr */ + pattribute = (__u32 *)value; + *pattribute = CIFS_I(inode)->cifsAttrs; + + return sizeof(__u32); +} + + static int cifs_xattr_get(const struct xattr_handler *handler, struct dentry *dentry, struct inode *inode, const char *name, void *value, size_t size) @@ -168,10 +194,16 @@ static int cifs_xattr_get(const struct xattr_handler *handler, rc = -ENOMEM; goto out; } - /* return dos attributes as pseudo xattr */ + /* return alt name if available as pseudo attr */ switch (handler->flags) { case XATTR_USER: + cifs_dbg(FYI, "%s:querying user xattr %s\n", __func__, name); + if (strcmp(name, CIFS_XATTR_ATTRIB) == 0) { + rc = cifs_attrib_get(dentry, inode, value, size); + break; + } + if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_XATTR) goto out; From 6609804413ae5b84830e35fdd3a7b7fe4149cf71 Mon Sep 17 00:00:00 2001 From: Steve French Date: Tue, 20 Sep 2016 03:05:57 -0500 Subject: [PATCH 02/21] Add way to query creation time of file via cifs xattr Add parsing for new pseudo-xattr user.cifs.creationtime file attribute to allow backup and test applications to view birth time of file on cifs/smb3 mounts. Signed-off-by: Steve French --- fs/cifs/xattr.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/fs/cifs/xattr.c b/fs/cifs/xattr.c index c4237b8ad9c6..20af5187ba63 100644 --- a/fs/cifs/xattr.c +++ b/fs/cifs/xattr.c @@ -34,7 +34,7 @@ #define MAX_EA_VALUE_SIZE 65535 #define CIFS_XATTR_CIFS_ACL "system.cifs_acl" #define CIFS_XATTR_ATTRIB "cifs.dosattrib" /* full name: user.cifs.dosattrib */ - +#define CIFS_XATTR_CREATETIME "cifs.creationtime" /* user.cifs.creationtime */ /* BB need to add server (Samba e.g) support for security and trusted prefix */ enum { XATTR_USER, XATTR_CIFS_ACL, XATTR_ACL_ACCESS, XATTR_ACL_DEFAULT }; @@ -169,6 +169,29 @@ static int cifs_attrib_get(struct dentry *dentry, return sizeof(__u32); } +static int cifs_creation_time_get(struct dentry *dentry, struct inode *inode, + void *value, size_t size) +{ + ssize_t rc; + __u64 * pcreatetime; + + rc = cifs_revalidate_dentry_attr(dentry); + if (rc) + return rc; + + if ((value == NULL) || (size == 0)) + return sizeof(__u64); + else if (size < sizeof(__u64)) + return -ERANGE; + + /* return dos attributes as pseudo xattr */ + pcreatetime = (__u64 *)value; + *pcreatetime = CIFS_I(inode)->createtime; + return sizeof(__u64); + + return rc; +} + static int cifs_xattr_get(const struct xattr_handler *handler, struct dentry *dentry, struct inode *inode, @@ -202,6 +225,9 @@ static int cifs_xattr_get(const struct xattr_handler *handler, if (strcmp(name, CIFS_XATTR_ATTRIB) == 0) { rc = cifs_attrib_get(dentry, inode, value, size); break; + } else if (strcmp(name, CIFS_XATTR_CREATETIME) == 0) { + rc = cifs_creation_time_get(dentry, inode, value, size); + break; } if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_XATTR) From 9742805d6b1bfb45d7f267648c34fb5bcd347397 Mon Sep 17 00:00:00 2001 From: Steve French Date: Mon, 19 Sep 2016 22:06:35 -0500 Subject: [PATCH 03/21] Display number of credits available In debugging smb3, it is useful to display the number of credits available, so we can see when the server has not granted sufficient operations for the client to make progress, or alternatively the client has requested too many credits (as we saw in a recent bug) so we can compare with the number of credits the server thinks we have. Add a /proc/fs/cifs/DebugData line to display the client view on how many credits are available. Signed-off-by: Steve French Reported-by: Germano Percossi CC: Stable --- fs/cifs/cifs_debug.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/cifs/cifs_debug.c b/fs/cifs/cifs_debug.c index 6c58e13fed2f..3d03e48a9213 100644 --- a/fs/cifs/cifs_debug.c +++ b/fs/cifs/cifs_debug.c @@ -152,6 +152,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) list_for_each(tmp1, &cifs_tcp_ses_list) { server = list_entry(tmp1, struct TCP_Server_Info, tcp_ses_list); + seq_printf(m, "\nNumber of credits: %d", server->credits); i++; list_for_each(tmp2, &server->smb_ses_list) { ses = list_entry(tmp2, struct cifs_ses, From 7d414f396c91a3382e51cf628c1cf0709ad0188b Mon Sep 17 00:00:00 2001 From: Ross Lagerwall Date: Tue, 20 Sep 2016 13:37:13 +0100 Subject: [PATCH 04/21] cifs: Limit the overall credit acquired The kernel client requests 2 credits for many operations even though they only use 1 credit (presumably to build up a buffer of credit). Some servers seem to give the client as much credit as is requested. In this case, the amount of credit the client has continues increasing to the point where (server->credits * MAX_BUFFER_SIZE) overflows in smb2_wait_mtu_credits(). Fix this by throttling the credit requests if an set limit is reached. For async requests where the credit charge may be > 1, request as much credit as what is charged. The limit is chosen somewhat arbitrarily. The Windows client defaults to 128 credits, the Windows server allows clients up to 512 credits (or 8192 for Windows 2016), and the NetApp server (and at least one other) does not limit clients at all. Choose a high enough value such that the client shouldn't limit performance. This behavior was seen with a NetApp filer (NetApp Release 9.0RC2). Signed-off-by: Ross Lagerwall CC: Stable Signed-off-by: Steve French --- fs/cifs/smb2glob.h | 10 ++++++++++ fs/cifs/smb2pdu.c | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/fs/cifs/smb2glob.h b/fs/cifs/smb2glob.h index 0ffa18094335..238759c146ba 100644 --- a/fs/cifs/smb2glob.h +++ b/fs/cifs/smb2glob.h @@ -61,4 +61,14 @@ /* Maximum buffer size value we can send with 1 credit */ #define SMB2_MAX_BUFFER_SIZE 65536 +/* + * Maximum number of credits to keep available. + * This value is chosen somewhat arbitrarily. The Windows client + * defaults to 128 credits, the Windows server allows clients up to + * 512 credits, and the NetApp server does not limit clients at all. + * Choose a high enough value such that the client shouldn't limit + * performance. + */ +#define SMB2_MAX_CREDITS_AVAILABLE 32000 + #endif /* _SMB2_GLOB_H */ diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 29e06db5f187..967a7904fb8f 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -100,7 +100,21 @@ smb2_hdr_assemble(struct smb2_hdr *hdr, __le16 smb2_cmd /* command */ , hdr->ProtocolId = SMB2_PROTO_NUMBER; hdr->StructureSize = cpu_to_le16(64); hdr->Command = smb2_cmd; - hdr->CreditRequest = cpu_to_le16(2); /* BB make this dynamic */ + if (tcon && tcon->ses && tcon->ses->server) { + struct TCP_Server_Info *server = tcon->ses->server; + + spin_lock(&server->req_lock); + /* Request up to 2 credits but don't go over the limit. */ + if (server->credits >= SMB2_MAX_CREDITS_AVAILABLE) + hdr->CreditRequest = cpu_to_le16(0); + else + hdr->CreditRequest = cpu_to_le16( + min_t(int, SMB2_MAX_CREDITS_AVAILABLE - + server->credits, 2)); + spin_unlock(&server->req_lock); + } else { + hdr->CreditRequest = cpu_to_le16(2); + } hdr->ProcessId = cpu_to_le32((__u16)current->tgid); if (!tcon) @@ -2057,6 +2071,7 @@ smb2_async_readv(struct cifs_readdata *rdata) if (rdata->credits) { buf->CreditCharge = cpu_to_le16(DIV_ROUND_UP(rdata->bytes, SMB2_MAX_BUFFER_SIZE)); + buf->CreditRequest = buf->CreditCharge; spin_lock(&server->req_lock); server->credits += rdata->credits - le16_to_cpu(buf->CreditCharge); @@ -2243,6 +2258,7 @@ smb2_async_writev(struct cifs_writedata *wdata, if (wdata->credits) { req->hdr.CreditCharge = cpu_to_le16(DIV_ROUND_UP(wdata->bytes, SMB2_MAX_BUFFER_SIZE)); + req->hdr.CreditRequest = req->hdr.CreditCharge; spin_lock(&server->req_lock); server->credits += wdata->credits - le16_to_cpu(req->hdr.CreditCharge); From c2afb8147e69819885493edf3a7c1ce03aaf2d4e Mon Sep 17 00:00:00 2001 From: Steve French Date: Tue, 20 Sep 2016 22:56:13 -0500 Subject: [PATCH 05/21] Set previous session id correctly on SMB3 reconnect Signed-off-by: Steve French CC: Stable Reported-by: David Goebel --- fs/cifs/smb2pdu.c | 5 +++++ fs/cifs/smb2pdu.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 967a7904fb8f..e8638432a233 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -604,6 +604,7 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, char *security_blob = NULL; unsigned char *ntlmssp_blob = NULL; bool use_spnego = false; /* else use raw ntlmssp */ + u64 previous_session = ses->Suid; cifs_dbg(FYI, "Session Setup\n"); @@ -641,6 +642,10 @@ ssetup_ntlmssp_authenticate: return rc; req->hdr.SessionId = 0; /* First session, not a reauthenticate */ + + /* if reconnect, we need to send previous sess id, otherwise it is 0 */ + req->PreviousSessionId = previous_session; + req->Flags = 0; /* MBZ */ /* to enable echos and oplocks */ req->hdr.CreditRequest = cpu_to_le16(3); diff --git a/fs/cifs/smb2pdu.h b/fs/cifs/smb2pdu.h index ff88d9feb01e..fd3709e8de33 100644 --- a/fs/cifs/smb2pdu.h +++ b/fs/cifs/smb2pdu.h @@ -276,7 +276,7 @@ struct smb2_sess_setup_req { __le32 Channel; __le16 SecurityBufferOffset; __le16 SecurityBufferLength; - __le64 PreviousSessionId; + __u64 PreviousSessionId; __u8 Buffer[1]; /* variable length GSS security buffer */ } __packed; From fa70b87cc6641978b20e12cc5d517e9ffc0086d4 Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 22 Sep 2016 00:39:34 -0500 Subject: [PATCH 06/21] SMB3: GUIDs should be constructed as random but valid uuids GUIDs although random, and 16 bytes, need to be generated as proper uuids. Signed-off-by: Steve French Reviewed-by: Aurelien Aptel Reported-by: David Goebels CC: Stable --- fs/cifs/cifsfs.c | 2 +- fs/cifs/connect.c | 2 +- fs/cifs/smb2ops.c | 2 +- fs/cifs/smb2pdu.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index cca04e710421..978dbf049a77 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -271,7 +271,7 @@ cifs_alloc_inode(struct super_block *sb) cifs_inode->createtime = 0; cifs_inode->epoch = 0; #ifdef CONFIG_CIFS_SMB2 - get_random_bytes(cifs_inode->lease_key, SMB2_LEASE_KEY_SIZE); + generate_random_uuid(cifs_inode->lease_key); #endif /* * Can not set i_flags here - they get immediately overwritten to zero diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 2e4f4bad8b1e..c841244a954d 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2163,7 +2163,7 @@ cifs_get_tcp_session(struct smb_vol *volume_info) memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr, sizeof(tcp_ses->dstaddr)); #ifdef CONFIG_CIFS_SMB2 - get_random_bytes(tcp_ses->client_guid, SMB2_CLIENT_GUID_SIZE); + generate_random_uuid(tcp_ses->client_guid); #endif /* * at this point we are the only ones with the pointer diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index d203c0329626..5ed591d7fff3 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -1041,7 +1041,7 @@ smb2_set_lease_key(struct inode *inode, struct cifs_fid *fid) static void smb2_new_lease_key(struct cifs_fid *fid) { - get_random_bytes(fid->lease_key, SMB2_LEASE_KEY_SIZE); + generate_random_uuid(fid->lease_key); } #define SMB2_SYMLINK_STRUCT_SIZE \ diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index e8638432a233..3eec96ca87d9 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -1183,7 +1183,7 @@ create_durable_v2_buf(struct cifs_fid *pfid) buf->dcontext.Timeout = 0; /* Should this be configurable by workload */ buf->dcontext.Flags = cpu_to_le32(SMB2_DHANDLE_FLAG_PERSISTENT); - get_random_bytes(buf->dcontext.CreateGuid, 16); + generate_random_uuid(buf->dcontext.CreateGuid); memcpy(pfid->create_guid, buf->dcontext.CreateGuid, 16); /* SMB2_CREATE_DURABLE_HANDLE_REQUEST is "DH2Q" */ From 94f873717571c759b7928399cbbddfa3d569bd01 Mon Sep 17 00:00:00 2001 From: Aurelien Aptel Date: Thu, 22 Sep 2016 07:38:50 +0200 Subject: [PATCH 07/21] fs/cifs: keep guid when assigning fid to fileinfo When we open a durable handle we give a Globally Unique Identifier (GUID) to the server which we must keep for later reference e.g. when reopening persistent handles on reconnection. Without this the GUID generated for a new persistent handle was lost and 16 zero bytes were used instead on re-opening. Signed-off-by: Aurelien Aptel CC: Stable Signed-off-by: Steve French --- fs/cifs/smb2ops.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 5ed591d7fff3..91e53df4e420 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -541,6 +541,7 @@ smb2_set_fid(struct cifsFileInfo *cfile, struct cifs_fid *fid, __u32 oplock) server->ops->set_oplock_level(cinode, oplock, fid->epoch, &fid->purge_cache); cinode->can_cache_brlcks = CIFS_CACHE_WRITE(cinode); + memcpy(cfile->fid.create_guid, fid->create_guid, 16); } static void From d171356ff11ab1825e456dfb979755e01b3c54a1 Mon Sep 17 00:00:00 2001 From: Sachin Prabhu Date: Tue, 6 Sep 2016 13:22:34 +0100 Subject: [PATCH 08/21] Fix regression which breaks DFS mounting Patch a6b5058 results in -EREMOTE returned by is_path_accessible() in cifs_mount() to be ignored which breaks DFS mounting. Signed-off-by: Sachin Prabhu Reviewed-by: Aurelien Aptel CC: Stable Signed-off-by: Steve French --- fs/cifs/connect.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index c841244a954d..7b67179521cf 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -3688,14 +3688,16 @@ remote_path_check: goto mount_fail_check; } - rc = cifs_are_all_path_components_accessible(server, + if (rc != -EREMOTE) { + rc = cifs_are_all_path_components_accessible(server, xid, tcon, cifs_sb, full_path); - if (rc != 0) { - cifs_dbg(VFS, "cannot query dirs between root and final path, " - "enabling CIFS_MOUNT_USE_PREFIX_PATH\n"); - cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH; - rc = 0; + if (rc != 0) { + cifs_dbg(VFS, "cannot query dirs between root and final path, " + "enabling CIFS_MOUNT_USE_PREFIX_PATH\n"); + cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH; + rc = 0; + } } kfree(full_path); } From 3afca265b5f53a0b15b79531c13858049505582d Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 22 Sep 2016 18:58:16 -0500 Subject: [PATCH 09/21] Clarify locking of cifs file and tcon structures and make more granular Remove the global file_list_lock to simplify cifs/smb3 locking and have spinlocks that more closely match the information they are protecting. Add new tcon->open_file_lock and file->file_info_lock spinlocks. Locks continue to follow a heirachy, cifs_socket --> cifs_ses --> cifs_tcon --> cifs_file where global tcp_ses_lock still protects socket and cifs_ses, while the the newer locks protect the lower level structure's information (tcon and cifs_file respectively). CC: Stable Signed-off-by: Steve French Signed-off-by: Pavel Shilovsky Reviewed-by: Aurelien Aptel Reviewed-by: Germano Percossi --- fs/cifs/cifsfs.c | 1 - fs/cifs/cifsglob.h | 30 ++++++++++----------- fs/cifs/cifssmb.c | 4 +-- fs/cifs/file.c | 66 +++++++++++++++++++++++++++------------------- fs/cifs/misc.c | 15 ++++++----- fs/cifs/readdir.c | 6 ++--- fs/cifs/smb2misc.c | 16 +++++------ 7 files changed, 75 insertions(+), 63 deletions(-) diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 978dbf049a77..d2b2703fed7d 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -1262,7 +1262,6 @@ init_cifs(void) GlobalTotalActiveXid = 0; GlobalMaxActiveXid = 0; spin_lock_init(&cifs_tcp_ses_lock); - spin_lock_init(&cifs_file_list_lock); spin_lock_init(&GlobalMid_Lock); get_random_bytes(&cifs_lock_secret, sizeof(cifs_lock_secret)); diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 8f1d8c1e72be..65f78b7a9062 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -833,6 +833,7 @@ struct cifs_tcon { struct list_head tcon_list; int tc_count; struct list_head openFileList; + spinlock_t open_file_lock; /* protects list above */ struct cifs_ses *ses; /* pointer to session associated with */ char treeName[MAX_TREE_SIZE + 1]; /* UNC name of resource in ASCII */ char *nativeFileSystem; @@ -889,7 +890,7 @@ struct cifs_tcon { #endif /* CONFIG_CIFS_STATS2 */ __u64 bytes_read; __u64 bytes_written; - spinlock_t stat_lock; + spinlock_t stat_lock; /* protects the two fields above */ #endif /* CONFIG_CIFS_STATS */ FILE_SYSTEM_DEVICE_INFO fsDevInfo; FILE_SYSTEM_ATTRIBUTE_INFO fsAttrInfo; /* ok if fs name truncated */ @@ -1040,8 +1041,10 @@ struct cifs_fid_locks { }; struct cifsFileInfo { + /* following two lists are protected by tcon->open_file_lock */ struct list_head tlist; /* pointer to next fid owned by tcon */ struct list_head flist; /* next fid (file instance) for this inode */ + /* lock list below protected by cifsi->lock_sem */ struct cifs_fid_locks *llist; /* brlocks held by this fid */ kuid_t uid; /* allows finding which FileInfo structure */ __u32 pid; /* process id who opened file */ @@ -1049,11 +1052,12 @@ struct cifsFileInfo { /* BB add lock scope info here if needed */ ; /* lock scope id (0 if none) */ struct dentry *dentry; - unsigned int f_flags; struct tcon_link *tlink; + unsigned int f_flags; bool invalidHandle:1; /* file closed via session abend */ bool oplock_break_cancelled:1; - int count; /* refcount protected by cifs_file_list_lock */ + int count; + spinlock_t file_info_lock; /* protects four flag/count fields above */ struct mutex fh_mutex; /* prevents reopen race after dead ses*/ struct cifs_search_info srch_inf; struct work_struct oplock_break; /* work for oplock breaks */ @@ -1120,7 +1124,7 @@ struct cifs_writedata { /* * Take a reference on the file private data. Must be called with - * cifs_file_list_lock held. + * cfile->file_info_lock held. */ static inline void cifsFileInfo_get_locked(struct cifsFileInfo *cifs_file) @@ -1514,8 +1518,10 @@ require use of the stronger protocol */ * GlobalMid_Lock protects: * list operations on pending_mid_q and oplockQ * updates to XID counters, multiplex id and SMB sequence numbers - * cifs_file_list_lock protects: - * list operations on tcp and SMB session lists and tCon lists + * tcp_ses_lock protects: + * list operations on tcp and SMB session lists + * tcon->open_file_lock protects the list of open files hanging off the tcon + * cfile->file_info_lock protects counters and fields in cifs file struct * f_owner.lock protects certain per file struct operations * mapping->page_lock protects certain per page operations * @@ -1547,18 +1553,12 @@ GLOBAL_EXTERN struct list_head cifs_tcp_ses_list; * tcp session, and the list of tcon's per smb session. It also protects * the reference counters for the server, smb session, and tcon. Finally, * changes to the tcon->tidStatus should be done while holding this lock. + * generally the locks should be taken in order tcp_ses_lock before + * tcon->open_file_lock and that before file->file_info_lock since the + * structure order is cifs_socket-->cifs_ses-->cifs_tcon-->cifs_file */ GLOBAL_EXTERN spinlock_t cifs_tcp_ses_lock; -/* - * This lock protects the cifs_file->llist and cifs_file->flist - * list operations, and updates to some flags (cifs_file->invalidHandle) - * It will be moved to either use the tcon->stat_lock or equivalent later. - * If cifs_tcp_ses_lock and the lock below are both needed to be held, then - * the cifs_tcp_ses_lock must be grabbed first and released last. - */ -GLOBAL_EXTERN spinlock_t cifs_file_list_lock; - #ifdef CONFIG_CIFS_DNOTIFY_EXPERIMENTAL /* unused temporarily */ /* Outstanding dir notify requests */ GLOBAL_EXTERN struct list_head GlobalDnotifyReqList; diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c index f82d2823622f..3f3185febc58 100644 --- a/fs/cifs/cifssmb.c +++ b/fs/cifs/cifssmb.c @@ -98,13 +98,13 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon) struct list_head *tmp1; /* list all files open on tree connection and mark them invalid */ - spin_lock(&cifs_file_list_lock); + spin_lock(&tcon->open_file_lock); list_for_each_safe(tmp, tmp1, &tcon->openFileList) { open_file = list_entry(tmp, struct cifsFileInfo, tlist); open_file->invalidHandle = true; open_file->oplock_break_cancelled = true; } - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); /* * BB Add call to invalidate_inodes(sb) for all superblocks mounted * to this tcon. diff --git a/fs/cifs/file.c b/fs/cifs/file.c index a95fe8b1afe9..ee5ceae22411 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -305,6 +305,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, cfile->tlink = cifs_get_tlink(tlink); INIT_WORK(&cfile->oplock_break, cifs_oplock_break); mutex_init(&cfile->fh_mutex); + spin_lock_init(&cfile->file_info_lock); cifs_sb_active(inode->i_sb); @@ -317,7 +318,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, oplock = 0; } - spin_lock(&cifs_file_list_lock); + spin_lock(&tcon->open_file_lock); if (fid->pending_open->oplock != CIFS_OPLOCK_NO_CHANGE && oplock) oplock = fid->pending_open->oplock; list_del(&fid->pending_open->olist); @@ -326,12 +327,13 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, server->ops->set_fid(cfile, fid, oplock); list_add(&cfile->tlist, &tcon->openFileList); + /* if readable file instance put first in list*/ if (file->f_mode & FMODE_READ) list_add(&cfile->flist, &cinode->openFileList); else list_add_tail(&cfile->flist, &cinode->openFileList); - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); if (fid->purge_cache) cifs_zap_mapping(inode); @@ -343,16 +345,16 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, struct cifsFileInfo * cifsFileInfo_get(struct cifsFileInfo *cifs_file) { - spin_lock(&cifs_file_list_lock); + spin_lock(&cifs_file->file_info_lock); cifsFileInfo_get_locked(cifs_file); - spin_unlock(&cifs_file_list_lock); + spin_unlock(&cifs_file->file_info_lock); return cifs_file; } /* * Release a reference on the file private data. This may involve closing * the filehandle out on the server. Must be called without holding - * cifs_file_list_lock. + * tcon->open_file_lock and cifs_file->file_info_lock. */ void cifsFileInfo_put(struct cifsFileInfo *cifs_file) { @@ -367,11 +369,15 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file) struct cifs_pending_open open; bool oplock_break_cancelled; - spin_lock(&cifs_file_list_lock); + spin_lock(&tcon->open_file_lock); + + spin_lock(&cifs_file->file_info_lock); if (--cifs_file->count > 0) { - spin_unlock(&cifs_file_list_lock); + spin_unlock(&cifs_file->file_info_lock); + spin_unlock(&tcon->open_file_lock); return; } + spin_unlock(&cifs_file->file_info_lock); if (server->ops->get_lease_key) server->ops->get_lease_key(inode, &fid); @@ -395,7 +401,8 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file) set_bit(CIFS_INO_INVALID_MAPPING, &cifsi->flags); cifs_set_oplock_level(cifsi, 0); } - spin_unlock(&cifs_file_list_lock); + + spin_unlock(&tcon->open_file_lock); oplock_break_cancelled = cancel_work_sync(&cifs_file->oplock_break); @@ -772,10 +779,10 @@ int cifs_closedir(struct inode *inode, struct file *file) server = tcon->ses->server; cifs_dbg(FYI, "Freeing private data in close dir\n"); - spin_lock(&cifs_file_list_lock); + spin_lock(&cfile->file_info_lock); if (server->ops->dir_needs_close(cfile)) { cfile->invalidHandle = true; - spin_unlock(&cifs_file_list_lock); + spin_unlock(&cfile->file_info_lock); if (server->ops->close_dir) rc = server->ops->close_dir(xid, tcon, &cfile->fid); else @@ -784,7 +791,7 @@ int cifs_closedir(struct inode *inode, struct file *file) /* not much we can do if it fails anyway, ignore rc */ rc = 0; } else - spin_unlock(&cifs_file_list_lock); + spin_unlock(&cfile->file_info_lock); buf = cfile->srch_inf.ntwrk_buf_start; if (buf) { @@ -1728,12 +1735,13 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode, { struct cifsFileInfo *open_file = NULL; struct cifs_sb_info *cifs_sb = CIFS_SB(cifs_inode->vfs_inode.i_sb); + struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); /* only filter by fsuid on multiuser mounts */ if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER)) fsuid_only = false; - spin_lock(&cifs_file_list_lock); + spin_lock(&tcon->open_file_lock); /* we could simply get the first_list_entry since write-only entries are always at the end of the list but since the first entry might have a close pending, we go through the whole list */ @@ -1744,8 +1752,8 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode, if (!open_file->invalidHandle) { /* found a good file */ /* lock it so it will not be closed on us */ - cifsFileInfo_get_locked(open_file); - spin_unlock(&cifs_file_list_lock); + cifsFileInfo_get(open_file); + spin_unlock(&tcon->open_file_lock); return open_file; } /* else might as well continue, and look for another, or simply have the caller reopen it @@ -1753,7 +1761,7 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode, } else /* write only file */ break; /* write only files are last so must be done */ } - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); return NULL; } @@ -1762,6 +1770,7 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode, { struct cifsFileInfo *open_file, *inv_file = NULL; struct cifs_sb_info *cifs_sb; + struct cifs_tcon *tcon; bool any_available = false; int rc; unsigned int refind = 0; @@ -1777,15 +1786,16 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode, } cifs_sb = CIFS_SB(cifs_inode->vfs_inode.i_sb); + tcon = cifs_sb_master_tcon(cifs_sb); /* only filter by fsuid on multiuser mounts */ if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER)) fsuid_only = false; - spin_lock(&cifs_file_list_lock); + spin_lock(&tcon->open_file_lock); refind_writable: if (refind > MAX_REOPEN_ATT) { - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); return NULL; } list_for_each_entry(open_file, &cifs_inode->openFileList, flist) { @@ -1796,8 +1806,8 @@ refind_writable: if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) { if (!open_file->invalidHandle) { /* found a good writable file */ - cifsFileInfo_get_locked(open_file); - spin_unlock(&cifs_file_list_lock); + cifsFileInfo_get(open_file); + spin_unlock(&tcon->open_file_lock); return open_file; } else { if (!inv_file) @@ -1813,24 +1823,24 @@ refind_writable: if (inv_file) { any_available = false; - cifsFileInfo_get_locked(inv_file); + cifsFileInfo_get(inv_file); } - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); if (inv_file) { rc = cifs_reopen_file(inv_file, false); if (!rc) return inv_file; else { - spin_lock(&cifs_file_list_lock); + spin_lock(&tcon->open_file_lock); list_move_tail(&inv_file->flist, &cifs_inode->openFileList); - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); cifsFileInfo_put(inv_file); - spin_lock(&cifs_file_list_lock); ++refind; inv_file = NULL; + spin_lock(&tcon->open_file_lock); goto refind_writable; } } @@ -3612,15 +3622,17 @@ static int cifs_readpage(struct file *file, struct page *page) static int is_inode_writable(struct cifsInodeInfo *cifs_inode) { struct cifsFileInfo *open_file; + struct cifs_tcon *tcon = + cifs_sb_master_tcon(CIFS_SB(cifs_inode->vfs_inode.i_sb)); - spin_lock(&cifs_file_list_lock); + spin_lock(&tcon->open_file_lock); list_for_each_entry(open_file, &cifs_inode->openFileList, flist) { if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) { - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); return 1; } } - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); return 0; } diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index 813fe13c2ae1..c6729156f9a0 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -120,6 +120,7 @@ tconInfoAlloc(void) ++ret_buf->tc_count; INIT_LIST_HEAD(&ret_buf->openFileList); INIT_LIST_HEAD(&ret_buf->tcon_list); + spin_lock_init(&ret_buf->open_file_lock); #ifdef CONFIG_CIFS_STATS spin_lock_init(&ret_buf->stat_lock); #endif @@ -465,7 +466,7 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv) continue; cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks); - spin_lock(&cifs_file_list_lock); + spin_lock(&tcon->open_file_lock); list_for_each(tmp2, &tcon->openFileList) { netfile = list_entry(tmp2, struct cifsFileInfo, tlist); @@ -495,11 +496,11 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv) &netfile->oplock_break); netfile->oplock_break_cancelled = false; - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); spin_unlock(&cifs_tcp_ses_lock); return true; } - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); spin_unlock(&cifs_tcp_ses_lock); cifs_dbg(FYI, "No matching file for oplock break\n"); return true; @@ -613,9 +614,9 @@ backup_cred(struct cifs_sb_info *cifs_sb) void cifs_del_pending_open(struct cifs_pending_open *open) { - spin_lock(&cifs_file_list_lock); + spin_lock(&tlink_tcon(open->tlink)->open_file_lock); list_del(&open->olist); - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tlink_tcon(open->tlink)->open_file_lock); } void @@ -635,7 +636,7 @@ void cifs_add_pending_open(struct cifs_fid *fid, struct tcon_link *tlink, struct cifs_pending_open *open) { - spin_lock(&cifs_file_list_lock); + spin_lock(&tlink_tcon(tlink)->open_file_lock); cifs_add_pending_open_locked(fid, tlink, open); - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tlink_tcon(open->tlink)->open_file_lock); } diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c index 65cf85dcda09..8f6a2a5863b9 100644 --- a/fs/cifs/readdir.c +++ b/fs/cifs/readdir.c @@ -597,14 +597,14 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos, is_dir_changed(file)) || (index_to_find < first_entry_in_buffer)) { /* close and restart search */ cifs_dbg(FYI, "search backing up - close and restart search\n"); - spin_lock(&cifs_file_list_lock); + spin_lock(&cfile->file_info_lock); if (server->ops->dir_needs_close(cfile)) { cfile->invalidHandle = true; - spin_unlock(&cifs_file_list_lock); + spin_unlock(&cfile->file_info_lock); if (server->ops->close_dir) server->ops->close_dir(xid, tcon, &cfile->fid); } else - spin_unlock(&cifs_file_list_lock); + spin_unlock(&cfile->file_info_lock); if (cfile->srch_inf.ntwrk_buf_start) { cifs_dbg(FYI, "freeing SMB ff cache buf on search rewind\n"); if (cfile->srch_inf.smallBuf) diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c index 389fb9f8c84e..3d383489b9cf 100644 --- a/fs/cifs/smb2misc.c +++ b/fs/cifs/smb2misc.c @@ -549,19 +549,19 @@ smb2_is_valid_lease_break(char *buffer) list_for_each(tmp1, &server->smb_ses_list) { ses = list_entry(tmp1, struct cifs_ses, smb_ses_list); - spin_lock(&cifs_file_list_lock); list_for_each(tmp2, &ses->tcon_list) { tcon = list_entry(tmp2, struct cifs_tcon, tcon_list); + spin_lock(&tcon->open_file_lock); cifs_stats_inc( &tcon->stats.cifs_stats.num_oplock_brks); if (smb2_tcon_has_lease(tcon, rsp, lw)) { - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); spin_unlock(&cifs_tcp_ses_lock); return true; } + spin_unlock(&tcon->open_file_lock); } - spin_unlock(&cifs_file_list_lock); } } spin_unlock(&cifs_tcp_ses_lock); @@ -603,7 +603,7 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) tcon = list_entry(tmp1, struct cifs_tcon, tcon_list); cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks); - spin_lock(&cifs_file_list_lock); + spin_lock(&tcon->open_file_lock); list_for_each(tmp2, &tcon->openFileList) { cfile = list_entry(tmp2, struct cifsFileInfo, tlist); @@ -615,7 +615,7 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) cifs_dbg(FYI, "file id match, oplock break\n"); cinode = CIFS_I(d_inode(cfile->dentry)); - + spin_lock(&cfile->file_info_lock); if (!CIFS_CACHE_WRITE(cinode) && rsp->OplockLevel == SMB2_OPLOCK_LEVEL_NONE) cfile->oplock_break_cancelled = true; @@ -637,14 +637,14 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) clear_bit( CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, &cinode->flags); - + spin_unlock(&cfile->file_info_lock); queue_work(cifsiod_wq, &cfile->oplock_break); - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); spin_unlock(&cifs_tcp_ses_lock); return true; } - spin_unlock(&cifs_file_list_lock); + spin_unlock(&tcon->open_file_lock); spin_unlock(&cifs_tcp_ses_lock); cifs_dbg(FYI, "No matching file for oplock break\n"); return true; From 52ace1ef1259e119a24a34b45cb800c4e7529090 Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 22 Sep 2016 19:23:56 -0500 Subject: [PATCH 10/21] fs/cifs: reopen persistent handles on reconnect Continuous Availability features like persistent handles require that clients reconnect their open files, not just the sessions, soon after the network connection comes back up, otherwise the server will throw away the state (byte range locks, leases, deny modes) on those handles after a timeout. Add code to reconnect handles when use_persistent set (e.g. Continuous Availability shares) after tree reconnect. Signed-off-by: Aurelien Aptel Reviewed-by: Germano Percossi Signed-off-by: Steve French --- fs/cifs/cifsproto.h | 2 ++ fs/cifs/file.c | 18 ++++++++++++++++++ fs/cifs/smb2pdu.c | 5 +++++ 3 files changed, 25 insertions(+) diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index 4ead72a001f9..ced0e42ce460 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -193,6 +193,8 @@ extern struct smb_vol *cifs_get_volume_info(char *mount_data, extern int cifs_mount(struct cifs_sb_info *, struct smb_vol *); extern void cifs_umount(struct cifs_sb_info *); extern void cifs_mark_open_files_invalid(struct cifs_tcon *tcon); +extern void cifs_reopen_persistent_handles(struct cifs_tcon *tcon); + extern bool cifs_find_lock_conflict(struct cifsFileInfo *cfile, __u64 offset, __u64 length, __u8 type, struct cifsLockInfo **conf_lock, diff --git a/fs/cifs/file.c b/fs/cifs/file.c index ee5ceae22411..8f27c8a74384 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -760,6 +760,24 @@ int cifs_close(struct inode *inode, struct file *file) return 0; } +void +cifs_reopen_persistent_handles(struct cifs_tcon *tcon) +{ + struct cifsFileInfo *open_file = NULL; + struct list_head *tmp; + struct list_head *tmp1; + + /* list all files open on tree connection, reopen resilient handles */ + spin_lock(&tcon->open_file_lock); + list_for_each_safe(tmp, tmp1, &tcon->openFileList) { + open_file = list_entry(tmp, struct cifsFileInfo, tlist); + spin_unlock(&tcon->open_file_lock); + cifs_reopen_file(open_file, false /* do not flush */); + spin_lock(&tcon->open_file_lock); + } + spin_unlock(&tcon->open_file_lock); +} + int cifs_closedir(struct inode *inode, struct file *file) { int rc = 0; diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 3eec96ca87d9..4d944c4c55a8 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -250,8 +250,13 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon) } cifs_mark_open_files_invalid(tcon); + rc = SMB2_tcon(0, tcon->ses, tcon->treeName, tcon, nls_codepage); mutex_unlock(&tcon->ses->session_mutex); + + if (tcon->use_persistent) + cifs_reopen_persistent_handles(tcon); + cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); if (rc) goto out; From 141891f4727c08829755be6c785e125d2e96c899 Mon Sep 17 00:00:00 2001 From: Steve French Date: Fri, 23 Sep 2016 00:44:16 -0500 Subject: [PATCH 11/21] SMB3: Add mount parameter to allow user to override max credits Add mount option "max_credits" to allow setting maximum SMB3 credits to any value from 10 to 64000 (default is 32000). This can be useful to workaround servers with problems allocating credits, or to throttle the client to use smaller amount of simultaneous i/o or to workaround server performance issues. Also adds a cap, so that even if the server granted us more than 65000 credits due to a server bug, we would not use that many. Signed-off-by: Steve French --- fs/cifs/cifsglob.h | 16 +++++++++++++++- fs/cifs/connect.c | 19 ++++++++++++++++--- fs/cifs/smb2glob.h | 10 ---------- fs/cifs/smb2ops.c | 4 ++++ fs/cifs/smb2pdu.c | 4 ++-- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 65f78b7a9062..cd80594f14d0 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -75,6 +75,18 @@ #define SMB_ECHO_INTERVAL_MAX 600 #define SMB_ECHO_INTERVAL_DEFAULT 60 +/* + * Default number of credits to keep available for SMB3. + * This value is chosen somewhat arbitrarily. The Windows client + * defaults to 128 credits, the Windows server allows clients up to + * 512 credits (or 8K for later versions), and the NetApp server + * does not limit clients at all. Choose a high enough default value + * such that the client shouldn't limit performance, but allow mount + * to override (until you approach 64K, where we limit credits to 65000 + * to reduce possibility of seeing more server credit overflow bugs. + */ +#define SMB2_MAX_CREDITS_AVAILABLE 32000 + #include "cifspdu.h" #ifndef XATTR_DOS_ATTRIB @@ -510,6 +522,7 @@ struct smb_vol { struct sockaddr_storage srcaddr; /* allow binding to a local IP */ struct nls_table *local_nls; unsigned int echo_interval; /* echo interval in secs */ + unsigned int max_credits; /* smb3 max_credits 10 < credits < 60000 */ }; #define CIFS_MOUNT_MASK (CIFS_MOUNT_NO_PERM | CIFS_MOUNT_SET_UID | \ @@ -567,7 +580,8 @@ struct TCP_Server_Info { bool noblocksnd; /* use blocking sendmsg */ bool noautotune; /* do not autotune send buf sizes */ bool tcp_nodelay; - int credits; /* send no more requests at once */ + unsigned int credits; /* send no more requests at once */ + unsigned int max_credits; /* can override large 32000 default at mnt */ unsigned int in_flight; /* number of requests on the wire to server */ spinlock_t req_lock; /* protect the two values above */ struct mutex srv_mutex; diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 7b67179521cf..657369db76df 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -63,7 +63,6 @@ extern mempool_t *cifs_req_poolp; #define TLINK_IDLE_EXPIRE (600 * HZ) enum { - /* Mount options that take no arguments */ Opt_user_xattr, Opt_nouser_xattr, Opt_forceuid, Opt_noforceuid, @@ -95,7 +94,7 @@ enum { Opt_cruid, Opt_gid, Opt_file_mode, Opt_dirmode, Opt_port, Opt_rsize, Opt_wsize, Opt_actimeo, - Opt_echo_interval, + Opt_echo_interval, Opt_max_credits, /* Mount options which take string value */ Opt_user, Opt_pass, Opt_ip, @@ -190,6 +189,7 @@ static const match_table_t cifs_mount_option_tokens = { { Opt_wsize, "wsize=%s" }, { Opt_actimeo, "actimeo=%s" }, { Opt_echo_interval, "echo_interval=%s" }, + { Opt_max_credits, "max_credits=%s" }, { Opt_blank_user, "user=" }, { Opt_blank_user, "username=" }, @@ -1586,6 +1586,15 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, } vol->echo_interval = option; break; + case Opt_max_credits: + if (get_option_ul(args, &option) || (option < 20) || + (option > 60000)) { + cifs_dbg(VFS, "%s: Invalid max_credits value\n", + __func__); + goto cifs_parse_mount_err; + } + vol->max_credits = option; + break; /* String Arguments */ @@ -3598,7 +3607,11 @@ try_mount_again: bdi_destroy(&cifs_sb->bdi); goto out; } - + if ((volume_info->max_credits < 20) || + (volume_info->max_credits > 60000)) + server->max_credits = SMB2_MAX_CREDITS_AVAILABLE; + else + server->max_credits = volume_info->max_credits; /* get a reference to a SMB session */ ses = cifs_get_smb_ses(server, volume_info); if (IS_ERR(ses)) { diff --git a/fs/cifs/smb2glob.h b/fs/cifs/smb2glob.h index 238759c146ba..0ffa18094335 100644 --- a/fs/cifs/smb2glob.h +++ b/fs/cifs/smb2glob.h @@ -61,14 +61,4 @@ /* Maximum buffer size value we can send with 1 credit */ #define SMB2_MAX_BUFFER_SIZE 65536 -/* - * Maximum number of credits to keep available. - * This value is chosen somewhat arbitrarily. The Windows client - * defaults to 128 credits, the Windows server allows clients up to - * 512 credits, and the NetApp server does not limit clients at all. - * Choose a high enough value such that the client shouldn't limit - * performance. - */ -#define SMB2_MAX_CREDITS_AVAILABLE 32000 - #endif /* _SMB2_GLOB_H */ diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 91e53df4e420..378260cd25a6 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -70,6 +70,10 @@ smb2_add_credits(struct TCP_Server_Info *server, const unsigned int add, spin_lock(&server->req_lock); val = server->ops->get_credits_field(server, optype); *val += add; + if (*val > 65000) { + *val = 65000; /* Don't get near 64K credits, avoid srv bugs */ + printk_once(KERN_WARNING "server overflowed SMB3 credits\n"); + } server->in_flight--; if (server->in_flight == 0 && (optype & CIFS_OP_MASK) != CIFS_NEG_OP) rc = change_conf(server); diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 4d944c4c55a8..d6a045690266 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -105,11 +105,11 @@ smb2_hdr_assemble(struct smb2_hdr *hdr, __le16 smb2_cmd /* command */ , spin_lock(&server->req_lock); /* Request up to 2 credits but don't go over the limit. */ - if (server->credits >= SMB2_MAX_CREDITS_AVAILABLE) + if (server->credits >= server->max_credits) hdr->CreditRequest = cpu_to_le16(0); else hdr->CreditRequest = cpu_to_le16( - min_t(int, SMB2_MAX_CREDITS_AVAILABLE - + min_t(int, server->max_credits - server->credits, 2)); spin_unlock(&server->req_lock); } else { From 18dd8e1a65ddae2351d0f0d6dd4a334f441fc5fa Mon Sep 17 00:00:00 2001 From: Steve French Date: Mon, 26 Sep 2016 14:23:08 -0500 Subject: [PATCH 12/21] Do not send SMB3 SET_INFO request if nothing is changing [CIFS] We had cases where we sent a SMB2/SMB3 setinfo request with all timestamp (and DOS attribute) fields marked as 0 (ie do not change) e.g. on chmod or chown. Signed-off-by: Steve French CC: Stable --- fs/cifs/smb2inode.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c index 4f0231e685a9..1238cd3552f9 100644 --- a/fs/cifs/smb2inode.c +++ b/fs/cifs/smb2inode.c @@ -266,9 +266,15 @@ smb2_set_file_info(struct inode *inode, const char *full_path, struct tcon_link *tlink; int rc; + if ((buf->CreationTime == 0) && (buf->LastAccessTime == 0) && + (buf->LastWriteTime == 0) && (buf->ChangeTime) && + (buf->Attributes == 0)) + return 0; /* would be a no op, no sense sending this */ + tlink = cifs_sb_tlink(cifs_sb); if (IS_ERR(tlink)) return PTR_ERR(tlink); + rc = smb2_open_op_close(xid, tlink_tcon(tlink), cifs_sb, full_path, FILE_WRITE_ATTRIBUTES, FILE_OPEN, 0, buf, SMB2_OP_SET_INFO); From 834170c85978b0e85805364f623393968f7a9b5e Mon Sep 17 00:00:00 2001 From: Steve French Date: Fri, 30 Sep 2016 21:14:26 -0500 Subject: [PATCH 13/21] Enable previous version support Add ioctl to query previous versions of file Allows listing snapshots on files on SMB3 mounts. Signed-off-by: Steve French --- fs/cifs/cifs_ioctl.h | 8 ++++++++ fs/cifs/cifsglob.h | 2 ++ fs/cifs/ioctl.c | 16 +++++++++++++-- fs/cifs/smb2ops.c | 48 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/fs/cifs/cifs_ioctl.h b/fs/cifs/cifs_ioctl.h index 0065256881d8..57ff0756e30c 100644 --- a/fs/cifs/cifs_ioctl.h +++ b/fs/cifs/cifs_ioctl.h @@ -36,7 +36,15 @@ struct smb_mnt_fs_info { __u64 cifs_posix_caps; } __packed; +struct smb_snapshot_array { + __u32 number_of_snapshots; + __u32 number_of_snapshots_returned; + __u32 snapshot_array_size; + /* snapshots[]; */ +} __packed; + #define CIFS_IOCTL_MAGIC 0xCF #define CIFS_IOC_COPYCHUNK_FILE _IOW(CIFS_IOCTL_MAGIC, 3, int) #define CIFS_IOC_SET_INTEGRITY _IO(CIFS_IOCTL_MAGIC, 4) #define CIFS_IOC_GET_MNT_INFO _IOR(CIFS_IOCTL_MAGIC, 5, struct smb_mnt_fs_info) +#define CIFS_ENUMERATE_SNAPSHOTS _IOR(CIFS_IOCTL_MAGIC, 6, struct smb_snapshot_array) diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index cd80594f14d0..d9a17288baf3 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -388,6 +388,8 @@ struct smb_version_operations { int (*calc_signature)(struct smb_rqst *, struct TCP_Server_Info *); int (*set_integrity)(const unsigned int, struct cifs_tcon *tcon, struct cifsFileInfo *src_file); + int (*enum_snapshots)(const unsigned int xid, struct cifs_tcon *tcon, + struct cifsFileInfo *src_file, void __user *); int (*query_mf_symlink)(unsigned int, struct cifs_tcon *, struct cifs_sb_info *, const unsigned char *, char *, unsigned int *); diff --git a/fs/cifs/ioctl.c b/fs/cifs/ioctl.c index 7a3b84e300f8..9f51b81119f2 100644 --- a/fs/cifs/ioctl.c +++ b/fs/cifs/ioctl.c @@ -189,7 +189,7 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) xid = get_xid(); cifs_sb = CIFS_SB(inode->i_sb); - + cifs_dbg(VFS, "cifs ioctl 0x%x\n", command); switch (command) { case FS_IOC_GETFLAGS: if (pSMBFile == NULL) @@ -267,11 +267,23 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) tcon = tlink_tcon(pSMBFile->tlink); rc = smb_mnt_get_fsinfo(xid, tcon, (void __user *)arg); break; + case CIFS_ENUMERATE_SNAPSHOTS: + if (arg == 0) { + rc = -EINVAL; + goto cifs_ioc_exit; + } + tcon = tlink_tcon(pSMBFile->tlink); + if (tcon->ses->server->ops->enum_snapshots) + rc = tcon->ses->server->ops->enum_snapshots(xid, tcon, + pSMBFile, (void __user *)arg); + else + rc = -EOPNOTSUPP; + break; default: cifs_dbg(FYI, "unsupported ioctl\n"); break; } - +cifs_ioc_exit: free_xid(xid); return rc; } diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 378260cd25a6..f0872d2abc6f 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -28,6 +28,7 @@ #include "cifs_unicode.h" #include "smb2status.h" #include "smb2glob.h" +#include "cifs_ioctl.h" static int change_conf(struct TCP_Server_Info *server) @@ -894,6 +895,50 @@ smb3_set_integrity(const unsigned int xid, struct cifs_tcon *tcon, } +static int +smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon, + struct cifsFileInfo *cfile, void __user *ioc_buf) +{ + char *retbuf = NULL; + unsigned int ret_data_len = 0; + int rc; + struct smb_snapshot_array snapshot_in; + + rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + FSCTL_SRV_ENUMERATE_SNAPSHOTS, + true /* is_fsctl */, NULL, 0 /* no input data */, + (char **)&retbuf, + &ret_data_len); + cifs_dbg(FYI, "enum snaphots ioctl returned %d and ret buflen is %d\n", + rc, ret_data_len); + if (rc) + return rc; + + if (ret_data_len && (ioc_buf != NULL) && (retbuf != NULL)) { + /* Fixup buffer */ + if (copy_from_user(&snapshot_in, ioc_buf, + sizeof(struct smb_snapshot_array))) { + rc = -EFAULT; + kfree(retbuf); + return rc; + } + if (snapshot_in.snapshot_array_size < sizeof(struct smb_snapshot_array)) { + rc = -ERANGE; + return rc; + } + + if (ret_data_len > snapshot_in.snapshot_array_size) + ret_data_len = snapshot_in.snapshot_array_size; + + if (copy_to_user(ioc_buf, retbuf, ret_data_len)) + rc = -EFAULT; + } + + kfree(retbuf); + return rc; +} + static int smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon, const char *path, struct cifs_sb_info *cifs_sb, @@ -1659,6 +1704,7 @@ struct smb_version_operations smb21_operations = { .clone_range = smb2_clone_range, .wp_retry_size = smb2_wp_retry_size, .dir_needs_close = smb2_dir_needs_close, + .enum_snapshots = smb3_enum_snapshots, }; struct smb_version_operations smb30_operations = { @@ -1745,6 +1791,7 @@ struct smb_version_operations smb30_operations = { .wp_retry_size = smb2_wp_retry_size, .dir_needs_close = smb2_dir_needs_close, .fallocate = smb3_fallocate, + .enum_snapshots = smb3_enum_snapshots, }; #ifdef CONFIG_CIFS_SMB311 @@ -1832,6 +1879,7 @@ struct smb_version_operations smb311_operations = { .wp_retry_size = smb2_wp_retry_size, .dir_needs_close = smb2_dir_needs_close, .fallocate = smb3_fallocate, + .enum_snapshots = smb3_enum_snapshots, }; #endif /* CIFS_SMB311 */ From 24df1483c272c99ed88b0cba135d0e1dfdee3930 Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 29 Sep 2016 04:20:23 -0500 Subject: [PATCH 14/21] Cleanup missing frees on some ioctls Cleanup some missing mem frees on some cifs ioctls, and clarify others to make more obvious that no data is returned. CC: Stable Signed-off-by: Steve French Acked-by: Sachin Prabhu --- fs/cifs/smb2ops.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index f0872d2abc6f..5d456ebb3813 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -292,7 +292,7 @@ SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon) cifs_dbg(FYI, "Link Speed %lld\n", le64_to_cpu(out_buf->LinkSpeed)); } - + kfree(out_buf); return rc; } #endif /* STATS2 */ @@ -705,6 +705,7 @@ smb2_clone_range(const unsigned int xid, cchunk_out: kfree(pcchunk); + kfree(retbuf); return rc; } @@ -829,7 +830,6 @@ smb2_duplicate_extents(const unsigned int xid, { int rc; unsigned int ret_data_len; - char *retbuf = NULL; struct duplicate_extents_to_file dup_ext_buf; struct cifs_tcon *tcon = tlink_tcon(trgtfile->tlink); @@ -855,7 +855,7 @@ smb2_duplicate_extents(const unsigned int xid, FSCTL_DUPLICATE_EXTENTS_TO_FILE, true /* is_fsctl */, (char *)&dup_ext_buf, sizeof(struct duplicate_extents_to_file), - (char **)&retbuf, + NULL, &ret_data_len); if (ret_data_len > 0) @@ -878,7 +878,6 @@ smb3_set_integrity(const unsigned int xid, struct cifs_tcon *tcon, struct cifsFileInfo *cfile) { struct fsctl_set_integrity_information_req integr_info; - char *retbuf = NULL; unsigned int ret_data_len; integr_info.ChecksumAlgorithm = cpu_to_le16(CHECKSUM_TYPE_UNCHANGED); @@ -890,7 +889,7 @@ smb3_set_integrity(const unsigned int xid, struct cifs_tcon *tcon, FSCTL_SET_INTEGRITY_INFORMATION, true /* is_fsctl */, (char *)&integr_info, sizeof(struct fsctl_set_integrity_information_req), - (char **)&retbuf, + NULL, &ret_data_len); } From cb978ac8b85fa8861352e57fcf8020f7f7bfbd82 Mon Sep 17 00:00:00 2001 From: Germano Percossi Date: Fri, 30 Sep 2016 21:25:24 -0500 Subject: [PATCH 15/21] Expose cifs module parameters in sysfs /sys/module/cifs/parameters should display the three other module load time configuration settings for cifs.ko Signed-off-by: Germano Percossi Signed-off-by: Steve French --- fs/cifs/cifsfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index d2b2703fed7d..f963c880c72d 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -64,15 +64,15 @@ unsigned int global_secflags = CIFSSEC_DEF; unsigned int sign_CIFS_PDUs = 1; static const struct super_operations cifs_super_ops; unsigned int CIFSMaxBufSize = CIFS_MAX_MSGSIZE; -module_param(CIFSMaxBufSize, uint, 0); +module_param(CIFSMaxBufSize, uint, 0444); MODULE_PARM_DESC(CIFSMaxBufSize, "Network buffer size (not including header). " "Default: 16384 Range: 8192 to 130048"); unsigned int cifs_min_rcv = CIFS_MIN_RCV_POOL; -module_param(cifs_min_rcv, uint, 0); +module_param(cifs_min_rcv, uint, 0444); MODULE_PARM_DESC(cifs_min_rcv, "Network buffers in pool. Default: 4 Range: " "1 to 64"); unsigned int cifs_min_small = 30; -module_param(cifs_min_small, uint, 0); +module_param(cifs_min_small, uint, 0444); MODULE_PARM_DESC(cifs_min_small, "Small network buffers in pool. Default: 30 " "Range: 2 to 256"); unsigned int cifs_max_pending = CIFS_MAX_REQ; From 3baf1a7b921500596b77487d5a34a27d656fc032 Mon Sep 17 00:00:00 2001 From: Sachin Prabhu Date: Fri, 7 Oct 2016 19:11:21 +0100 Subject: [PATCH 16/21] SMB2: Separate Kerberos authentication from SMB2_sess_setup Add helper functions and split Kerberos authentication off SMB2_sess_setup. Signed-off-by: Sachin Prabhu Signed-off-by: Steve French Reviewed-by: Pavel Shilovsky --- fs/cifs/smb2pdu.c | 276 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 230 insertions(+), 46 deletions(-) diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index d6a045690266..386b512189b2 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -593,6 +593,216 @@ vneg_out: return -EIO; } +struct SMB2_sess_data { + unsigned int xid; + struct cifs_ses *ses; + struct nls_table *nls_cp; + void (*func)(struct SMB2_sess_data *); + int result; + u64 previous_session; + + /* we will send the SMB in three pieces: + * a fixed length beginning part, an optional + * SPNEGO blob (which can be zero length), and a + * last part which will include the strings + * and rest of bcc area. This allows us to avoid + * a large buffer 17K allocation + */ + int buf0_type; + struct kvec iov[2]; +}; + +static int +SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data) +{ + int rc; + struct cifs_ses *ses = sess_data->ses; + struct smb2_sess_setup_req *req; + struct TCP_Server_Info *server = ses->server; + + rc = small_smb2_init(SMB2_SESSION_SETUP, NULL, (void **) &req); + if (rc) + return rc; + + req->hdr.SessionId = 0; /* First session, not a reauthenticate */ + + /* if reconnect, we need to send previous sess id, otherwise it is 0 */ + req->PreviousSessionId = sess_data->previous_session; + + req->Flags = 0; /* MBZ */ + /* to enable echos and oplocks */ + req->hdr.CreditRequest = cpu_to_le16(3); + + /* only one of SMB2 signing flags may be set in SMB2 request */ + if (server->sign) + req->SecurityMode = SMB2_NEGOTIATE_SIGNING_REQUIRED; + else if (global_secflags & CIFSSEC_MAY_SIGN) /* one flag unlike MUST_ */ + req->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED; + else + req->SecurityMode = 0; + + req->Capabilities = 0; + req->Channel = 0; /* MBZ */ + + sess_data->iov[0].iov_base = (char *)req; + /* 4 for rfc1002 length field and 1 for pad */ + sess_data->iov[0].iov_len = get_rfc1002_length(req) + 4 - 1; + /* + * This variable will be used to clear the buffer + * allocated above in case of any error in the calling function. + */ + sess_data->buf0_type = CIFS_SMALL_BUFFER; + + return 0; +} + +static void +SMB2_sess_free_buffer(struct SMB2_sess_data *sess_data) +{ + free_rsp_buf(sess_data->buf0_type, sess_data->iov[0].iov_base); + sess_data->buf0_type = CIFS_NO_BUFFER; +} + +static int +SMB2_sess_sendreceive(struct SMB2_sess_data *sess_data) +{ + int rc; + struct smb2_sess_setup_req *req = sess_data->iov[0].iov_base; + + /* Testing shows that buffer offset must be at location of Buffer[0] */ + req->SecurityBufferOffset = + cpu_to_le16(sizeof(struct smb2_sess_setup_req) - + 1 /* pad */ - 4 /* rfc1001 len */); + req->SecurityBufferLength = cpu_to_le16(sess_data->iov[1].iov_len); + + inc_rfc1001_len(req, sess_data->iov[1].iov_len - 1 /* pad */); + + /* BB add code to build os and lm fields */ + + rc = SendReceive2(sess_data->xid, sess_data->ses, + sess_data->iov, 2, + &sess_data->buf0_type, + CIFS_LOG_ERROR | CIFS_NEG_OP); + + return rc; +} + +static int +SMB2_sess_establish_session(struct SMB2_sess_data *sess_data) +{ + int rc = 0; + struct cifs_ses *ses = sess_data->ses; + + mutex_lock(&ses->server->srv_mutex); + if (ses->server->sign && ses->server->ops->generate_signingkey) { + rc = ses->server->ops->generate_signingkey(ses); + kfree(ses->auth_key.response); + ses->auth_key.response = NULL; + if (rc) { + cifs_dbg(FYI, + "SMB3 session key generation failed\n"); + mutex_unlock(&ses->server->srv_mutex); + goto keygen_exit; + } + } + if (!ses->server->session_estab) { + ses->server->sequence_number = 0x2; + ses->server->session_estab = true; + } + mutex_unlock(&ses->server->srv_mutex); + + cifs_dbg(FYI, "SMB2/3 session established successfully\n"); + spin_lock(&GlobalMid_Lock); + ses->status = CifsGood; + ses->need_reconnect = false; + spin_unlock(&GlobalMid_Lock); + +keygen_exit: + if (!ses->server->sign) { + kfree(ses->auth_key.response); + ses->auth_key.response = NULL; + } + return rc; +} + +#ifdef CONFIG_CIFS_UPCALL +static void +SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) +{ + int rc; + struct cifs_ses *ses = sess_data->ses; + struct cifs_spnego_msg *msg; + struct key *spnego_key = NULL; + struct smb2_sess_setup_rsp *rsp = NULL; + + rc = SMB2_sess_alloc_buffer(sess_data); + if (rc) + goto out; + + spnego_key = cifs_get_spnego_key(ses); + if (IS_ERR(spnego_key)) { + rc = PTR_ERR(spnego_key); + spnego_key = NULL; + goto out; + } + + msg = spnego_key->payload.data[0]; + /* + * check version field to make sure that cifs.upcall is + * sending us a response in an expected form + */ + if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) { + cifs_dbg(VFS, + "bad cifs.upcall version. Expected %d got %d", + CIFS_SPNEGO_UPCALL_VERSION, msg->version); + rc = -EKEYREJECTED; + goto out_put_spnego_key; + } + + ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len, + GFP_KERNEL); + if (!ses->auth_key.response) { + cifs_dbg(VFS, + "Kerberos can't allocate (%u bytes) memory", + msg->sesskey_len); + rc = -ENOMEM; + goto out_put_spnego_key; + } + ses->auth_key.len = msg->sesskey_len; + + sess_data->iov[1].iov_base = msg->data + msg->sesskey_len; + sess_data->iov[1].iov_len = msg->secblob_len; + + rc = SMB2_sess_sendreceive(sess_data); + if (rc) + goto out_put_spnego_key; + + rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base; + ses->Suid = rsp->hdr.SessionId; + + ses->session_flags = le16_to_cpu(rsp->SessionFlags); + if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA) + cifs_dbg(VFS, "SMB3 encryption not supported yet\n"); + + rc = SMB2_sess_establish_session(sess_data); +out_put_spnego_key: + key_invalidate(spnego_key); + key_put(spnego_key); +out: + sess_data->result = rc; + sess_data->func = NULL; + SMB2_sess_free_buffer(sess_data); +} +#else +static void +SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) +{ + cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n"); + sess_data->result = -EOPNOTSUPP; + sess_data->func = NULL; +} +#endif + int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, const struct nls_table *nls_cp) @@ -605,11 +815,11 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, __le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */ struct TCP_Server_Info *server = ses->server; u16 blob_length = 0; - struct key *spnego_key = NULL; char *security_blob = NULL; unsigned char *ntlmssp_blob = NULL; bool use_spnego = false; /* else use raw ntlmssp */ u64 previous_session = ses->Suid; + struct SMB2_sess_data *sess_data; cifs_dbg(FYI, "Session Setup\n"); @@ -618,6 +828,20 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, return -EIO; } + sess_data = kzalloc(sizeof(struct SMB2_sess_data), GFP_KERNEL); + if (!sess_data) + return -ENOMEM; + sess_data->xid = xid; + sess_data->ses = ses; + sess_data->buf0_type = CIFS_NO_BUFFER; + sess_data->nls_cp = (struct nls_table *) nls_cp; + sess_data->previous_session = ses->Suid; + + if (ses->sectype == Kerberos) { + SMB2_auth_kerberos(sess_data); + goto out; + } + /* * If we are here due to reconnect, free per-smb session key * in case signing was required. @@ -670,47 +894,7 @@ ssetup_ntlmssp_authenticate: /* 4 for rfc1002 length field and 1 for pad */ iov[0].iov_len = get_rfc1002_length(req) + 4 - 1; - if (ses->sectype == Kerberos) { -#ifdef CONFIG_CIFS_UPCALL - struct cifs_spnego_msg *msg; - - spnego_key = cifs_get_spnego_key(ses); - if (IS_ERR(spnego_key)) { - rc = PTR_ERR(spnego_key); - spnego_key = NULL; - goto ssetup_exit; - } - - msg = spnego_key->payload.data[0]; - /* - * check version field to make sure that cifs.upcall is - * sending us a response in an expected form - */ - if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) { - cifs_dbg(VFS, - "bad cifs.upcall version. Expected %d got %d", - CIFS_SPNEGO_UPCALL_VERSION, msg->version); - rc = -EKEYREJECTED; - goto ssetup_exit; - } - ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len, - GFP_KERNEL); - if (!ses->auth_key.response) { - cifs_dbg(VFS, - "Kerberos can't allocate (%u bytes) memory", - msg->sesskey_len); - rc = -ENOMEM; - goto ssetup_exit; - } - ses->auth_key.len = msg->sesskey_len; - blob_length = msg->secblob_len; - iov[1].iov_base = msg->data + msg->sesskey_len; - iov[1].iov_len = blob_length; -#else - rc = -EOPNOTSUPP; - goto ssetup_exit; -#endif /* CONFIG_CIFS_UPCALL */ - } else if (phase == NtLmNegotiate) { /* if not krb5 must be ntlmssp */ + if (phase == NtLmNegotiate) { ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE), GFP_KERNEL); if (ntlmssp_blob == NULL) { @@ -853,12 +1037,12 @@ keygen_exit: kfree(ses->auth_key.response); ses->auth_key.response = NULL; } - if (spnego_key) { - key_invalidate(spnego_key); - key_put(spnego_key); - } kfree(ses->ntlmssp); + return rc; +out: + rc = sess_data->result; + kfree(sess_data); return rc; } From 166cea4dc3a4f66f020cfb9286225ecd228ab61d Mon Sep 17 00:00:00 2001 From: Sachin Prabhu Date: Fri, 7 Oct 2016 19:11:22 +0100 Subject: [PATCH 17/21] SMB2: Separate RawNTLMSSP authentication from SMB2_sess_setup We split the rawntlmssp authentication into negotiate and authencate parts. We also clean up the code and add helpers. Signed-off-by: Sachin Prabhu Signed-off-by: Steve French Reviewed-by: Pavel Shilovsky --- fs/cifs/smb2pdu.c | 391 +++++++++++++++++++++------------------------- 1 file changed, 177 insertions(+), 214 deletions(-) diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 386b512189b2..5ca5ea4668a1 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -803,22 +803,182 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) } #endif +static void +SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data); + +static void +SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data) +{ + int rc; + struct cifs_ses *ses = sess_data->ses; + struct smb2_sess_setup_rsp *rsp = NULL; + char *ntlmssp_blob = NULL; + bool use_spnego = false; /* else use raw ntlmssp */ + u16 blob_length = 0; + + /* + * If memory allocation is successful, caller of this function + * frees it. + */ + ses->ntlmssp = kmalloc(sizeof(struct ntlmssp_auth), GFP_KERNEL); + if (!ses->ntlmssp) { + rc = -ENOMEM; + goto out_err; + } + ses->ntlmssp->sesskey_per_smbsess = true; + + rc = SMB2_sess_alloc_buffer(sess_data); + if (rc) + goto out_err; + + ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE), + GFP_KERNEL); + if (ntlmssp_blob == NULL) { + rc = -ENOMEM; + goto out; + } + + build_ntlmssp_negotiate_blob(ntlmssp_blob, ses); + if (use_spnego) { + /* BB eventually need to add this */ + cifs_dbg(VFS, "spnego not supported for SMB2 yet\n"); + rc = -EOPNOTSUPP; + goto out; + } else { + blob_length = sizeof(struct _NEGOTIATE_MESSAGE); + /* with raw NTLMSSP we don't encapsulate in SPNEGO */ + } + sess_data->iov[1].iov_base = ntlmssp_blob; + sess_data->iov[1].iov_len = blob_length; + + rc = SMB2_sess_sendreceive(sess_data); + rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base; + + /* If true, rc here is expected and not an error */ + if (sess_data->buf0_type != CIFS_NO_BUFFER && + rsp->hdr.Status == STATUS_MORE_PROCESSING_REQUIRED) + rc = 0; + + if (rc) + goto out; + + if (offsetof(struct smb2_sess_setup_rsp, Buffer) - 4 != + le16_to_cpu(rsp->SecurityBufferOffset)) { + cifs_dbg(VFS, "Invalid security buffer offset %d\n", + le16_to_cpu(rsp->SecurityBufferOffset)); + rc = -EIO; + goto out; + } + rc = decode_ntlmssp_challenge(rsp->Buffer, + le16_to_cpu(rsp->SecurityBufferLength), ses); + if (rc) + goto out; + + cifs_dbg(FYI, "rawntlmssp session setup challenge phase\n"); + + + ses->Suid = rsp->hdr.SessionId; + ses->session_flags = le16_to_cpu(rsp->SessionFlags); + if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA) + cifs_dbg(VFS, "SMB3 encryption not supported yet\n"); + +out: + kfree(ntlmssp_blob); + SMB2_sess_free_buffer(sess_data); + if (!rc) { + sess_data->result = 0; + sess_data->func = SMB2_sess_auth_rawntlmssp_authenticate; + return; + } +out_err: + kfree(ses->ntlmssp); + ses->ntlmssp = NULL; + sess_data->result = rc; + sess_data->func = NULL; +} + +static void +SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data) +{ + int rc; + struct cifs_ses *ses = sess_data->ses; + struct smb2_sess_setup_req *req; + struct smb2_sess_setup_rsp *rsp = NULL; + unsigned char *ntlmssp_blob = NULL; + bool use_spnego = false; /* else use raw ntlmssp */ + u16 blob_length = 0; + + rc = SMB2_sess_alloc_buffer(sess_data); + if (rc) + goto out; + + req = (struct smb2_sess_setup_req *) sess_data->iov[0].iov_base; + req->hdr.SessionId = ses->Suid; + + rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length, ses, + sess_data->nls_cp); + if (rc) { + cifs_dbg(FYI, "build_ntlmssp_auth_blob failed %d\n", rc); + goto out; + } + + if (use_spnego) { + /* BB eventually need to add this */ + cifs_dbg(VFS, "spnego not supported for SMB2 yet\n"); + rc = -EOPNOTSUPP; + goto out; + } + sess_data->iov[1].iov_base = ntlmssp_blob; + sess_data->iov[1].iov_len = blob_length; + + rc = SMB2_sess_sendreceive(sess_data); + if (rc) + goto out; + + rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base; + + ses->Suid = rsp->hdr.SessionId; + ses->session_flags = le16_to_cpu(rsp->SessionFlags); + if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA) + cifs_dbg(VFS, "SMB3 encryption not supported yet\n"); + + rc = SMB2_sess_establish_session(sess_data); +out: + kfree(ntlmssp_blob); + SMB2_sess_free_buffer(sess_data); + kfree(ses->ntlmssp); + ses->ntlmssp = NULL; + sess_data->result = rc; + sess_data->func = NULL; +} + +static int +SMB2_select_sec(struct cifs_ses *ses, struct SMB2_sess_data *sess_data) +{ + if (ses->sectype != Kerberos && ses->sectype != RawNTLMSSP) + ses->sectype = RawNTLMSSP; + + switch (ses->sectype) { + case Kerberos: + sess_data->func = SMB2_auth_kerberos; + break; + case RawNTLMSSP: + sess_data->func = SMB2_sess_auth_rawntlmssp_negotiate; + break; + default: + cifs_dbg(VFS, "secType %d not supported!\n", ses->sectype); + return -EOPNOTSUPP; + } + + return 0; +} + int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, const struct nls_table *nls_cp) { - struct smb2_sess_setup_req *req; - struct smb2_sess_setup_rsp *rsp = NULL; - struct kvec iov[2]; int rc = 0; - int resp_buftype = CIFS_NO_BUFFER; - __le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */ struct TCP_Server_Info *server = ses->server; - u16 blob_length = 0; - char *security_blob = NULL; - unsigned char *ntlmssp_blob = NULL; - bool use_spnego = false; /* else use raw ntlmssp */ - u64 previous_session = ses->Suid; struct SMB2_sess_data *sess_data; cifs_dbg(FYI, "Session Setup\n"); @@ -831,217 +991,20 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, sess_data = kzalloc(sizeof(struct SMB2_sess_data), GFP_KERNEL); if (!sess_data) return -ENOMEM; + + rc = SMB2_select_sec(ses, sess_data); + if (rc) + goto out; sess_data->xid = xid; sess_data->ses = ses; sess_data->buf0_type = CIFS_NO_BUFFER; sess_data->nls_cp = (struct nls_table *) nls_cp; - sess_data->previous_session = ses->Suid; - if (ses->sectype == Kerberos) { - SMB2_auth_kerberos(sess_data); - goto out; - } + while (sess_data->func) + sess_data->func(sess_data); - /* - * If we are here due to reconnect, free per-smb session key - * in case signing was required. - */ - kfree(ses->auth_key.response); - ses->auth_key.response = NULL; - - /* - * If memory allocation is successful, caller of this function - * frees it. - */ - ses->ntlmssp = kmalloc(sizeof(struct ntlmssp_auth), GFP_KERNEL); - if (!ses->ntlmssp) - return -ENOMEM; - ses->ntlmssp->sesskey_per_smbsess = true; - - /* FIXME: allow for other auth types besides NTLMSSP (e.g. krb5) */ - if (ses->sectype != Kerberos && ses->sectype != RawNTLMSSP) - ses->sectype = RawNTLMSSP; - -ssetup_ntlmssp_authenticate: - if (phase == NtLmChallenge) - phase = NtLmAuthenticate; /* if ntlmssp, now final phase */ - - rc = small_smb2_init(SMB2_SESSION_SETUP, NULL, (void **) &req); - if (rc) - return rc; - - req->hdr.SessionId = 0; /* First session, not a reauthenticate */ - - /* if reconnect, we need to send previous sess id, otherwise it is 0 */ - req->PreviousSessionId = previous_session; - - req->Flags = 0; /* MBZ */ - /* to enable echos and oplocks */ - req->hdr.CreditRequest = cpu_to_le16(3); - - /* only one of SMB2 signing flags may be set in SMB2 request */ - if (server->sign) - req->SecurityMode = SMB2_NEGOTIATE_SIGNING_REQUIRED; - else if (global_secflags & CIFSSEC_MAY_SIGN) /* one flag unlike MUST_ */ - req->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED; - else - req->SecurityMode = 0; - - req->Capabilities = 0; - req->Channel = 0; /* MBZ */ - - iov[0].iov_base = (char *)req; - /* 4 for rfc1002 length field and 1 for pad */ - iov[0].iov_len = get_rfc1002_length(req) + 4 - 1; - - if (phase == NtLmNegotiate) { - ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE), - GFP_KERNEL); - if (ntlmssp_blob == NULL) { - rc = -ENOMEM; - goto ssetup_exit; - } - build_ntlmssp_negotiate_blob(ntlmssp_blob, ses); - if (use_spnego) { - /* blob_length = build_spnego_ntlmssp_blob( - &security_blob, - sizeof(struct _NEGOTIATE_MESSAGE), - ntlmssp_blob); */ - /* BB eventually need to add this */ - cifs_dbg(VFS, "spnego not supported for SMB2 yet\n"); - rc = -EOPNOTSUPP; - kfree(ntlmssp_blob); - goto ssetup_exit; - } else { - blob_length = sizeof(struct _NEGOTIATE_MESSAGE); - /* with raw NTLMSSP we don't encapsulate in SPNEGO */ - security_blob = ntlmssp_blob; - } - iov[1].iov_base = security_blob; - iov[1].iov_len = blob_length; - } else if (phase == NtLmAuthenticate) { - req->hdr.SessionId = ses->Suid; - rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length, ses, - nls_cp); - if (rc) { - cifs_dbg(FYI, "build_ntlmssp_auth_blob failed %d\n", - rc); - goto ssetup_exit; /* BB double check error handling */ - } - if (use_spnego) { - /* blob_length = build_spnego_ntlmssp_blob( - &security_blob, - blob_length, - ntlmssp_blob); */ - cifs_dbg(VFS, "spnego not supported for SMB2 yet\n"); - rc = -EOPNOTSUPP; - kfree(ntlmssp_blob); - goto ssetup_exit; - } else { - security_blob = ntlmssp_blob; - } - iov[1].iov_base = security_blob; - iov[1].iov_len = blob_length; - } else { - cifs_dbg(VFS, "illegal ntlmssp phase\n"); - rc = -EIO; - goto ssetup_exit; - } - - /* Testing shows that buffer offset must be at location of Buffer[0] */ - req->SecurityBufferOffset = - cpu_to_le16(sizeof(struct smb2_sess_setup_req) - - 1 /* pad */ - 4 /* rfc1001 len */); - req->SecurityBufferLength = cpu_to_le16(blob_length); - - inc_rfc1001_len(req, blob_length - 1 /* pad */); - - /* BB add code to build os and lm fields */ - - rc = SendReceive2(xid, ses, iov, 2, &resp_buftype, - CIFS_LOG_ERROR | CIFS_NEG_OP); - - kfree(security_blob); - rsp = (struct smb2_sess_setup_rsp *)iov[0].iov_base; - ses->Suid = rsp->hdr.SessionId; - if (resp_buftype != CIFS_NO_BUFFER && - rsp->hdr.Status == STATUS_MORE_PROCESSING_REQUIRED) { - if (phase != NtLmNegotiate) { - cifs_dbg(VFS, "Unexpected more processing error\n"); - goto ssetup_exit; - } - if (offsetof(struct smb2_sess_setup_rsp, Buffer) - 4 != - le16_to_cpu(rsp->SecurityBufferOffset)) { - cifs_dbg(VFS, "Invalid security buffer offset %d\n", - le16_to_cpu(rsp->SecurityBufferOffset)); - rc = -EIO; - goto ssetup_exit; - } - - /* NTLMSSP Negotiate sent now processing challenge (response) */ - phase = NtLmChallenge; /* process ntlmssp challenge */ - rc = 0; /* MORE_PROCESSING is not an error here but expected */ - rc = decode_ntlmssp_challenge(rsp->Buffer, - le16_to_cpu(rsp->SecurityBufferLength), ses); - } - - /* - * BB eventually add code for SPNEGO decoding of NtlmChallenge blob, - * but at least the raw NTLMSSP case works. - */ - /* - * No tcon so can't do - * cifs_stats_inc(&tcon->stats.smb2_stats.smb2_com_fail[SMB2...]); - */ - if (rc != 0) - goto ssetup_exit; - - ses->session_flags = le16_to_cpu(rsp->SessionFlags); - if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA) - cifs_dbg(VFS, "SMB3 encryption not supported yet\n"); -ssetup_exit: - free_rsp_buf(resp_buftype, rsp); - - /* if ntlmssp, and negotiate succeeded, proceed to authenticate phase */ - if ((phase == NtLmChallenge) && (rc == 0)) - goto ssetup_ntlmssp_authenticate; - - if (!rc) { - mutex_lock(&server->srv_mutex); - if (server->sign && server->ops->generate_signingkey) { - rc = server->ops->generate_signingkey(ses); - kfree(ses->auth_key.response); - ses->auth_key.response = NULL; - if (rc) { - cifs_dbg(FYI, - "SMB3 session key generation failed\n"); - mutex_unlock(&server->srv_mutex); - goto keygen_exit; - } - } - if (!server->session_estab) { - server->sequence_number = 0x2; - server->session_estab = true; - } - mutex_unlock(&server->srv_mutex); - - cifs_dbg(FYI, "SMB2/3 session established successfully\n"); - spin_lock(&GlobalMid_Lock); - ses->status = CifsGood; - ses->need_reconnect = false; - spin_unlock(&GlobalMid_Lock); - } - -keygen_exit: - if (!server->sign) { - kfree(ses->auth_key.response); - ses->auth_key.response = NULL; - } - kfree(ses->ntlmssp); - - return rc; -out: rc = sess_data->result; +out: kfree(sess_data); return rc; } From f2cca6a7c99fdeadacd0bdc37d825c4bc2b03653 Mon Sep 17 00:00:00 2001 From: Pavel Shilovsky Date: Fri, 7 Oct 2016 17:26:36 -0700 Subject: [PATCH 18/21] CIFS: Fix persistent handles re-opening on reconnect openFileList of tcon can be changed while cifs_reopen_file() is called that can lead to an unexpected behavior when we return to the loop. Fix this by introducing a temp list for keeping all file handles that need to be reopen. Signed-off-by: Pavel Shilovsky Signed-off-by: Steve French --- fs/cifs/cifsglob.h | 1 + fs/cifs/file.c | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index d9a17288baf3..0c828d3c48c3 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1065,6 +1065,7 @@ struct cifsFileInfo { kuid_t uid; /* allows finding which FileInfo structure */ __u32 pid; /* process id who opened file */ struct cifs_fid fid; /* file id from remote */ + struct list_head rlist; /* reconnect list */ /* BB add lock scope info here if needed */ ; /* lock scope id (0 if none) */ struct dentry *dentry; diff --git a/fs/cifs/file.c b/fs/cifs/file.c index 8f27c8a74384..07c14f9217cb 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -763,19 +763,31 @@ int cifs_close(struct inode *inode, struct file *file) void cifs_reopen_persistent_handles(struct cifs_tcon *tcon) { - struct cifsFileInfo *open_file = NULL; + struct cifsFileInfo *open_file; struct list_head *tmp; struct list_head *tmp1; + struct list_head tmp_list; + + cifs_dbg(FYI, "Reopen persistent handles"); + INIT_LIST_HEAD(&tmp_list); /* list all files open on tree connection, reopen resilient handles */ spin_lock(&tcon->open_file_lock); - list_for_each_safe(tmp, tmp1, &tcon->openFileList) { + list_for_each(tmp, &tcon->openFileList) { open_file = list_entry(tmp, struct cifsFileInfo, tlist); - spin_unlock(&tcon->open_file_lock); - cifs_reopen_file(open_file, false /* do not flush */); - spin_lock(&tcon->open_file_lock); + if (!open_file->invalidHandle) + continue; + cifsFileInfo_get(open_file); + list_add_tail(&open_file->rlist, &tmp_list); } spin_unlock(&tcon->open_file_lock); + + list_for_each_safe(tmp, tmp1, &tmp_list) { + open_file = list_entry(tmp, struct cifsFileInfo, rlist); + cifs_reopen_file(open_file, false /* do not flush */); + list_del_init(&open_file->rlist); + cifsFileInfo_put(open_file); + } } int cifs_closedir(struct inode *inode, struct file *file) From de74025052ef63852d80a444ea19f2bdd7bec63f Mon Sep 17 00:00:00 2001 From: Pavel Shilovsky Date: Tue, 11 Oct 2016 15:34:07 -0700 Subject: [PATCH 19/21] CIFS: Reset read oplock to NONE if we have mandatory locks after reopen We are already doing the same thing for an ordinary open case: we can't keep read oplock on a file if we have mandatory byte-range locks because pagereading can conflict with these locks on a server. Fix it by setting oplock level to NONE. Signed-off-by: Pavel Shilovsky Signed-off-by: Steve French --- fs/cifs/file.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/cifs/file.c b/fs/cifs/file.c index 07c14f9217cb..7f5f6176c6f1 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -739,6 +739,15 @@ reopen_success: * to the server to get the new inode info. */ + /* + * If the server returned a read oplock and we have mandatory brlocks, + * set oplock level to None. + */ + if (server->ops->is_read_op(oplock) && cifs_has_mand_locks(cinode)) { + cifs_dbg(FYI, "Reset oplock val from read to None due to mand locks\n"); + oplock = 0; + } + server->ops->set_fid(cfile, &cfile->fid, oplock); if (oparms.reconnect) cifs_relock_file(cfile); From 9593265531fa66177a0547e63abd99907ec0a687 Mon Sep 17 00:00:00 2001 From: Steve French Date: Fri, 23 Sep 2016 01:36:34 -0500 Subject: [PATCH 20/21] CIFS: Add new mount option to set owner uid and gid from special sids in acl Add "idsfromsid" mount option to indicate to cifs.ko that it should try to retrieve the uid and gid owner fields from special sids in the ACL if present. This first patch just adds the parsing for the mount option. Signed-off-by: Steve French Reviewed-by: Shirish Pargaonkar Reviewed-by: Pavel Shilovsky --- fs/cifs/cifs_fs_sb.h | 1 + fs/cifs/cifsfs.c | 2 ++ fs/cifs/cifsglob.h | 1 + fs/cifs/connect.c | 8 +++++++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h index 1418daa03d95..07ed81cf1552 100644 --- a/fs/cifs/cifs_fs_sb.h +++ b/fs/cifs/cifs_fs_sb.h @@ -49,6 +49,7 @@ #define CIFS_MOUNT_USE_PREFIX_PATH 0x1000000 /* make subpath with unaccessible * root mountable */ +#define CIFS_MOUNT_UID_FROM_ACL 0x2000000 /* try to get UID via special SID */ struct cifs_sb_info { struct rb_root tlink_tree; diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index f963c880c72d..15261ba464c5 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -469,6 +469,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root) seq_puts(s, ",posixpaths"); if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID) seq_puts(s, ",setuids"); + if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UID_FROM_ACL) + seq_puts(s, ",idsfromsid"); if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM) seq_puts(s, ",serverino"); if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_RWPIDFORWARD) diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 0c828d3c48c3..1f17f6bd7a60 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -478,6 +478,7 @@ struct smb_vol { bool retry:1; bool intr:1; bool setuids:1; + bool setuidfromacl:1; bool override_uid:1; bool override_gid:1; bool dynperm:1; diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 657369db76df..aab5227979e2 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -75,7 +75,7 @@ enum { Opt_noposixpaths, Opt_nounix, Opt_nocase, Opt_brl, Opt_nobrl, - Opt_forcemandatorylock, Opt_setuids, + Opt_forcemandatorylock, Opt_setuidfromacl, Opt_setuids, Opt_nosetuids, Opt_dynperm, Opt_nodynperm, Opt_nohard, Opt_nosoft, Opt_nointr, Opt_intr, @@ -147,6 +147,7 @@ static const match_table_t cifs_mount_option_tokens = { { Opt_forcemandatorylock, "forcemand" }, { Opt_setuids, "setuids" }, { Opt_nosetuids, "nosetuids" }, + { Opt_setuidfromacl, "idsfromsid" }, { Opt_dynperm, "dynperm" }, { Opt_nodynperm, "nodynperm" }, { Opt_nohard, "nohard" }, @@ -1376,6 +1377,9 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, case Opt_nosetuids: vol->setuids = 0; break; + case Opt_setuidfromacl: + vol->setuidfromacl = 1; + break; case Opt_dynperm: vol->dynperm = true; break; @@ -3279,6 +3283,8 @@ int cifs_setup_cifs_sb(struct smb_vol *pvolume_info, cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_PERM; if (pvolume_info->setuids) cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_SET_UID; + if (pvolume_info->setuidfromacl) + cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_UID_FROM_ACL; if (pvolume_info->server_ino) cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_SERVER_INUM; if (pvolume_info->remap) From 3514de3fd5fab518520e393152d3f4e3d0441d8d Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 13 Oct 2016 19:06:23 -0500 Subject: [PATCH 21/21] CIFS: Retrieve uid and gid from special sid if enabled New mount option "idsfromsid" indicates to cifs.ko that it should try to retrieve the uid and gid owner fields from special sids. This patch adds the code to parse the owner sids in the ACL to see if they match, and if so populate the uid and/or gid from them. This is faster than upcalling for them and asking winbind, and is a fairly common case, and is also helpful when cifs.upcall and idmapping is not configured. Signed-off-by: Steve French Reviewed-by: Shirish Pargaonkar Reviewed-by: Jeff Layton Reviewed-by: Pavel Shilovsky --- fs/cifs/cifsacl.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/fs/cifs/cifsacl.c b/fs/cifs/cifsacl.c index 71e8a56e9479..15bac390dff9 100644 --- a/fs/cifs/cifsacl.c +++ b/fs/cifs/cifsacl.c @@ -42,6 +42,35 @@ static const struct cifs_sid sid_authusers = { /* group users */ static const struct cifs_sid sid_user = {1, 2 , {0, 0, 0, 0, 0, 5}, {} }; +/* S-1-22-1 Unmapped Unix users */ +static const struct cifs_sid sid_unix_users = {1, 1, {0, 0, 0, 0, 0, 22}, + {cpu_to_le32(1), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; + +/* S-1-22-2 Unmapped Unix groups */ +static const struct cifs_sid sid_unix_groups = { 1, 1, {0, 0, 0, 0, 0, 22}, + {cpu_to_le32(2), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; + +/* + * See http://technet.microsoft.com/en-us/library/hh509017(v=ws.10).aspx + */ + +/* S-1-5-88 MS NFS and Apple style UID/GID/mode */ + +/* S-1-5-88-1 Unix uid */ +static const struct cifs_sid sid_unix_NFS_users = { 1, 2, {0, 0, 0, 0, 0, 5}, + {cpu_to_le32(88), + cpu_to_le32(1), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; + +/* S-1-5-88-2 Unix gid */ +static const struct cifs_sid sid_unix_NFS_groups = { 1, 2, {0, 0, 0, 0, 0, 5}, + {cpu_to_le32(88), + cpu_to_le32(2), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; + +/* S-1-5-88-3 Unix mode */ +static const struct cifs_sid sid_unix_NFS_mode = { 1, 2, {0, 0, 0, 0, 0, 5}, + {cpu_to_le32(88), + cpu_to_le32(3), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; + static const struct cred *root_cred; static int @@ -183,6 +212,62 @@ compare_sids(const struct cifs_sid *ctsid, const struct cifs_sid *cwsid) return 0; /* sids compare/match */ } +static bool +is_well_known_sid(const struct cifs_sid *psid, uint32_t *puid, bool is_group) +{ + int i; + int num_subauth; + const struct cifs_sid *pwell_known_sid; + + if (!psid || (puid == NULL)) + return false; + + num_subauth = psid->num_subauth; + + /* check if Mac (or Windows NFS) vs. Samba format for Unix owner SID */ + if (num_subauth == 2) { + if (is_group) + pwell_known_sid = &sid_unix_groups; + else + pwell_known_sid = &sid_unix_users; + } else if (num_subauth == 3) { + if (is_group) + pwell_known_sid = &sid_unix_NFS_groups; + else + pwell_known_sid = &sid_unix_NFS_users; + } else + return false; + + /* compare the revision */ + if (psid->revision != pwell_known_sid->revision) + return false; + + /* compare all of the six auth values */ + for (i = 0; i < NUM_AUTHS; ++i) { + if (psid->authority[i] != pwell_known_sid->authority[i]) { + cifs_dbg(FYI, "auth %d did not match\n", i); + return false; + } + } + + if (num_subauth == 2) { + if (psid->sub_auth[0] != pwell_known_sid->sub_auth[0]) + return false; + + *puid = le32_to_cpu(psid->sub_auth[1]); + } else /* 3 subauths, ie Windows/Mac style */ { + *puid = le32_to_cpu(psid->sub_auth[0]); + if ((psid->sub_auth[0] != pwell_known_sid->sub_auth[0]) || + (psid->sub_auth[1] != pwell_known_sid->sub_auth[1])) + return false; + + *puid = le32_to_cpu(psid->sub_auth[2]); + } + + cifs_dbg(FYI, "Unix UID %d returned from SID\n", *puid); + return true; /* well known sid found, uid returned */ +} + static void cifs_copy_sid(struct cifs_sid *dst, const struct cifs_sid *src) { @@ -276,6 +361,43 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid, return -EIO; } + if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UID_FROM_ACL) { + uint32_t unix_id; + bool is_group; + + if (sidtype != SIDOWNER) + is_group = true; + else + is_group = false; + + if (is_well_known_sid(psid, &unix_id, is_group) == false) + goto try_upcall_to_get_id; + + if (is_group) { + kgid_t gid; + gid_t id; + + id = (gid_t)unix_id; + gid = make_kgid(&init_user_ns, id); + if (gid_valid(gid)) { + fgid = gid; + goto got_valid_id; + } + } else { + kuid_t uid; + uid_t id; + + id = (uid_t)unix_id; + uid = make_kuid(&init_user_ns, id); + if (uid_valid(uid)) { + fuid = uid; + goto got_valid_id; + } + } + /* If unable to find uid/gid easily from SID try via upcall */ + } + +try_upcall_to_get_id: sidstr = sid_to_key_str(psid, sidtype); if (!sidstr) return -ENOMEM; @@ -329,6 +451,7 @@ out_revert_creds: * Note that we return 0 here unconditionally. If the mapping * fails then we just fall back to using the mnt_uid/mnt_gid. */ +got_valid_id: if (sidtype == SIDOWNER) fattr->cf_uid = fuid; else