diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2022-01-17 09:53:21 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2022-01-17 09:53:21 +0200 |
commit | 0c947b893d69231a9add855939da7c66237ab44f (patch) | |
tree | 8a570c067894ba4d483994c6e7527cd704cb6c00 | |
parent | a6097180d884ddab769fb25588ea8598589c218c (diff) | |
parent | 9bbf8662a27b56358366027d1a77c0676f85b222 (diff) |
Merge tag '5.17-rc-part1-smb3-fixes' of git://git.samba.org/sfrench/cifs-2.6
Pull cifs updates from Steve French:
- multichannel patches mostly related to improving reconnect behavior
- minor cleanup patches
* tag '5.17-rc-part1-smb3-fixes' of git://git.samba.org/sfrench/cifs-2.6:
cifs: fix FILE_BOTH_DIRECTORY_INFO definition
cifs: move superblock magic defitions to magic.h
cifs: Fix smb311_update_preauth_hash() kernel-doc comment
cifs: avoid race during socket reconnect between send and recv
cifs: maintain a state machine for tcp/smb/tcon sessions
cifs: fix hang on cifs_get_next_mid()
cifs: take cifs_tcp_ses_lock for status checks
cifs: reconnect only the connection and not smb session where possible
cifs: add WARN_ON for when chan_count goes below minimum
cifs: adjust DebugData to use chans_need_reconnect for conn status
cifs: use the chans_need_reconnect bitmap for reconnect status
cifs: track individual channel status using chans_need_reconnect
cifs: remove redundant assignment to pointer p
-rw-r--r-- | fs/cifs/cifs_debug.c | 8 | ||||
-rw-r--r-- | fs/cifs/cifs_spnego.c | 4 | ||||
-rw-r--r-- | fs/cifs/cifs_spnego.h | 3 | ||||
-rw-r--r-- | fs/cifs/cifs_swn.c | 4 | ||||
-rw-r--r-- | fs/cifs/cifsencrypt.c | 6 | ||||
-rw-r--r-- | fs/cifs/cifsfs.c | 5 | ||||
-rw-r--r-- | fs/cifs/cifsglob.h | 79 | ||||
-rw-r--r-- | fs/cifs/cifspdu.h | 2 | ||||
-rw-r--r-- | fs/cifs/cifsproto.h | 31 | ||||
-rw-r--r-- | fs/cifs/cifssmb.c | 94 | ||||
-rw-r--r-- | fs/cifs/connect.c | 230 | ||||
-rw-r--r-- | fs/cifs/netmisc.c | 4 | ||||
-rw-r--r-- | fs/cifs/ntlmssp.h | 2 | ||||
-rw-r--r-- | fs/cifs/sess.c | 183 | ||||
-rw-r--r-- | fs/cifs/smb1ops.c | 24 | ||||
-rw-r--r-- | fs/cifs/smb2glob.h | 2 | ||||
-rw-r--r-- | fs/cifs/smb2misc.c | 5 | ||||
-rw-r--r-- | fs/cifs/smb2ops.c | 34 | ||||
-rw-r--r-- | fs/cifs/smb2pdu.c | 179 | ||||
-rw-r--r-- | fs/cifs/smb2proto.h | 6 | ||||
-rw-r--r-- | fs/cifs/smb2transport.c | 61 | ||||
-rw-r--r-- | fs/cifs/transport.c | 78 | ||||
-rw-r--r-- | include/uapi/linux/magic.h | 4 |
23 files changed, 723 insertions, 325 deletions
diff --git a/fs/cifs/cifs_debug.c b/fs/cifs/cifs_debug.c index d282caf9f037..ea00e1a91250 100644 --- a/fs/cifs/cifs_debug.c +++ b/fs/cifs/cifs_debug.c @@ -416,11 +416,17 @@ skip_rdma: from_kuid(&init_user_ns, ses->cred_uid)); spin_lock(&ses->chan_lock); + if (CIFS_CHAN_NEEDS_RECONNECT(ses, 0)) + seq_puts(m, "\tPrimary channel: DISCONNECTED "); + if (ses->chan_count > 1) { seq_printf(m, "\n\n\tExtra Channels: %zu ", ses->chan_count-1); - for (j = 1; j < ses->chan_count; j++) + for (j = 1; j < ses->chan_count; j++) { cifs_dump_channel(m, j, &ses->chans[j]); + if (CIFS_CHAN_NEEDS_RECONNECT(ses, j)) + seq_puts(m, "\tDISCONNECTED "); + } } spin_unlock(&ses->chan_lock); diff --git a/fs/cifs/cifs_spnego.c b/fs/cifs/cifs_spnego.c index 353bd0dd7026..342717bf1dc2 100644 --- a/fs/cifs/cifs_spnego.c +++ b/fs/cifs/cifs_spnego.c @@ -84,9 +84,9 @@ struct key_type cifs_spnego_key_type = { /* get a key struct with a SPNEGO security blob, suitable for session setup */ struct key * -cifs_get_spnego_key(struct cifs_ses *sesInfo) +cifs_get_spnego_key(struct cifs_ses *sesInfo, + struct TCP_Server_Info *server) { - struct TCP_Server_Info *server = cifs_ses_server(sesInfo); struct sockaddr_in *sa = (struct sockaddr_in *) &server->dstaddr; struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *) &server->dstaddr; char *description, *dp; diff --git a/fs/cifs/cifs_spnego.h b/fs/cifs/cifs_spnego.h index e6a0451877d4..7f102ffeb675 100644 --- a/fs/cifs/cifs_spnego.h +++ b/fs/cifs/cifs_spnego.h @@ -29,7 +29,8 @@ struct cifs_spnego_msg { #ifdef __KERNEL__ extern struct key_type cifs_spnego_key_type; -extern struct key *cifs_get_spnego_key(struct cifs_ses *sesInfo); +extern struct key *cifs_get_spnego_key(struct cifs_ses *sesInfo, + struct TCP_Server_Info *server); #endif /* KERNEL */ #endif /* _CIFS_SPNEGO_H */ diff --git a/fs/cifs/cifs_swn.c b/fs/cifs/cifs_swn.c index 23a1ed2fb769..8f386dd9939e 100644 --- a/fs/cifs/cifs_swn.c +++ b/fs/cifs/cifs_swn.c @@ -498,10 +498,10 @@ static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *a goto unlock; } - spin_lock(&GlobalMid_Lock); + spin_lock(&cifs_tcp_ses_lock); if (tcon->ses->server->tcpStatus != CifsExiting) tcon->ses->server->tcpStatus = CifsNeedReconnect; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); unlock: mutex_unlock(&tcon->ses->server->srv_mutex); diff --git a/fs/cifs/cifsencrypt.c b/fs/cifs/cifsencrypt.c index d118282071b3..0912d8bbbac1 100644 --- a/fs/cifs/cifsencrypt.c +++ b/fs/cifs/cifsencrypt.c @@ -141,9 +141,13 @@ int cifs_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server, if ((cifs_pdu == NULL) || (server == NULL)) return -EINVAL; + spin_lock(&cifs_tcp_ses_lock); if (!(cifs_pdu->Flags2 & SMBFLG2_SECURITY_SIGNATURE) || - server->tcpStatus == CifsNeedNegotiate) + server->tcpStatus == CifsNeedNegotiate) { + spin_unlock(&cifs_tcp_ses_lock); return rc; + } + spin_unlock(&cifs_tcp_ses_lock); if (!server->session_estab) { memcpy(cifs_pdu->Signature.SecuritySignature, "BSRSPYL", 8); diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index dca42aa87d30..36b2e0cb9736 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -26,6 +26,7 @@ #include <linux/random.h> #include <linux/uuid.h> #include <linux/xattr.h> +#include <uapi/linux/magic.h> #include <net/ipv6.h> #include "cifsfs.h" #include "cifspdu.h" @@ -202,7 +203,7 @@ cifs_read_super(struct super_block *sb) sb->s_time_max = ts.tv_sec; } - sb->s_magic = CIFS_MAGIC_NUMBER; + sb->s_magic = CIFS_SUPER_MAGIC; sb->s_op = &cifs_super_ops; sb->s_xattr = cifs_xattr_handlers; rc = super_setup_bdi(sb); @@ -773,7 +774,7 @@ cifs_get_root(struct smb3_fs_context *ctx, struct super_block *sb) sep = CIFS_DIR_SEP(cifs_sb); dentry = dget(sb->s_root); - p = s = full_path; + s = full_path; do { struct inode *dir = d_inode(dentry); diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index be74606724c7..f84978b76bb6 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -24,8 +24,6 @@ #include "../smbfs_common/smb2pdu.h" #include "smb2pdu.h" -#define CIFS_MAGIC_NUMBER 0xFF534D42 /* the first four bytes of SMB PDUs */ - #define SMB_PATH_MAX 260 #define CIFS_PORT 445 #define RFC1001_PORT 139 @@ -113,7 +111,13 @@ enum statusEnum { CifsGood, CifsExiting, CifsNeedReconnect, - CifsNeedNegotiate + CifsNeedNegotiate, + CifsInNegotiate, + CifsNeedSessSetup, + CifsInSessSetup, + CifsNeedTcon, + CifsInTcon, + CifsInFilesInvalidate }; enum securityEnum { @@ -263,13 +267,16 @@ struct smb_version_operations { /* check if we need to negotiate */ bool (*need_neg)(struct TCP_Server_Info *); /* negotiate to the server */ - int (*negotiate)(const unsigned int, struct cifs_ses *); + int (*negotiate)(const unsigned int xid, + struct cifs_ses *ses, + struct TCP_Server_Info *server); /* set negotiated write size */ unsigned int (*negotiate_wsize)(struct cifs_tcon *tcon, struct smb3_fs_context *ctx); /* set negotiated read size */ unsigned int (*negotiate_rsize)(struct cifs_tcon *tcon, struct smb3_fs_context *ctx); /* setup smb sessionn */ int (*sess_setup)(const unsigned int, struct cifs_ses *, + struct TCP_Server_Info *server, const struct nls_table *); /* close smb session */ int (*logoff)(const unsigned int, struct cifs_ses *); @@ -414,7 +421,8 @@ struct smb_version_operations { void (*set_lease_key)(struct inode *, struct cifs_fid *); /* generate new lease key */ void (*new_lease_key)(struct cifs_fid *); - int (*generate_signingkey)(struct cifs_ses *); + int (*generate_signingkey)(struct cifs_ses *ses, + struct TCP_Server_Info *server); int (*calc_signature)(struct smb_rqst *, struct TCP_Server_Info *, bool allocate_crypto); int (*set_integrity)(const unsigned int, struct cifs_tcon *tcon, @@ -582,7 +590,7 @@ struct TCP_Server_Info { char server_RFC1001_name[RFC1001_NAME_LEN_WITH_NULL]; struct smb_version_operations *ops; struct smb_version_values *vals; - /* updates to tcpStatus protected by GlobalMid_Lock */ + /* updates to tcpStatus protected by cifs_tcp_ses_lock */ enum statusEnum tcpStatus; /* what we think the status is */ char *hostname; /* hostname portion of UNC string */ struct socket *ssocket; @@ -920,7 +928,7 @@ struct cifs_ses { struct mutex session_mutex; struct TCP_Server_Info *server; /* pointer to server info */ int ses_count; /* reference counter */ - enum statusEnum status; /* updates protected by GlobalMid_Lock */ + enum statusEnum status; /* updates protected by cifs_tcp_ses_lock */ unsigned overrideSecFlg; /* if non-zero override global sec flags */ char *serverOS; /* name of operating system underlying server */ char *serverNOS; /* name of network operating system of server */ @@ -939,17 +947,13 @@ struct cifs_ses { struct ntlmssp_auth *ntlmssp; /* ciphertext, flags, server challenge */ enum securityEnum sectype; /* what security flavor was specified? */ bool sign; /* is signing required? */ - bool need_reconnect:1; /* connection reset, uid now invalid */ bool domainAuto:1; - bool binding:1; /* are we binding the session? */ __u16 session_flags; __u8 smb3signingkey[SMB3_SIGN_KEY_SIZE]; __u8 smb3encryptionkey[SMB3_ENC_DEC_KEY_SIZE]; __u8 smb3decryptionkey[SMB3_ENC_DEC_KEY_SIZE]; __u8 preauth_sha_hash[SMB2_PREAUTH_HASH_SIZE]; - __u8 binding_preauth_sha_hash[SMB2_PREAUTH_HASH_SIZE]; - /* * Network interfaces available on the server this session is * connected to. @@ -969,45 +973,34 @@ struct cifs_ses { spinlock_t chan_lock; /* ========= begin: protected by chan_lock ======== */ #define CIFS_MAX_CHANNELS 16 +#define CIFS_ALL_CHANNELS_SET(ses) \ + ((1UL << (ses)->chan_count) - 1) +#define CIFS_ALL_CHANS_NEED_RECONNECT(ses) \ + ((ses)->chans_need_reconnect == CIFS_ALL_CHANNELS_SET(ses)) +#define CIFS_SET_ALL_CHANS_NEED_RECONNECT(ses) \ + ((ses)->chans_need_reconnect = CIFS_ALL_CHANNELS_SET(ses)) +#define CIFS_CHAN_NEEDS_RECONNECT(ses, index) \ + test_bit((index), &(ses)->chans_need_reconnect) + struct cifs_chan chans[CIFS_MAX_CHANNELS]; - struct cifs_chan *binding_chan; size_t chan_count; size_t chan_max; atomic_t chan_seq; /* round robin state */ + + /* + * chans_need_reconnect is a bitmap indicating which of the channels + * under this smb session needs to be reconnected. + * If not multichannel session, only one bit will be used. + * + * We will ask for sess and tcon reconnection only if all the + * channels are marked for needing reconnection. This will + * enable the sessions on top to continue to live till any + * of the channels below are active. + */ + unsigned long chans_need_reconnect; /* ========= end: protected by chan_lock ======== */ }; -/* - * When binding a new channel, we need to access the channel which isn't fully - * established yet. - */ - -static inline -struct cifs_chan *cifs_ses_binding_channel(struct cifs_ses *ses) -{ - if (ses->binding) - return ses->binding_chan; - else - return NULL; -} - -/* - * Returns the server pointer of the session. When binding a new - * channel this returns the last channel which isn't fully established - * yet. - * - * This function should be use for negprot/sess.setup codepaths. For - * the other requests see cifs_pick_channel(). - */ -static inline -struct TCP_Server_Info *cifs_ses_server(struct cifs_ses *ses) -{ - if (ses->binding) - return ses->binding_chan->server; - else - return ses->server; -} - static inline bool cap_unix(struct cifs_ses *ses) { diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h index d2ff438fd31f..68b9a436af4b 100644 --- a/fs/cifs/cifspdu.h +++ b/fs/cifs/cifspdu.h @@ -2560,7 +2560,7 @@ typedef struct { __le32 EaSize; /* length of the xattrs */ __u8 ShortNameLength; __u8 Reserved; - __u8 ShortName[12]; + __u8 ShortName[24]; char FileName[1]; } __attribute__((packed)) FILE_BOTH_DIRECTORY_INFO; /* level 0x104 FFrsp data */ diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index 4f5a3e857df4..e0dc147e69a8 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -131,7 +131,8 @@ extern int SendReceiveBlockingLock(const unsigned int xid, struct smb_hdr *in_buf , struct smb_hdr *out_buf, int *bytes_returned); -extern int cifs_reconnect(struct TCP_Server_Info *server); +extern int cifs_reconnect(struct TCP_Server_Info *server, + bool mark_smb_session); 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 *); @@ -164,6 +165,7 @@ extern int small_smb_init_no_tc(const int smb_cmd, const int wct, extern enum securityEnum select_sectype(struct TCP_Server_Info *server, enum securityEnum requested); extern int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, + struct TCP_Server_Info *server, const struct nls_table *nls_cp); extern struct timespec64 cifs_NTtimeToUnix(__le64 utc_nanoseconds_since_1601); extern u64 cifs_UnixTimeToNT(struct timespec64); @@ -293,11 +295,15 @@ extern int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc); extern int cifs_negotiate_protocol(const unsigned int xid, - struct cifs_ses *ses); + struct cifs_ses *ses, + struct TCP_Server_Info *server); extern int cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, + struct TCP_Server_Info *server, struct nls_table *nls_info); extern int cifs_enable_signing(struct TCP_Server_Info *server, bool mnt_sign_required); -extern int CIFSSMBNegotiate(const unsigned int xid, struct cifs_ses *ses); +extern int CIFSSMBNegotiate(const unsigned int xid, + struct cifs_ses *ses, + struct TCP_Server_Info *server); extern int CIFSTCon(const unsigned int xid, struct cifs_ses *ses, const char *tree, struct cifs_tcon *tcon, @@ -504,8 +510,10 @@ extern int cifs_verify_signature(struct smb_rqst *rqst, extern int setup_ntlmv2_rsp(struct cifs_ses *, const struct nls_table *); extern void cifs_crypto_secmech_release(struct TCP_Server_Info *server); extern int calc_seckey(struct cifs_ses *); -extern int generate_smb30signingkey(struct cifs_ses *); -extern int generate_smb311signingkey(struct cifs_ses *); +extern int generate_smb30signingkey(struct cifs_ses *ses, + struct TCP_Server_Info *server); +extern int generate_smb311signingkey(struct cifs_ses *ses, + struct TCP_Server_Info *server); extern int CIFSSMBCopy(unsigned int xid, struct cifs_tcon *source_tcon, @@ -601,6 +609,19 @@ bool is_server_using_iface(struct TCP_Server_Info *server, bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface); void cifs_ses_mark_for_reconnect(struct cifs_ses *ses); +unsigned int +cifs_ses_get_chan_index(struct cifs_ses *ses, + struct TCP_Server_Info *server); +void +cifs_chan_set_need_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server); +void +cifs_chan_clear_need_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server); +bool +cifs_chan_needs_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server); + void extract_unc_hostname(const char *unc, const char **h, size_t *len); int copy_path_name(char *dst, const char *src); int smb2_parse_query_directory(struct cifs_tcon *tcon, struct kvec *rsp_iov, diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c index 243d17696f06..071e2f21a7db 100644 --- a/fs/cifs/cifssmb.c +++ b/fs/cifs/cifssmb.c @@ -73,6 +73,16 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon) struct list_head *tmp; struct list_head *tmp1; + /* only send once per connect */ + spin_lock(&cifs_tcp_ses_lock); + if (tcon->ses->status != CifsGood || + tcon->tidStatus != CifsNeedReconnect) { + spin_unlock(&cifs_tcp_ses_lock); + return; + } + tcon->tidStatus = CifsInFilesInvalidate; + spin_unlock(&cifs_tcp_ses_lock); + /* list all files open on tree connection and mark them invalid */ spin_lock(&tcon->open_file_lock); list_for_each_safe(tmp, tmp1, &tcon->openFileList) { @@ -89,6 +99,11 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon) memset(tcon->crfid.fid, 0, sizeof(struct cifs_fid)); mutex_unlock(&tcon->crfid.fid_mutex); + spin_lock(&cifs_tcp_ses_lock); + if (tcon->tidStatus == CifsInFilesInvalidate) + tcon->tidStatus = CifsNeedTcon; + spin_unlock(&cifs_tcp_ses_lock); + /* * BB Add call to invalidate_inodes(sb) for all superblocks mounted * to this tcon. @@ -120,15 +135,18 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) * only tree disconnect, open, and write, (and ulogoff which does not * have tcon) are allowed as we start force umount */ + spin_lock(&cifs_tcp_ses_lock); if (tcon->tidStatus == CifsExiting) { if (smb_command != SMB_COM_WRITE_ANDX && smb_command != SMB_COM_OPEN_ANDX && smb_command != SMB_COM_TREE_DISCONNECT) { + spin_unlock(&cifs_tcp_ses_lock); cifs_dbg(FYI, "can not send cmd %d while umounting\n", smb_command); return -ENODEV; } } + spin_unlock(&cifs_tcp_ses_lock); retries = server->nr_targets; @@ -148,8 +166,12 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) } /* are we still trying to reconnect? */ - if (server->tcpStatus != CifsNeedReconnect) + spin_lock(&cifs_tcp_ses_lock); + if (server->tcpStatus != CifsNeedReconnect) { + spin_unlock(&cifs_tcp_ses_lock); break; + } + spin_unlock(&cifs_tcp_ses_lock); if (retries && --retries) continue; @@ -166,31 +188,49 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) retries = server->nr_targets; } - if (!ses->need_reconnect && !tcon->need_reconnect) + spin_lock(&ses->chan_lock); + if (!cifs_chan_needs_reconnect(ses, server) && !tcon->need_reconnect) { + spin_unlock(&ses->chan_lock); return 0; + } + spin_unlock(&ses->chan_lock); nls_codepage = load_nls_default(); /* - * need to prevent multiple threads trying to simultaneously - * reconnect the same SMB session - */ - mutex_lock(&ses->session_mutex); - - /* * Recheck after acquire mutex. If another thread is negotiating * and the server never sends an answer the socket will be closed * and tcpStatus set to reconnect. */ + spin_lock(&cifs_tcp_ses_lock); if (server->tcpStatus == CifsNeedReconnect) { + spin_unlock(&cifs_tcp_ses_lock); rc = -EHOSTDOWN; - mutex_unlock(&ses->session_mutex); goto out; } + spin_unlock(&cifs_tcp_ses_lock); - rc = cifs_negotiate_protocol(0, ses); - if (rc == 0 && ses->need_reconnect) - rc = cifs_setup_session(0, ses, nls_codepage); + /* + * need to prevent multiple threads trying to simultaneously + * reconnect the same SMB session + */ + spin_lock(&ses->chan_lock); + if (!cifs_chan_needs_reconnect(ses, server)) { + spin_unlock(&ses->chan_lock); + + /* this means that we only need to tree connect */ + if (tcon->need_reconnect) + goto skip_sess_setup; + + rc = -EHOSTDOWN; + goto out; + } + spin_unlock(&ses->chan_lock); + + mutex_lock(&ses->session_mutex); + rc = cifs_negotiate_protocol(0, ses, server); + if (!rc) + rc = cifs_setup_session(0, ses, server, nls_codepage); /* do we need to reconnect tcon? */ if (rc || !tcon->need_reconnect) { @@ -198,6 +238,7 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) goto out; } +skip_sess_setup: cifs_mark_open_files_invalid(tcon); rc = cifs_tree_connect(0, tcon, nls_codepage); mutex_unlock(&ses->session_mutex); @@ -337,8 +378,13 @@ static int smb_init_no_reconnect(int smb_command, int wct, struct cifs_tcon *tcon, void **request_buf, void **response_buf) { - if (tcon->ses->need_reconnect || tcon->need_reconnect) + spin_lock(&tcon->ses->chan_lock); + if (cifs_chan_needs_reconnect(tcon->ses, tcon->ses->server) || + tcon->need_reconnect) { + spin_unlock(&tcon->ses->chan_lock); return -EHOSTDOWN; + } + spin_unlock(&tcon->ses->chan_lock); return __smb_init(smb_command, wct, tcon, request_buf, response_buf); } @@ -476,14 +522,15 @@ should_set_ext_sec_flag(enum securityEnum sectype) } int -CIFSSMBNegotiate(const unsigned int xid, struct cifs_ses *ses) +CIFSSMBNegotiate(const unsigned int xid, + struct cifs_ses *ses, + struct TCP_Server_Info *server) { NEGOTIATE_REQ *pSMB; NEGOTIATE_RSP *pSMBr; int rc = 0; int bytes_returned; int i; - struct TCP_Server_Info *server = ses->server; u16 count; if (!server) { @@ -600,8 +647,12 @@ CIFSSMBTDis(const unsigned int xid, struct cifs_tcon *tcon) * the tcon is no longer on the list, so no need to take lock before * checking this. */ - if ((tcon->need_reconnect) || (tcon->ses->need_reconnect)) - return 0; + spin_lock(&tcon->ses->chan_lock); + if ((tcon->need_reconnect) || CIFS_ALL_CHANS_NEED_RECONNECT(tcon->ses)) { + spin_unlock(&tcon->ses->chan_lock); + return -EIO; + } + spin_unlock(&tcon->ses->chan_lock); rc = small_smb_init(SMB_COM_TREE_DISCONNECT, 0, tcon, (void **)&smb_buffer); @@ -696,9 +747,14 @@ CIFSSMBLogoff(const unsigned int xid, struct cifs_ses *ses) return -EIO; mutex_lock(&ses->session_mutex); - if (ses->need_reconnect) + spin_lock(&ses->chan_lock); + if (CIFS_ALL_CHANS_NEED_RECONNECT(ses)) { + spin_unlock(&ses->chan_lock); goto session_already_dead; /* no need to send SMBlogoff if uid already closed due to reconnect */ + } + spin_unlock(&ses->chan_lock); + rc = small_smb_init(SMB_COM_LOGOFF_ANDX, 2, NULL, (void **)&pSMB); if (rc) { mutex_unlock(&ses->session_mutex); @@ -1401,7 +1457,7 @@ cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid) if (server->ops->is_session_expired && server->ops->is_session_expired(buf)) { - cifs_reconnect(server); + cifs_reconnect(server, true); return -1; } diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 68ed29cff7e8..0f36deff790e 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -166,14 +166,17 @@ static void cifs_resolve_server(struct work_struct *work) * Mark all sessions and tcons for reconnect. * * @server needs to be previously set to CifsNeedReconnect. + * */ -static void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server) +static void +cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server, + bool mark_smb_session) { + struct TCP_Server_Info *pserver; struct cifs_ses *ses; struct cifs_tcon *tcon; struct mid_q_entry *mid, *nmid; struct list_head retry_list; - struct TCP_Server_Info *pserver; server->maxBuf = 0; server->max_read = 0; @@ -191,16 +194,37 @@ static void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server spin_lock(&cifs_tcp_ses_lock); list_for_each_entry(ses, &pserver->smb_ses_list, smb_ses_list) { - ses->need_reconnect = true; - list_for_each_entry(tcon, &ses->tcon_list, tcon_list) + spin_lock(&ses->chan_lock); + if (!mark_smb_session && cifs_chan_needs_reconnect(ses, server)) + goto next_session; + + cifs_chan_set_need_reconnect(ses, server); + + /* If all channels need reconnect, then tcon needs reconnect */ + if (!mark_smb_session && !CIFS_ALL_CHANS_NEED_RECONNECT(ses)) + goto next_session; + + ses->status = CifsNeedReconnect; + + list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { tcon->need_reconnect = true; + tcon->tidStatus = CifsNeedReconnect; + } if (ses->tcon_ipc) ses->tcon_ipc->need_reconnect = true; + +next_session: + spin_unlock(&ses->chan_lock); } spin_unlock(&cifs_tcp_ses_lock); + /* + * before reconnecting the tcp session, mark the smb session (uid) + * and the tid bad so they are not used until reconnected + */ + cifs_dbg(FYI, "%s: marking sessions and tcons for reconnect and tearing down socket\n", + __func__); /* do not want to be sending data on a socket we are freeing */ - cifs_dbg(FYI, "%s: tearing down socket\n", __func__); mutex_lock(&server->srv_mutex); if (server->ssocket) { cifs_dbg(FYI, "State: 0x%x Flags: 0x%lx\n", server->ssocket->state, @@ -248,16 +272,16 @@ static void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server static bool cifs_tcp_ses_needs_reconnect(struct TCP_Server_Info *server, int num_targets) { - spin_lock(&GlobalMid_Lock); + spin_lock(&cifs_tcp_ses_lock); server->nr_targets = num_targets; if (server->tcpStatus == CifsExiting) { /* the demux thread will exit normally next time through the loop */ - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); wake_up(&server->response_q); return false; } server->tcpStatus = CifsNeedReconnect; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); return true; } @@ -268,15 +292,21 @@ static bool cifs_tcp_ses_needs_reconnect(struct TCP_Server_Info *server, int num * mark all smb sessions as reconnecting for tcp session * reconnect tcp session * wake up waiters on reconnection? - (not needed currently) + * + * if mark_smb_session is passed as true, unconditionally mark + * the smb session (and tcon) for reconnect as well. This value + * doesn't really matter for non-multichannel scenario. + * */ -static int __cifs_reconnect(struct TCP_Server_Info *server) +static int __cifs_reconnect(struct TCP_Server_Info *server, + bool mark_smb_session) { int rc = 0; if (!cifs_tcp_ses_needs_reconnect(server, 1)) return 0; - cifs_mark_tcp_ses_conns_for_reconnect(server); + cifs_mark_tcp_ses_conns_for_reconnect(server, mark_smb_session); do { try_to_freeze(); @@ -299,10 +329,10 @@ static int __cifs_reconnect(struct TCP_Server_Info *server) } else { atomic_inc(&tcpSesReconnectCount); set_credits(server, 1); - spin_lock(&GlobalMid_Lock); + spin_lock(&cifs_tcp_ses_lock); if (server->tcpStatus != CifsExiting) server->tcpStatus = CifsNeedNegotiate; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); cifs_swn_reset_server_dstaddr(server); mutex_unlock(&server->srv_mutex); } @@ -371,7 +401,9 @@ static int reconnect_target_unlocked(struct TCP_Server_Info *server, struct dfs_ return rc; } -static int reconnect_dfs_server(struct TCP_Server_Info *server) +static int +reconnect_dfs_server(struct TCP_Server_Info *server, + bool mark_smb_session) { int rc = 0; const char *refpath = server->current_fullpath + 1; @@ -395,7 +427,7 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server) if (!cifs_tcp_ses_needs_reconnect(server, num_targets)) return 0; - cifs_mark_tcp_ses_conns_for_reconnect(server); + cifs_mark_tcp_ses_conns_for_reconnect(server, mark_smb_session); do { try_to_freeze(); @@ -416,10 +448,10 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server) */ atomic_inc(&tcpSesReconnectCount); set_credits(server, 1); - spin_lock(&GlobalMid_Lock); + spin_lock(&cifs_tcp_ses_lock); if (server->tcpStatus != CifsExiting) server->tcpStatus = CifsNeedNegotiate; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); cifs_swn_reset_server_dstaddr(server); mutex_unlock(&server->srv_mutex); } while (server->tcpStatus == CifsNeedReconnect); @@ -430,29 +462,32 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server) dfs_cache_free_tgts(&tl); /* Need to set up echo worker again once connection has been established */ + spin_lock(&cifs_tcp_ses_lock); if (server->tcpStatus == CifsNeedNegotiate) mod_delayed_work(cifsiod_wq, &server->echo, 0); + spin_unlock(&cifs_tcp_ses_lock); + wake_up(&server->response_q); return rc; } -int cifs_reconnect(struct TCP_Server_Info *server) +int cifs_reconnect(struct TCP_Server_Info *server, bool mark_smb_session) { /* If tcp session is not an dfs connection, then reconnect to last target server */ spin_lock(&cifs_tcp_ses_lock); if (!server->is_dfs_conn || !server->origin_fullpath || !server->leaf_fullpath) { spin_unlock(&cifs_tcp_ses_lock); - return __cifs_reconnect(server); + return __cifs_reconnect(server, mark_smb_session); } spin_unlock(&cifs_tcp_ses_lock); - return reconnect_dfs_server(server); + return reconnect_dfs_server(server, mark_smb_session); } #else -int cifs_reconnect(struct TCP_Server_Info *server) +int cifs_reconnect(struct TCP_Server_Info *server, bool mark_smb_session) { - return __cifs_reconnect(server); + return __cifs_reconnect(server, mark_smb_session); } #endif @@ -534,15 +569,18 @@ server_unresponsive(struct TCP_Server_Info *server) * 65s kernel_recvmsg times out, and we see that we haven't gotten * a response in >60s. */ + spin_lock(&cifs_tcp_ses_lock); if ((server->tcpStatus == CifsGood || server->tcpStatus == CifsNeedNegotiate) && (!server->ops->can_echo || server->ops->can_echo(server)) && time_after(jiffies, server->lstrp + 3 * server->echo_interval)) { + spin_unlock(&cifs_tcp_ses_lock); cifs_server_dbg(VFS, "has not responded in %lu seconds. Reconnecting...\n", (3 * server->echo_interval) / HZ); - cifs_reconnect(server); + cifs_reconnect(server, false); return true; } + spin_unlock(&cifs_tcp_ses_lock); return false; } @@ -576,7 +614,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg) /* reconnect if no credits and no requests in flight */ if (zero_credits(server)) { - cifs_reconnect(server); + cifs_reconnect(server, false); return -ECONNABORTED; } @@ -587,13 +625,18 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg) else length = sock_recvmsg(server->ssocket, smb_msg, 0); - if (server->tcpStatus == CifsExiting) + spin_lock(&cifs_tcp_ses_lock); + if (server->tcpStatus == CifsExiting) { + spin_unlock(&cifs_tcp_ses_lock); return -ESHUTDOWN; + } if (server->tcpStatus == CifsNeedReconnect) { - cifs_reconnect(server); + spin_unlock(&cifs_tcp_ses_lock); + cifs_reconnect(server, false); return -ECONNABORTED; } + spin_unlock(&cifs_tcp_ses_lock); if (length == -ERESTARTSYS || length == -EAGAIN || @@ -610,7 +653,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg) if (length <= 0) { cifs_dbg(FYI, "Received no data or error: %d\n", length); - cifs_reconnect(server); + cifs_reconnect(server, false); return -ECONNABORTED; } } @@ -689,11 +732,11 @@ is_smb_response(struct TCP_Server_Info *server, unsigned char type) * initialize frame). */ cifs_set_port((struct sockaddr *)&server->dstaddr, CIFS_PORT); - cifs_reconnect(server); + cifs_reconnect(server, true); break; default: cifs_server_dbg(VFS, "RFC 1002 unknown response type 0x%x\n", type); - cifs_reconnect(server); + cifs_reconnect(server, true); } return false; @@ -771,9 +814,9 @@ static void clean_demultiplex_info(struct TCP_Server_Info *server) cancel_delayed_work_sync(&server->echo); cancel_delayed_work_sync(&server->resolve); - spin_lock(&GlobalMid_Lock); + spin_lock(&cifs_tcp_ses_lock); server->tcpStatus = CifsExiting; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); wake_up_all(&server->response_q); /* check if we have blocked requests that need to free */ @@ -866,7 +909,7 @@ standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid) if (pdu_length > CIFSMaxBufSize + MAX_HEADER_SIZE(server) - server->vals->header_preamble_size) { cifs_server_dbg(VFS, "SMB response too long (%u bytes)\n", pdu_length); - cifs_reconnect(server); + cifs_reconnect(server, true); return -ECONNABORTED; } @@ -913,7 +956,7 @@ cifs_handle_standard(struct TCP_Server_Info *server, struct mid_q_entry *mid) if (server->ops->is_session_expired && server->ops->is_session_expired(buf)) { - cifs_reconnect(server); + cifs_reconnect(server, true); return -1; } @@ -1017,7 +1060,7 @@ next_pdu: server->vals->header_preamble_size) { cifs_server_dbg(VFS, "SMB response too short (%u bytes)\n", server->pdu_size); - cifs_reconnect(server); + cifs_reconnect(server, true); continue; } @@ -1069,7 +1112,7 @@ next_pdu: server->ops->is_status_io_timeout(buf)) { num_io_timeout++; if (num_io_timeout > NUM_STATUS_IO_TIMEOUT) { - cifs_reconnect(server); + cifs_reconnect(server, false); num_io_timeout = 0; continue; } @@ -1390,9 +1433,9 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect) else cancel_delayed_work_sync(&server->reconnect); - spin_lock(&GlobalMid_Lock); + spin_lock(&cifs_tcp_ses_lock); server->tcpStatus = CifsExiting; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); cifs_crypto_secmech_release(server); @@ -1545,7 +1588,9 @@ smbd_connected: * to the struct since the kernel thread not created yet * no need to spinlock this update of tcpStatus */ + spin_lock(&cifs_tcp_ses_lock); tcp_ses->tcpStatus = CifsNeedNegotiate; + spin_unlock(&cifs_tcp_ses_lock); if ((ctx->max_credits < 20) || (ctx->max_credits > 60000)) tcp_ses->max_credits = SMB2_MAX_CREDITS_AVAILABLE; @@ -1762,15 +1807,13 @@ void cifs_put_smb_ses(struct cifs_ses *ses) spin_unlock(&cifs_tcp_ses_lock); return; } - spin_unlock(&cifs_tcp_ses_lock); /* ses_count can never go negative */ WARN_ON(ses->ses_count < 0); - spin_lock(&GlobalMid_Lock); if (ses->status == CifsGood) ses->status = CifsExiting; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); cifs_free_ipc(ses); @@ -1987,11 +2030,13 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) cifs_dbg(FYI, "Existing smb sess found (status=%d)\n", ses->status); - mutex_lock(&ses->session_mutex); - if (ses->need_reconnect) { + spin_lock(&ses->chan_lock); + if (cifs_chan_needs_reconnect(ses, server)) { + spin_unlock(&ses->chan_lock); cifs_dbg(FYI, "Session needs reconnect\n"); - rc = cifs_negotiate_protocol(xid, ses); + mutex_lock(&ses->session_mutex); + rc = cifs_negotiate_protocol(xid, ses, server); if (rc) { mutex_unlock(&ses->session_mutex); /* problem -- put our ses reference */ @@ -2000,7 +2045,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) return ERR_PTR(rc); } - rc = cifs_setup_session(xid, ses, + rc = cifs_setup_session(xid, ses, server, ctx->local_nls); if (rc) { mutex_unlock(&ses->session_mutex); @@ -2009,8 +2054,11 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) free_xid(xid); return ERR_PTR(rc); } + mutex_unlock(&ses->session_mutex); + + spin_lock(&ses->chan_lock); } - mutex_unlock(&ses->session_mutex); + spin_unlock(&ses->chan_lock); /* existing SMB ses has a server reference already */ cifs_put_tcp_session(server, 0); @@ -2060,28 +2108,33 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) ses->sectype = ctx->sectype; ses->sign = ctx->sign; - mutex_lock(&ses->session_mutex); /* add server as first channel */ spin_lock(&ses->chan_lock); ses->chans[0].server = server; ses->chan_count = 1; ses->chan_max = ctx->multichannel ? ctx->max_channels:1; + ses->chans_need_reconnect = 1; spin_unlock(&ses->chan_lock); - rc = cifs_negotiate_protocol(xid, ses); + mutex_lock(&ses->session_mutex); + rc = cifs_negotiate_protocol(xid, ses, server); if (!rc) - rc = cifs_setup_session(xid, ses, ctx->local_nls); + rc = cifs_setup_session(xid, ses, server, ctx->local_nls); + mutex_unlock(&ses->session_mutex); /* each channel uses a different signing key */ memcpy(ses->chans[0].signkey, ses->smb3signingkey, sizeof(ses->smb3signingkey)); - mutex_unlock(&ses->session_mutex); if (rc) goto get_ses_fail; - /* success, put it on the list and add it as first channel */ + /* + * success, put it on the list and add it as first channel + * note: the session becomes active soon after this. So you'll + * need to lock before changing something in the session. + */ spin_lock(&cifs_tcp_ses_lock); list_add(&ses->smb_ses_list, &server->smb_ses_list); spin_unlock(&cifs_tcp_ses_lock); @@ -2161,6 +2214,9 @@ cifs_put_tcon(struct cifs_tcon *tcon) /* tc_count can never go negative */ WARN_ON(tcon->tc_count < 0); + list_del_init(&tcon->tcon_list); + spin_unlock(&cifs_tcp_ses_lock); + if (tcon->use_witness) { int rc; @@ -2171,9 +2227,6 @@ cifs_put_tcon(struct cifs_tcon *tcon) } } - list_del_init(&tcon->tcon_list); - spin_unlock(&cifs_tcp_ses_lock); - xid = get_xid(); if (ses->server->ops->tree_disconnect) ses->server->ops->tree_disconnect(xid, tcon); @@ -2290,10 +2343,6 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) } } - /* - * BB Do we need to wrap session_mutex around this TCon call and Unix - * SetFS as we do on SessSetup and reconnect? - */ xid = get_xid(); rc = ses->server->ops->tree_connect(xid, ses, ctx->UNC, tcon, ctx->local_nls); @@ -3029,12 +3078,15 @@ static int mount_get_conns(struct mount_ctx *mnt_ctx) * for just this mount. */ reset_cifs_unix_caps(xid, tcon, cifs_sb, ctx); + spin_lock(&cifs_tcp_ses_lock); if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) && (le64_to_cpu(tcon->fsUnixInfo.Capability) & CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)) { + spin_unlock(&cifs_tcp_ses_lock); rc = -EACCES; goto out; } + spin_unlock(&cifs_tcp_ses_lock); } else tcon->unix_ext = 0; /* server does not support them */ @@ -3709,7 +3761,9 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, if (rc == 0) { bool is_unicode; + spin_lock(&cifs_tcp_ses_lock); tcon->tidStatus = CifsGood; + spin_unlock(&cifs_tcp_ses_lock); tcon->need_reconnect = false; tcon->tid = smb_buffer_response->Tid; bcc_ptr = pByteArea(smb_buffer_response); @@ -3799,26 +3853,32 @@ cifs_umount(struct cifs_sb_info *cifs_sb) } int -cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses) +cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses, + struct TCP_Server_Info *server) { int rc = 0; - struct TCP_Server_Info *server = cifs_ses_server(ses); if (!server->ops->need_neg || !server->ops->negotiate) return -ENOSYS; /* only send once per connect */ - if (!server->ops->need_neg(server)) + spin_lock(&cifs_tcp_ses_lock); + if (!server->ops->need_neg(server) || + server->tcpStatus != CifsNeedNegotiate) { + spin_unlock(&cifs_tcp_ses_lock); return 0; + } + server->tcpStatus = CifsInNegotiate; + spin_unlock(&cifs_tcp_ses_lock); - rc = server->ops->negotiate(xid, ses); + rc = server->ops->negotiate(xid, ses, server); if (rc == 0) { - spin_lock(&GlobalMid_Lock); - if (server->tcpStatus == CifsNeedNegotiate) - server->tcpStatus = CifsGood; + spin_lock(&cifs_tcp_ses_lock); + if (server->tcpStatus == CifsInNegotiate) + server->tcpStatus = CifsNeedSessSetup; else rc = -EHOSTDOWN; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); } return rc; @@ -3826,12 +3886,26 @@ cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses) int cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, + struct TCP_Server_Info *server, struct nls_table *nls_info) { int rc = -ENOSYS; - struct TCP_Server_Info *server = cifs_ses_server(ses); + bool is_binding = false; - if (!ses->binding) { + /* only send once per connect */ + spin_lock(&cifs_tcp_ses_lock); + if (server->tcpStatus != CifsNeedSessSetup) { + spin_unlock(&cifs_tcp_ses_lock); + return 0; + } + ses->status = CifsInSessSetup; + spin_unlock(&cifs_tcp_ses_lock); + + spin_lock(&ses->chan_lock); + is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses); + spin_unlock(&ses->chan_lock); + + if (!is_binding) { ses->capabilities = server->capabilities; if (!linuxExtEnabled) ses->capabilities &= (~server->vals->cap_unix); @@ -3849,7 +3923,7 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, server->sec_mode, server->capabilities, server->timeAdj); if (server->ops->sess_setup) - rc = server->ops->sess_setup(xid, ses, nls_info); + rc = server->ops->sess_setup(xid, ses, server, nls_info); if (rc) cifs_server_dbg(VFS, "Send error in SessSetup = %d\n", rc); @@ -4197,6 +4271,17 @@ static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *t struct dfs_cache_tgt_iterator *tit; bool target_match; + /* only send once per connect */ + spin_lock(&cifs_tcp_ses_lock); + if (tcon->ses->status != CifsGood || + (tcon->tidStatus != CifsNew && + tcon->tidStatus != CifsNeedTcon)) { + spin_unlock(&cifs_tcp_ses_lock); + return 0; + } + tcon->tidStatus = CifsInTcon; + spin_unlock(&cifs_tcp_ses_lock); + extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len); tit = dfs_cache_get_tgt_iterator(tl); @@ -4355,6 +4440,17 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru { const struct smb_version_operations *ops = tcon->ses->server->ops; + /* only send once per connect */ + spin_lock(&cifs_tcp_ses_lock); + if (tcon->ses->status != CifsGood || + (tcon->tidStatus != CifsNew && + tcon->tidStatus != CifsNeedTcon)) { + spin_unlock(&cifs_tcp_ses_lock); + return 0; + } + tcon->tidStatus = CifsInTcon; + spin_unlock(&cifs_tcp_ses_lock); + return ops->tree_connect(xid, tcon->ses, tcon->treeName, tcon, nlsc); } #endif diff --git a/fs/cifs/netmisc.c b/fs/cifs/netmisc.c index fa9fbd6a819c..43b16b6d108c 100644 --- a/fs/cifs/netmisc.c +++ b/fs/cifs/netmisc.c @@ -896,10 +896,10 @@ map_and_check_smb_error(struct mid_q_entry *mid, bool logErr) if (class == ERRSRV && code == ERRbaduid) { cifs_dbg(FYI, "Server returned 0x%x, reconnecting session...\n", code); - spin_lock(&GlobalMid_Lock); + spin_lock(&cifs_tcp_ses_lock); if (mid->server->tcpStatus != CifsExiting) mid->server->tcpStatus = CifsNeedReconnect; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); } } diff --git a/fs/cifs/ntlmssp.h b/fs/cifs/ntlmssp.h index fe707f45da89..6d242af536cb 100644 --- a/fs/cifs/ntlmssp.h +++ b/fs/cifs/ntlmssp.h @@ -121,7 +121,9 @@ typedef struct _AUTHENTICATE_MESSAGE { int decode_ntlmssp_challenge(char *bcc_ptr, int blob_len, struct cifs_ses *ses); int build_ntlmssp_negotiate_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, + struct TCP_Server_Info *server, const struct nls_table *nls_cp); int build_ntlmssp_auth_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, + struct TCP_Server_Info *server, const struct nls_table *nls_cp); diff --git a/fs/cifs/sess.c b/fs/cifs/sess.c index 035dc3e245dc..d12490e12be5 100644 --- a/fs/cifs/sess.c +++ b/fs/cifs/sess.c @@ -65,6 +65,53 @@ bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface) return false; } +unsigned int +cifs_ses_get_chan_index(struct cifs_ses *ses, + struct TCP_Server_Info *server) +{ + unsigned int i; + + for (i = 0; i < ses->chan_count; i++) { + if (ses->chans[i].server == server) + return i; + } + + /* If we didn't find the channel, it is likely a bug */ + WARN_ON(1); + return 0; +} + +void +cifs_chan_set_need_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server) +{ + unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + + set_bit(chan_index, &ses->chans_need_reconnect); + cifs_dbg(FYI, "Set reconnect bitmask for chan %u; now 0x%lx\n", + chan_index, ses->chans_need_reconnect); +} + +void +cifs_chan_clear_need_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server) +{ + unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + + clear_bit(chan_index, &ses->chans_need_reconnect); + cifs_dbg(FYI, "Cleared reconnect bitmask for chan %u; now 0x%lx\n", + chan_index, ses->chans_need_reconnect); +} + +bool +cifs_chan_needs_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server) +{ + unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + + return CIFS_CHAN_NEEDS_RECONNECT(ses, chan_index); +} + /* returns number of channels added */ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) { @@ -261,9 +308,8 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, chan_server = cifs_get_tcp_session(&ctx, ses->server); - mutex_lock(&ses->session_mutex); spin_lock(&ses->chan_lock); - chan = ses->binding_chan = &ses->chans[ses->chan_count]; + chan = &ses->chans[ses->chan_count]; chan->server = chan_server; if (IS_ERR(chan->server)) { rc = PTR_ERR(chan->server); @@ -271,8 +317,15 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, spin_unlock(&ses->chan_lock); goto out; } + ses->chan_count++; + atomic_set(&ses->chan_seq, 0); + + /* Mark this channel as needing connect/setup */ + cifs_chan_set_need_reconnect(ses, chan->server); + spin_unlock(&ses->chan_lock); + mutex_lock(&ses->session_mutex); /* * We need to allocate the server crypto now as we will need * to sign packets before we generate the channel signing key @@ -281,37 +334,29 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, rc = smb311_crypto_shash_allocate(chan->server); if (rc) { cifs_dbg(VFS, "%s: crypto alloc failed\n", __func__); + mutex_unlock(&ses->session_mutex); goto out; } - ses->binding = true; - rc = cifs_negotiate_protocol(xid, ses); - if (rc) - goto out; - - rc = cifs_setup_session(xid, ses, cifs_sb->local_nls); - if (rc) - goto out; - - /* success, put it on the list - * XXX: sharing ses between 2 tcp servers is not possible, the - * way "internal" linked lists works in linux makes element - * only able to belong to one list - * - * the binding session is already established so the rest of - * the code should be able to look it up, no need to add the - * ses to the new server. - */ + rc = cifs_negotiate_protocol(xid, ses, chan->server); + if (!rc) + rc = cifs_setup_session(xid, ses, chan->server, cifs_sb->local_nls); - spin_lock(&ses->chan_lock); - ses->chan_count++; - atomic_set(&ses->chan_seq, 0); - spin_unlock(&ses->chan_lock); + mutex_unlock(&ses->session_mutex); out: - ses->binding = false; - ses->binding_chan = NULL; - mutex_unlock(&ses->session_mutex); + if (rc && chan->server) { + spin_lock(&ses->chan_lock); + /* we rely on all bits beyond chan_count to be clear */ + cifs_chan_clear_need_reconnect(ses, chan->server); + ses->chan_count--; + /* + * chan_count should never reach 0 as at least the primary + * channel is always allocated + */ + WARN_ON(ses->chan_count < 1); + spin_unlock(&ses->chan_lock); + } if (rc && chan->server) cifs_put_tcp_session(chan->server, 0); @@ -325,14 +370,16 @@ void cifs_ses_mark_for_reconnect(struct cifs_ses *ses) int i; for (i = 0; i < ses->chan_count; i++) { - spin_lock(&GlobalMid_Lock); + spin_lock(&cifs_tcp_ses_lock); if (ses->chans[i].server->tcpStatus != CifsExiting) ses->chans[i].server->tcpStatus = CifsNeedReconnect; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); } } -static __u32 cifs_ssetup_hdr(struct cifs_ses *ses, SESSION_SETUP_ANDX *pSMB) +static __u32 cifs_ssetup_hdr(struct cifs_ses *ses, + struct TCP_Server_Info *server, + SESSION_SETUP_ANDX *pSMB) { __u32 capabilities = 0; @@ -345,7 +392,7 @@ static __u32 cifs_ssetup_hdr(struct cifs_ses *ses, SESSION_SETUP_ANDX *pSMB) pSMB->req.MaxBufferSize = cpu_to_le16(min_t(u32, CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4, USHRT_MAX)); - pSMB->req.MaxMpxCount = cpu_to_le16(ses->server->maxReq); + pSMB->req.MaxMpxCount = cpu_to_le16(server->maxReq); pSMB->req.VcNumber = cpu_to_le16(1); /* Now no need to set SMBFLG_CASELESS or obsolete CANONICAL PATH */ @@ -356,7 +403,7 @@ static __u32 cifs_ssetup_hdr(struct cifs_ses *ses, SESSION_SETUP_ANDX *pSMB) capabilities = CAP_LARGE_FILES | CAP_NT_SMBS | CAP_LEVEL_II_OPLOCKS | CAP_LARGE_WRITE_X | CAP_LARGE_READ_X; - if (ses->server->sign) + if (server->sign) pSMB->req.hdr.Flags2 |= SMBFLG2_SECURITY_SIGNATURE; if (ses->capabilities & CAP_UNICODE) { @@ -719,10 +766,10 @@ static inline void cifs_security_buffer_from_str(SECURITY_BUFFER *pbuf, int build_ntlmssp_negotiate_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, + struct TCP_Server_Info *server, const struct nls_table *nls_cp) { int rc = 0; - struct TCP_Server_Info *server = cifs_ses_server(ses); NEGOTIATE_MESSAGE *sec_blob; __u32 flags; unsigned char *tmp; @@ -776,6 +823,7 @@ setup_ntlm_neg_ret: int build_ntlmssp_auth_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, + struct TCP_Server_Info *server, const struct nls_table *nls_cp) { int rc; @@ -912,6 +960,7 @@ cifs_select_sectype(struct TCP_Server_Info *server, enum securityEnum requested) struct sess_data { unsigned int xid; struct cifs_ses *ses; + struct TCP_Server_Info *server; struct nls_table *nls_cp; void (*func)(struct sess_data *); int result; @@ -978,30 +1027,36 @@ static int sess_establish_session(struct sess_data *sess_data) { struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; - mutex_lock(&ses->server->srv_mutex); - if (!ses->server->session_estab) { - if (ses->server->sign) { - ses->server->session_key.response = + mutex_lock(&server->srv_mutex); + if (!server->session_estab) { + if (server->sign) { + server->session_key.response = kmemdup(ses->auth_key.response, ses->auth_key.len, GFP_KERNEL); - if (!ses->server->session_key.response) { - mutex_unlock(&ses->server->srv_mutex); + if (!server->session_key.response) { + mutex_unlock(&server->srv_mutex); return -ENOMEM; } - ses->server->session_key.len = + server->session_key.len = ses->auth_key.len; } - ses->server->sequence_number = 0x2; - ses->server->session_estab = true; + server->sequence_number = 0x2; + server->session_estab = true; } - mutex_unlock(&ses->server->srv_mutex); + mutex_unlock(&server->srv_mutex); cifs_dbg(FYI, "CIFS session established successfully\n"); - spin_lock(&GlobalMid_Lock); + spin_lock(&ses->chan_lock); + cifs_chan_clear_need_reconnect(ses, server); + spin_unlock(&ses->chan_lock); + + /* Even if one channel is active, session is in good state */ + spin_lock(&cifs_tcp_ses_lock); + server->tcpStatus = CifsGood; ses->status = CifsGood; - ses->need_reconnect = false; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); return 0; } @@ -1036,6 +1091,7 @@ sess_auth_ntlmv2(struct sess_data *sess_data) SESSION_SETUP_ANDX *pSMB; char *bcc_ptr; struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; __u32 capabilities; __u16 bytes_remaining; @@ -1047,7 +1103,7 @@ sess_auth_ntlmv2(struct sess_data *sess_data) pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base; bcc_ptr = sess_data->iov[2].iov_base; - capabilities = cifs_ssetup_hdr(ses, pSMB); + capabilities = cifs_ssetup_hdr(ses, server, pSMB); pSMB->req_no_secext.Capabilities = cpu_to_le32(capabilities); @@ -1145,6 +1201,7 @@ sess_auth_kerberos(struct sess_data *sess_data) SESSION_SETUP_ANDX *pSMB; char *bcc_ptr; struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; __u32 capabilities; __u16 bytes_remaining; struct key *spnego_key = NULL; @@ -1159,9 +1216,9 @@ sess_auth_kerberos(struct sess_data *sess_data) pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base; bcc_ptr = sess_data->iov[2].iov_base; - capabilities = cifs_ssetup_hdr(ses, pSMB); + capabilities = cifs_ssetup_hdr(ses, server, pSMB); - spnego_key = cifs_get_spnego_key(ses); + spnego_key = cifs_get_spnego_key(ses, server); if (IS_ERR(spnego_key)) { rc = PTR_ERR(spnego_key); spnego_key = NULL; @@ -1285,12 +1342,13 @@ _sess_auth_rawntlmssp_assemble_req(struct sess_data *sess_data) { SESSION_SETUP_ANDX *pSMB; struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; __u32 capabilities; char *bcc_ptr; pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base; - capabilities = cifs_ssetup_hdr(ses, pSMB); + capabilities = cifs_ssetup_hdr(ses, server, pSMB); if ((pSMB->req.hdr.Flags2 & SMBFLG2_UNICODE) == 0) { cifs_dbg(VFS, "NTLMSSP requires Unicode support\n"); return -ENOSYS; @@ -1324,6 +1382,7 @@ sess_auth_rawntlmssp_negotiate(struct sess_data *sess_data) struct smb_hdr *smb_buf; SESSION_SETUP_ANDX *pSMB; struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; __u16 bytes_remaining; char *bcc_ptr; unsigned char *ntlmsspblob = NULL; @@ -1351,7 +1410,7 @@ sess_auth_rawntlmssp_negotiate(struct sess_data *sess_data) /* Build security blob before we assemble the request */ rc = build_ntlmssp_negotiate_blob(&ntlmsspblob, - &blob_len, ses, + &blob_len, ses, server, sess_data->nls_cp); if (rc) goto out; @@ -1426,6 +1485,7 @@ sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data) struct smb_hdr *smb_buf; SESSION_SETUP_ANDX *pSMB; struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; __u16 bytes_remaining; char *bcc_ptr; unsigned char *ntlmsspblob = NULL; @@ -1442,7 +1502,8 @@ sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data) pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base; smb_buf = (struct smb_hdr *)pSMB; rc = build_ntlmssp_auth_blob(&ntlmsspblob, - &blob_len, ses, sess_data->nls_cp); + &blob_len, ses, server, + sess_data->nls_cp); if (rc) goto out_free_ntlmsspblob; sess_data->iov[1].iov_len = blob_len; @@ -1526,11 +1587,13 @@ out: sess_data->result = rc; } -static int select_sec(struct cifs_ses *ses, struct sess_data *sess_data) +static int select_sec(struct sess_data *sess_data) { int type; + struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; - type = cifs_select_sectype(ses->server, ses->sectype); + type = cifs_select_sectype(server, ses->sectype); cifs_dbg(FYI, "sess setup type %d\n", type); if (type == Unspecified) { cifs_dbg(VFS, "Unable to select appropriate authentication method!\n"); @@ -1561,7 +1624,8 @@ static int select_sec(struct cifs_ses *ses, struct sess_data *sess_data) } int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, - const struct nls_table *nls_cp) + struct TCP_Server_Info *server, + const struct nls_table *nls_cp) { int rc = 0; struct sess_data *sess_data; @@ -1575,15 +1639,16 @@ int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, if (!sess_data) return -ENOMEM; - rc = select_sec(ses, sess_data); - if (rc) - goto out; - sess_data->xid = xid; sess_data->ses = ses; + sess_data->server = server; sess_data->buf0_type = CIFS_NO_BUFFER; sess_data->nls_cp = (struct nls_table *) nls_cp; + rc = select_sec(sess_data); + if (rc) + goto out; + while (sess_data->func) sess_data->func(sess_data); diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c index 3b83839fc2c2..8272c91e15ef 100644 --- a/fs/cifs/smb1ops.c +++ b/fs/cifs/smb1ops.c @@ -7,6 +7,7 @@ #include <linux/pagemap.h> #include <linux/vfs.h> +#include <uapi/linux/magic.h> #include "cifsglob.h" #include "cifsproto.h" #include "cifs_debug.h" @@ -163,7 +164,7 @@ cifs_get_next_mid(struct TCP_Server_Info *server) { __u64 mid = 0; __u16 last_mid, cur_mid; - bool collision; + bool collision, reconnect = false; spin_lock(&GlobalMid_Lock); @@ -215,7 +216,7 @@ cifs_get_next_mid(struct TCP_Server_Info *server) * an eventual reconnect to clean out the pending_mid_q. */ if (num_mids > 32768) - server->tcpStatus = CifsNeedReconnect; + reconnect = true; if (!collision) { mid = (__u64)cur_mid; @@ -225,6 +226,13 @@ cifs_get_next_mid(struct TCP_Server_Info *server) cur_mid++; } spin_unlock(&GlobalMid_Lock); + + if (reconnect) { + spin_lock(&cifs_tcp_ses_lock); + server->tcpStatus = CifsNeedReconnect; + spin_unlock(&cifs_tcp_ses_lock); + } + return mid; } @@ -414,14 +422,16 @@ cifs_need_neg(struct TCP_Server_Info *server) } static int -cifs_negotiate(const unsigned int xid, struct cifs_ses *ses) +cifs_negotiate(const unsigned int xid, + struct cifs_ses *ses, + struct TCP_Server_Info *server) { int rc; - rc = CIFSSMBNegotiate(xid, ses); + rc = CIFSSMBNegotiate(xid, ses, server); if (rc == -EAGAIN) { /* retry only once on 1st time connection */ - set_credits(ses->server, 1); - rc = CIFSSMBNegotiate(xid, ses); + set_credits(server, 1); + rc = CIFSSMBNegotiate(xid, ses, server); if (rc == -EAGAIN) rc = -EHOSTDOWN; } @@ -878,7 +888,7 @@ cifs_queryfs(const unsigned int xid, struct cifs_tcon *tcon, { int rc = -EOPNOTSUPP; - buf->f_type = CIFS_MAGIC_NUMBER; + buf->f_type = CIFS_SUPER_MAGIC; /* * We could add a second check for a QFS Unix capability bit diff --git a/fs/cifs/smb2glob.h b/fs/cifs/smb2glob.h index ca692b2283cd..4125fd113cfb 100644 --- a/fs/cifs/smb2glob.h +++ b/fs/cifs/smb2glob.h @@ -13,8 +13,6 @@ #ifndef _SMB2_GLOB_H #define _SMB2_GLOB_H -#define SMB2_MAGIC_NUMBER 0xFE534D42 - /* ***************************************************************** * Constants go here diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c index cdcdef32759e..b25623e3fe3d 100644 --- a/fs/cifs/smb2misc.c +++ b/fs/cifs/smb2misc.c @@ -847,16 +847,17 @@ smb2_handle_cancelled_mid(struct mid_q_entry *mid, struct TCP_Server_Info *serve * SMB2 header. * * @ses: server session structure + * @server: pointer to server info * @iov: array containing the SMB request we will send to the server * @nvec: number of array entries for the iov */ int -smb311_update_preauth_hash(struct cifs_ses *ses, struct kvec *iov, int nvec) +smb311_update_preauth_hash(struct cifs_ses *ses, struct TCP_Server_Info *server, + struct kvec *iov, int nvec) { int i, rc; struct sdesc *d; struct smb2_hdr *hdr; - struct TCP_Server_Info *server = cifs_ses_server(ses); hdr = (struct smb2_hdr *)iov[0].iov_base; /* neg prot are always taken */ diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index c5b1dea54ebc..af5d0830bc8a 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -13,6 +13,7 @@ #include <linux/sort.h> #include <crypto/aead.h> #include <linux/fiemap.h> +#include <uapi/linux/magic.h> #include "cifsfs.h" #include "cifsglob.h" #include "smb2pdu.h" @@ -121,9 +122,13 @@ smb2_add_credits(struct TCP_Server_Info *server, optype, scredits, add); } + spin_lock(&cifs_tcp_ses_lock); if (server->tcpStatus == CifsNeedReconnect - || server->tcpStatus == CifsExiting) + || server->tcpStatus == CifsExiting) { + spin_unlock(&cifs_tcp_ses_lock); return; + } + spin_unlock(&cifs_tcp_ses_lock); switch (rc) { case -1: @@ -208,11 +213,15 @@ smb2_wait_mtu_credits(struct TCP_Server_Info *server, unsigned int size, return rc; spin_lock(&server->req_lock); } else { + spin_unlock(&server->req_lock); + spin_lock(&cifs_tcp_ses_lock); if (server->tcpStatus == CifsExiting) { - spin_unlock(&server->req_lock); + spin_unlock(&cifs_tcp_ses_lock); return -ENOENT; } + spin_unlock(&cifs_tcp_ses_lock); + spin_lock(&server->req_lock); scredits = server->credits; /* can deadlock with reopen */ if (scredits <= 8) { @@ -384,14 +393,16 @@ smb2_need_neg(struct TCP_Server_Info *server) } static int -smb2_negotiate(const unsigned int xid, struct cifs_ses *ses) +smb2_negotiate(const unsigned int xid, + struct cifs_ses *ses, + struct TCP_Server_Info *server) { int rc; spin_lock(&GlobalMid_Lock); - cifs_ses_server(ses)->CurrentMid = 0; + server->CurrentMid = 0; spin_unlock(&GlobalMid_Lock); - rc = SMB2_negotiate(xid, ses); + rc = SMB2_negotiate(xid, ses, server); /* BB we probably don't need to retry with modern servers */ if (rc == -EAGAIN) rc = -EHOSTDOWN; @@ -2747,7 +2758,7 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon, goto qfs_exit; rsp = (struct smb2_query_info_rsp *)rsp_iov.iov_base; - buf->f_type = SMB2_MAGIC_NUMBER; + buf->f_type = SMB2_SUPER_MAGIC; info = (struct smb2_fs_full_size_info *)( le16_to_cpu(rsp->OutputBufferOffset) + (char *)rsp); rc = smb2_validate_iov(le16_to_cpu(rsp->OutputBufferOffset), @@ -2789,7 +2800,7 @@ smb311_queryfs(const unsigned int xid, struct cifs_tcon *tcon, rc = SMB311_posix_qfs_info(xid, tcon, fid.persistent_fid, fid.volatile_fid, buf); - buf->f_type = SMB2_MAGIC_NUMBER; + buf->f_type = SMB2_SUPER_MAGIC; SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); return rc; } @@ -4808,7 +4819,7 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid, if (server->ops->is_session_expired && server->ops->is_session_expired(buf)) { if (!is_offloaded) - cifs_reconnect(server); + cifs_reconnect(server, true); return -1; } @@ -4981,10 +4992,12 @@ static void smb2_decrypt_offload(struct work_struct *work) mid->callback(mid); } else { + spin_lock(&cifs_tcp_ses_lock); spin_lock(&GlobalMid_Lock); if (dw->server->tcpStatus == CifsNeedReconnect) { mid->mid_state = MID_RETRY_NEEDED; spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); mid->callback(mid); } else { mid->mid_state = MID_REQUEST_SUBMITTED; @@ -4992,6 +5005,7 @@ static void smb2_decrypt_offload(struct work_struct *work) list_add_tail(&mid->qhead, &dw->server->pending_mid_q); spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); } } cifs_mid_q_entry_release(mid); @@ -5221,13 +5235,13 @@ smb3_receive_transform(struct TCP_Server_Info *server, sizeof(struct smb2_hdr)) { cifs_server_dbg(VFS, "Transform message is too small (%u)\n", pdu_length); - cifs_reconnect(server); + cifs_reconnect(server, true); return -ECONNABORTED; } if (pdu_length < orig_len + sizeof(struct smb2_transform_hdr)) { cifs_server_dbg(VFS, "Transform message is broken\n"); - cifs_reconnect(server); + cifs_reconnect(server, true); return -ECONNABORTED; } diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 8b3670388cda..8d471df69c59 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -162,6 +162,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, if (smb2_command == SMB2_TREE_CONNECT || smb2_command == SMB2_IOCTL) return 0; + spin_lock(&cifs_tcp_ses_lock); if (tcon->tidStatus == CifsExiting) { /* * only tree disconnect, open, and write, @@ -171,11 +172,13 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, if ((smb2_command != SMB2_WRITE) && (smb2_command != SMB2_CREATE) && (smb2_command != SMB2_TREE_DISCONNECT)) { + spin_unlock(&cifs_tcp_ses_lock); cifs_dbg(FYI, "can not send cmd %d while umounting\n", smb2_command); return -ENODEV; } } + spin_unlock(&cifs_tcp_ses_lock); if ((!tcon->ses) || (tcon->ses->status == CifsExiting) || (!tcon->ses->server) || !server) return -EIO; @@ -214,8 +217,12 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, } /* are we still trying to reconnect? */ - if (server->tcpStatus != CifsNeedReconnect) + spin_lock(&cifs_tcp_ses_lock); + if (server->tcpStatus != CifsNeedReconnect) { + spin_unlock(&cifs_tcp_ses_lock); break; + } + spin_unlock(&cifs_tcp_ses_lock); if (retries && --retries) continue; @@ -232,64 +239,70 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, retries = server->nr_targets; } - if (!tcon->ses->need_reconnect && !tcon->need_reconnect) + spin_lock(&ses->chan_lock); + if (!cifs_chan_needs_reconnect(ses, server) && !tcon->need_reconnect) { + spin_unlock(&ses->chan_lock); return 0; + } + cifs_dbg(FYI, "sess reconnect mask: 0x%lx, tcon reconnect: %d", + tcon->ses->chans_need_reconnect, + tcon->need_reconnect); + spin_unlock(&ses->chan_lock); nls_codepage = load_nls_default(); /* - * need to prevent multiple threads trying to simultaneously reconnect - * the same SMB session - */ - mutex_lock(&tcon->ses->session_mutex); - - /* * Recheck after acquire mutex. If another thread is negotiating * and the server never sends an answer the socket will be closed * and tcpStatus set to reconnect. */ + spin_lock(&cifs_tcp_ses_lock); if (server->tcpStatus == CifsNeedReconnect) { + spin_unlock(&cifs_tcp_ses_lock); rc = -EHOSTDOWN; - mutex_unlock(&tcon->ses->session_mutex); goto out; } + spin_unlock(&cifs_tcp_ses_lock); /* - * If we are reconnecting an extra channel, bind + * need to prevent multiple threads trying to simultaneously + * reconnect the same SMB session */ - if (CIFS_SERVER_IS_CHAN(server)) { - ses->binding = true; - ses->binding_chan = cifs_ses_find_chan(ses, server); + spin_lock(&ses->chan_lock); + if (!cifs_chan_needs_reconnect(ses, server)) { + spin_unlock(&ses->chan_lock); + + /* this means that we only need to tree connect */ + if (tcon->need_reconnect) + goto skip_sess_setup; + + goto out; } + spin_unlock(&ses->chan_lock); - rc = cifs_negotiate_protocol(0, tcon->ses); - if (!rc && tcon->ses->need_reconnect) { - rc = cifs_setup_session(0, tcon->ses, nls_codepage); + mutex_lock(&ses->session_mutex); + rc = cifs_negotiate_protocol(0, ses, server); + if (!rc) { + rc = cifs_setup_session(0, ses, server, nls_codepage); if ((rc == -EACCES) && !tcon->retry) { + mutex_unlock(&ses->session_mutex); rc = -EHOSTDOWN; - ses->binding = false; - ses->binding_chan = NULL; - mutex_unlock(&tcon->ses->session_mutex); goto failed; } } - /* - * End of channel binding - */ - ses->binding = false; - ses->binding_chan = NULL; if (rc || !tcon->need_reconnect) { - mutex_unlock(&tcon->ses->session_mutex); + mutex_unlock(&ses->session_mutex); goto out; } +skip_sess_setup: cifs_mark_open_files_invalid(tcon); if (tcon->use_persistent) tcon->need_reopen_files = true; rc = cifs_tree_connect(0, tcon, nls_codepage); - mutex_unlock(&tcon->ses->session_mutex); + mutex_unlock(&ses->session_mutex); cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); if (rc) { @@ -833,7 +846,9 @@ add_posix_context(struct kvec *iov, unsigned int *num_iovec, umode_t mode) */ int -SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses) +SMB2_negotiate(const unsigned int xid, + struct cifs_ses *ses, + struct TCP_Server_Info *server) { struct smb_rqst rqst; struct smb2_negotiate_req *req; @@ -842,7 +857,6 @@ SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses) struct kvec rsp_iov; int rc = 0; int resp_buftype; - struct TCP_Server_Info *server = cifs_ses_server(ses); int blob_offset, blob_length; char *security_blob; int flags = CIFS_NEG_OP; @@ -1221,6 +1235,7 @@ smb2_select_sectype(struct TCP_Server_Info *server, enum securityEnum requested) struct SMB2_sess_data { unsigned int xid; struct cifs_ses *ses; + struct TCP_Server_Info *server; struct nls_table *nls_cp; void (*func)(struct SMB2_sess_data *); int result; @@ -1242,9 +1257,10 @@ SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data) { int rc; struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; struct smb2_sess_setup_req *req; - struct TCP_Server_Info *server = cifs_ses_server(ses); unsigned int total_len; + bool is_binding = false; rc = smb2_plain_req_init(SMB2_SESSION_SETUP, NULL, server, (void **) &req, @@ -1252,11 +1268,16 @@ SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data) if (rc) return rc; - if (sess_data->ses->binding) { - req->hdr.SessionId = cpu_to_le64(sess_data->ses->Suid); + spin_lock(&ses->chan_lock); + is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses); + spin_unlock(&ses->chan_lock); + + if (is_binding) { + req->hdr.SessionId = cpu_to_le64(ses->Suid); req->hdr.Flags |= SMB2_FLAGS_SIGNED; req->PreviousSessionId = 0; req->Flags = SMB2_SESSION_REQ_FLAG_BINDING; + cifs_dbg(FYI, "Binding to sess id: %llx\n", ses->Suid); } else { /* First session, not a reauthenticate */ req->hdr.SessionId = 0; @@ -1266,6 +1287,8 @@ SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data) */ req->PreviousSessionId = cpu_to_le64(sess_data->previous_session); req->Flags = 0; /* MBZ */ + cifs_dbg(FYI, "Fresh session. Previous: %llx\n", + sess_data->previous_session); } /* enough to enable echos and oplocks and one max size write */ @@ -1325,7 +1348,7 @@ SMB2_sess_sendreceive(struct SMB2_sess_data *sess_data) /* BB add code to build os and lm fields */ rc = cifs_send_recv(sess_data->xid, sess_data->ses, - cifs_ses_server(sess_data->ses), + sess_data->server, &rqst, &sess_data->buf0_type, CIFS_LOG_ERROR | CIFS_SESS_OP, &rsp_iov); @@ -1340,11 +1363,11 @@ SMB2_sess_establish_session(struct SMB2_sess_data *sess_data) { int rc = 0; struct cifs_ses *ses = sess_data->ses; - struct TCP_Server_Info *server = cifs_ses_server(ses); + struct TCP_Server_Info *server = sess_data->server; mutex_lock(&server->srv_mutex); if (server->ops->generate_signingkey) { - rc = server->ops->generate_signingkey(ses); + rc = server->ops->generate_signingkey(ses, server); if (rc) { cifs_dbg(FYI, "SMB3 session key generation failed\n"); @@ -1359,13 +1382,16 @@ SMB2_sess_establish_session(struct SMB2_sess_data *sess_data) mutex_unlock(&server->srv_mutex); cifs_dbg(FYI, "SMB2/3 session established successfully\n"); - /* keep existing ses state if binding */ - if (!ses->binding) { - spin_lock(&GlobalMid_Lock); - ses->status = CifsGood; - ses->need_reconnect = false; - spin_unlock(&GlobalMid_Lock); - } + + spin_lock(&ses->chan_lock); + cifs_chan_clear_need_reconnect(ses, server); + spin_unlock(&ses->chan_lock); + + /* Even if one channel is active, session is in good state */ + spin_lock(&cifs_tcp_ses_lock); + server->tcpStatus = CifsGood; + ses->status = CifsGood; + spin_unlock(&cifs_tcp_ses_lock); return rc; } @@ -1376,15 +1402,17 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) { int rc; struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; struct cifs_spnego_msg *msg; struct key *spnego_key = NULL; struct smb2_sess_setup_rsp *rsp = NULL; + bool is_binding = false; rc = SMB2_sess_alloc_buffer(sess_data); if (rc) goto out; - spnego_key = cifs_get_spnego_key(ses); + spnego_key = cifs_get_spnego_key(ses, server); if (IS_ERR(spnego_key)) { rc = PTR_ERR(spnego_key); if (rc == -ENOKEY) @@ -1405,8 +1433,12 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) goto out_put_spnego_key; } + spin_lock(&ses->chan_lock); + is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses); + spin_unlock(&ses->chan_lock); + /* keep session key if binding */ - if (!ses->binding) { + if (!is_binding) { ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len, GFP_KERNEL); if (!ses->auth_key.response) { @@ -1427,7 +1459,7 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base; /* keep session id and flags if binding */ - if (!ses->binding) { + if (!is_binding) { ses->Suid = le64_to_cpu(rsp->hdr.SessionId); ses->session_flags = le16_to_cpu(rsp->SessionFlags); } @@ -1459,10 +1491,12 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data) { int rc; struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; struct smb2_sess_setup_rsp *rsp = NULL; unsigned char *ntlmssp_blob = NULL; bool use_spnego = false; /* else use raw ntlmssp */ u16 blob_length = 0; + bool is_binding = false; /* * If memory allocation is successful, caller of this function @@ -1480,7 +1514,7 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data) goto out_err; rc = build_ntlmssp_negotiate_blob(&ntlmssp_blob, - &blob_length, ses, + &blob_length, ses, server, sess_data->nls_cp); if (rc) goto out_err; @@ -1519,8 +1553,12 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data) cifs_dbg(FYI, "rawntlmssp session setup challenge phase\n"); + spin_lock(&ses->chan_lock); + is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses); + spin_unlock(&ses->chan_lock); + /* keep existing ses id and flags if binding */ - if (!ses->binding) { + if (!is_binding) { ses->Suid = le64_to_cpu(rsp->hdr.SessionId); ses->session_flags = le16_to_cpu(rsp->SessionFlags); } @@ -1545,11 +1583,13 @@ SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data) { int rc; struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; 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; + bool is_binding = false; rc = SMB2_sess_alloc_buffer(sess_data); if (rc) @@ -1558,8 +1598,9 @@ SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data) req = (struct smb2_sess_setup_req *) sess_data->iov[0].iov_base; req->hdr.SessionId = cpu_to_le64(ses->Suid); - rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length, ses, - sess_data->nls_cp); + rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length, + ses, server, + sess_data->nls_cp); if (rc) { cifs_dbg(FYI, "build_ntlmssp_auth_blob failed %d\n", rc); goto out; @@ -1580,8 +1621,12 @@ SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data) rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base; + spin_lock(&ses->chan_lock); + is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses); + spin_unlock(&ses->chan_lock); + /* keep existing ses id and flags if binding */ - if (!ses->binding) { + if (!is_binding) { ses->Suid = le64_to_cpu(rsp->hdr.SessionId); ses->session_flags = le16_to_cpu(rsp->SessionFlags); } @@ -1612,11 +1657,13 @@ out: } static int -SMB2_select_sec(struct cifs_ses *ses, struct SMB2_sess_data *sess_data) +SMB2_select_sec(struct SMB2_sess_data *sess_data) { int type; + struct cifs_ses *ses = sess_data->ses; + struct TCP_Server_Info *server = sess_data->server; - type = smb2_select_sectype(cifs_ses_server(ses), ses->sectype); + type = smb2_select_sectype(server, ses->sectype); cifs_dbg(FYI, "sess setup type %d\n", type); if (type == Unspecified) { cifs_dbg(VFS, "Unable to select appropriate authentication method!\n"); @@ -1640,10 +1687,10 @@ SMB2_select_sec(struct cifs_ses *ses, struct SMB2_sess_data *sess_data) int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, + struct TCP_Server_Info *server, const struct nls_table *nls_cp) { int rc = 0; - struct TCP_Server_Info *server = cifs_ses_server(ses); struct SMB2_sess_data *sess_data; cifs_dbg(FYI, "Session Setup\n"); @@ -1657,15 +1704,17 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, 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->server = server; sess_data->buf0_type = CIFS_NO_BUFFER; sess_data->nls_cp = (struct nls_table *) nls_cp; sess_data->previous_session = ses->Suid; + rc = SMB2_select_sec(sess_data); + if (rc) + goto out; + /* * Initialize the session hash with the server one. */ @@ -1704,8 +1753,12 @@ SMB2_logoff(const unsigned int xid, struct cifs_ses *ses) return -EIO; /* no need to send SMB logoff if uid already closed due to reconnect */ - if (ses->need_reconnect) + spin_lock(&ses->chan_lock); + if (CIFS_ALL_CHANS_NEED_RECONNECT(ses)) { + spin_unlock(&ses->chan_lock); goto smb2_session_already_dead; + } + spin_unlock(&ses->chan_lock); rc = smb2_plain_req_init(SMB2_LOGOFF, NULL, ses->server, (void **) &req, &total_len); @@ -1867,7 +1920,9 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree, tcon->share_flags = le32_to_cpu(rsp->ShareFlags); tcon->capabilities = rsp->Capabilities; /* we keep caps little endian */ tcon->maximal_access = le32_to_cpu(rsp->MaximalAccess); + spin_lock(&cifs_tcp_ses_lock); tcon->tidStatus = CifsGood; + spin_unlock(&cifs_tcp_ses_lock); tcon->need_reconnect = false; tcon->tid = le32_to_cpu(rsp->hdr.Id.SyncId.TreeId); strlcpy(tcon->treeName, tree, sizeof(tcon->treeName)); @@ -1913,8 +1968,13 @@ SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon) if (!ses || !(ses->server)) return -EIO; - if ((tcon->need_reconnect) || (tcon->ses->need_reconnect)) + spin_lock(&ses->chan_lock); + if ((tcon->need_reconnect) || + (CIFS_ALL_CHANS_NEED_RECONNECT(tcon->ses))) { + spin_unlock(&ses->chan_lock); return 0; + } + spin_unlock(&ses->chan_lock); close_cached_dir_lease(&tcon->crfid); @@ -3797,13 +3857,16 @@ SMB2_echo(struct TCP_Server_Info *server) .rq_nvec = 1 }; unsigned int total_len; - cifs_dbg(FYI, "In echo request\n"); + cifs_dbg(FYI, "In echo request for conn_id %lld\n", server->conn_id); + spin_lock(&cifs_tcp_ses_lock); if (server->tcpStatus == CifsNeedNegotiate) { + spin_unlock(&cifs_tcp_ses_lock); /* No need to send echo on newly established connections */ mod_delayed_work(cifsiod_wq, &server->reconnect, 0); return rc; } + spin_unlock(&cifs_tcp_ses_lock); rc = smb2_plain_req_init(SMB2_ECHO, NULL, server, (void **)&req, &total_len); diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h index 096fada16ebd..4a7062fd1c26 100644 --- a/fs/cifs/smb2proto.h +++ b/fs/cifs/smb2proto.h @@ -123,8 +123,11 @@ extern void smb2_set_related(struct smb_rqst *rqst); * SMB2 Worker functions - most of protocol specific implementation details * are contained within these calls. */ -extern int SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses); +extern int SMB2_negotiate(const unsigned int xid, + struct cifs_ses *ses, + struct TCP_Server_Info *server); extern int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, + struct TCP_Server_Info *server, const struct nls_table *nls_cp); extern int SMB2_logoff(const unsigned int xid, struct cifs_ses *ses); extern int SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, @@ -276,6 +279,7 @@ extern void smb2_copy_fs_info_to_kstatfs( struct kstatfs *kst); extern int smb311_crypto_shash_allocate(struct TCP_Server_Info *server); extern int smb311_update_preauth_hash(struct cifs_ses *ses, + struct TCP_Server_Info *server, struct kvec *iov, int nvec); extern int smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, diff --git a/fs/cifs/smb2transport.c b/fs/cifs/smb2transport.c index 2bf047b390a9..b70a49b4edc0 100644 --- a/fs/cifs/smb2transport.c +++ b/fs/cifs/smb2transport.c @@ -100,7 +100,8 @@ int smb2_get_sign_key(__u64 ses_id, struct TCP_Server_Info *server, u8 *key) goto out; found: - if (ses->binding) { + if (cifs_chan_needs_reconnect(ses, server) && + !CIFS_ALL_CHANS_NEED_RECONNECT(ses)) { /* * If we are in the process of binding a new channel * to an existing session, use the master connection @@ -390,12 +391,18 @@ struct derivation_triplet { static int generate_smb3signingkey(struct cifs_ses *ses, + struct TCP_Server_Info *server, const struct derivation_triplet *ptriplet) { int rc; -#ifdef CONFIG_CIFS_DEBUG_DUMP_KEYS - struct TCP_Server_Info *server = ses->server; -#endif + bool is_binding = false; + int chan_index = 0; + + spin_lock(&ses->chan_lock); + is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses); + chan_index = cifs_ses_get_chan_index(ses, server); + /* TODO: introduce ref counting for channels when the can be freed */ + spin_unlock(&ses->chan_lock); /* * All channels use the same encryption/decryption keys but @@ -407,10 +414,10 @@ generate_smb3signingkey(struct cifs_ses *ses, * master connection signing key stored in the session */ - if (ses->binding) { + if (is_binding) { rc = generate_key(ses, ptriplet->signing.label, ptriplet->signing.context, - cifs_ses_binding_channel(ses)->signkey, + ses->chans[chan_index].signkey, SMB3_SIGN_KEY_SIZE); if (rc) return rc; @@ -422,6 +429,7 @@ generate_smb3signingkey(struct cifs_ses *ses, if (rc) return rc; + /* safe to access primary channel, since it will never go away */ memcpy(ses->chans[0].signkey, ses->smb3signingkey, SMB3_SIGN_KEY_SIZE); @@ -470,7 +478,8 @@ generate_smb3signingkey(struct cifs_ses *ses, } int -generate_smb30signingkey(struct cifs_ses *ses) +generate_smb30signingkey(struct cifs_ses *ses, + struct TCP_Server_Info *server) { struct derivation_triplet triplet; @@ -494,11 +503,12 @@ generate_smb30signingkey(struct cifs_ses *ses) d->context.iov_base = "ServerOut"; d->context.iov_len = 10; - return generate_smb3signingkey(ses, &triplet); + return generate_smb3signingkey(ses, server, &triplet); } int -generate_smb311signingkey(struct cifs_ses *ses) +generate_smb311signingkey(struct cifs_ses *ses, + struct TCP_Server_Info *server) { struct derivation_triplet triplet; @@ -522,7 +532,7 @@ generate_smb311signingkey(struct cifs_ses *ses) d->context.iov_base = ses->preauth_sha_hash; d->context.iov_len = 64; - return generate_smb3signingkey(ses, &triplet); + return generate_smb3signingkey(ses, server, &triplet); } int @@ -624,8 +634,12 @@ smb2_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server) if (!is_signed) return 0; - if (server->tcpStatus == CifsNeedNegotiate) + spin_lock(&cifs_tcp_ses_lock); + if (server->tcpStatus == CifsNeedNegotiate) { + spin_unlock(&cifs_tcp_ses_lock); return 0; + } + spin_unlock(&cifs_tcp_ses_lock); if (!is_binding && !server->session_estab) { strncpy(shdr->Signature, "BSRSPYL", 8); return 0; @@ -741,30 +755,41 @@ static int smb2_get_mid_entry(struct cifs_ses *ses, struct TCP_Server_Info *server, struct smb2_hdr *shdr, struct mid_q_entry **mid) { - if (server->tcpStatus == CifsExiting) + spin_lock(&cifs_tcp_ses_lock); + if (server->tcpStatus == CifsExiting) { + spin_unlock(&cifs_tcp_ses_lock); return -ENOENT; + } if (server->tcpStatus == CifsNeedReconnect) { + spin_unlock(&cifs_tcp_ses_lock); cifs_dbg(FYI, "tcp session dead - return to caller to retry\n"); return -EAGAIN; } if (server->tcpStatus == CifsNeedNegotiate && - shdr->Command != SMB2_NEGOTIATE) + shdr->Command != SMB2_NEGOTIATE) { + spin_unlock(&cifs_tcp_ses_lock); return -EAGAIN; + } if (ses->status == CifsNew) { if ((shdr->Command != SMB2_SESSION_SETUP) && - (shdr->Command != SMB2_NEGOTIATE)) + (shdr->Command != SMB2_NEGOTIATE)) { + spin_unlock(&cifs_tcp_ses_lock); return -EAGAIN; + } /* else ok - we are setting up session */ } if (ses->status == CifsExiting) { - if (shdr->Command != SMB2_LOGOFF) + if (shdr->Command != SMB2_LOGOFF) { + spin_unlock(&cifs_tcp_ses_lock); return -EAGAIN; + } /* else ok - we are shutting down the session */ } + spin_unlock(&cifs_tcp_ses_lock); *mid = smb2_mid_entry_alloc(shdr, server); if (*mid == NULL) @@ -837,9 +862,13 @@ smb2_setup_async_request(struct TCP_Server_Info *server, struct smb_rqst *rqst) (struct smb2_hdr *)rqst->rq_iov[0].iov_base; struct mid_q_entry *mid; + spin_lock(&cifs_tcp_ses_lock); if (server->tcpStatus == CifsNeedNegotiate && - shdr->Command != SMB2_NEGOTIATE) + shdr->Command != SMB2_NEGOTIATE) { + spin_unlock(&cifs_tcp_ses_lock); return ERR_PTR(-EAGAIN); + } + spin_unlock(&cifs_tcp_ses_lock); smb2_seq_num_into_buf(server, shdr); diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c index 61ea3d3f95b4..93f0e8c1ea23 100644 --- a/fs/cifs/transport.c +++ b/fs/cifs/transport.c @@ -430,9 +430,9 @@ unmask: * be taken as the remainder of this one. We need to kill the * socket so the server throws away the partial SMB */ - spin_lock(&GlobalMid_Lock); + spin_lock(&cifs_tcp_ses_lock); server->tcpStatus = CifsNeedReconnect; - spin_unlock(&GlobalMid_Lock); + spin_unlock(&cifs_tcp_ses_lock); trace_smb3_partial_send_reconnect(server->CurrentMid, server->conn_id, server->hostname); } @@ -578,10 +578,14 @@ wait_for_free_credits(struct TCP_Server_Info *server, const int num_credits, return -ERESTARTSYS; spin_lock(&server->req_lock); } else { + spin_unlock(&server->req_lock); + + spin_lock(&cifs_tcp_ses_lock); if (server->tcpStatus == CifsExiting) { - spin_unlock(&server->req_lock); + spin_unlock(&cifs_tcp_ses_lock); return -ENOENT; } + spin_unlock(&cifs_tcp_ses_lock); /* * For normal commands, reserve the last MAX_COMPOUND @@ -596,6 +600,7 @@ wait_for_free_credits(struct TCP_Server_Info *server, const int num_credits, * for servers that are slow to hand out credits on * new sessions. */ + spin_lock(&server->req_lock); if (!optype && num_credits == 1 && server->in_flight > 2 * MAX_COMPOUND && *credits <= MAX_COMPOUND) { @@ -723,28 +728,36 @@ cifs_wait_mtu_credits(struct TCP_Server_Info *server, unsigned int size, static int allocate_mid(struct cifs_ses *ses, struct smb_hdr *in_buf, struct mid_q_entry **ppmidQ) { + spin_lock(&cifs_tcp_ses_lock); if (ses->server->tcpStatus == CifsExiting) { + spin_unlock(&cifs_tcp_ses_lock); return -ENOENT; } if (ses->server->tcpStatus == CifsNeedReconnect) { + spin_unlock(&cifs_tcp_ses_lock); cifs_dbg(FYI, "tcp session dead - return to caller to retry\n"); return -EAGAIN; } if (ses->status == CifsNew) { if ((in_buf->Command != SMB_COM_SESSION_SETUP_ANDX) && - (in_buf->Command != SMB_COM_NEGOTIATE)) + (in_buf->Command != SMB_COM_NEGOTIATE)) { + spin_unlock(&cifs_tcp_ses_lock); return -EAGAIN; + } /* else ok - we are setting up session */ } if (ses->status == CifsExiting) { /* check if SMB session is bad because we are setting it up */ - if (in_buf->Command != SMB_COM_LOGOFF_ANDX) + if (in_buf->Command != SMB_COM_LOGOFF_ANDX) { + spin_unlock(&cifs_tcp_ses_lock); return -EAGAIN; + } /* else ok - we are shutting down session */ } + spin_unlock(&cifs_tcp_ses_lock); *ppmidQ = AllocMidQEntry(in_buf, ses->server); if (*ppmidQ == NULL) @@ -1044,19 +1057,11 @@ struct TCP_Server_Info *cifs_pick_channel(struct cifs_ses *ses) if (!ses) return NULL; - spin_lock(&ses->chan_lock); - if (!ses->binding) { - /* round robin */ - if (ses->chan_count > 1) { - index = (uint)atomic_inc_return(&ses->chan_seq); - index %= ses->chan_count; - } - spin_unlock(&ses->chan_lock); - return ses->chans[index].server; - } else { - spin_unlock(&ses->chan_lock); - return cifs_ses_server(ses); - } + /* round robin */ + index = (uint)atomic_inc_return(&ses->chan_seq); + index %= ses->chan_count; + + return ses->chans[index].server; } int @@ -1084,8 +1089,12 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, return -EIO; } - if (server->tcpStatus == CifsExiting) + spin_lock(&cifs_tcp_ses_lock); + if (server->tcpStatus == CifsExiting) { + spin_unlock(&cifs_tcp_ses_lock); return -ENOENT; + } + spin_unlock(&cifs_tcp_ses_lock); /* * Wait for all the requests to become available. @@ -1188,12 +1197,17 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, /* * Compounding is never used during session establish. */ + spin_lock(&cifs_tcp_ses_lock); if ((ses->status == CifsNew) || (optype & CIFS_NEG_OP) || (optype & CIFS_SESS_OP)) { + spin_unlock(&cifs_tcp_ses_lock); + mutex_lock(&server->srv_mutex); - smb311_update_preauth_hash(ses, rqst[0].rq_iov, - rqst[0].rq_nvec); + smb311_update_preauth_hash(ses, server, rqst[0].rq_iov, rqst[0].rq_nvec); mutex_unlock(&server->srv_mutex); + + spin_lock(&cifs_tcp_ses_lock); } + spin_unlock(&cifs_tcp_ses_lock); for (i = 0; i < num_rqst; i++) { rc = wait_for_response(server, midQ[i]); @@ -1256,15 +1270,19 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, /* * Compounding is never used during session establish. */ + spin_lock(&cifs_tcp_ses_lock); if ((ses->status == CifsNew) || (optype & CIFS_NEG_OP) || (optype & CIFS_SESS_OP)) { struct kvec iov = { .iov_base = resp_iov[0].iov_base, .iov_len = resp_iov[0].iov_len }; + spin_unlock(&cifs_tcp_ses_lock); mutex_lock(&server->srv_mutex); - smb311_update_preauth_hash(ses, &iov, 1); + smb311_update_preauth_hash(ses, server, &iov, 1); mutex_unlock(&server->srv_mutex); + spin_lock(&cifs_tcp_ses_lock); } + spin_unlock(&cifs_tcp_ses_lock); out: /* @@ -1353,8 +1371,12 @@ SendReceive(const unsigned int xid, struct cifs_ses *ses, return -EIO; } - if (server->tcpStatus == CifsExiting) + spin_lock(&cifs_tcp_ses_lock); + if (server->tcpStatus == CifsExiting) { + spin_unlock(&cifs_tcp_ses_lock); return -ENOENT; + } + spin_unlock(&cifs_tcp_ses_lock); /* Ensure that we do not send more than 50 overlapping requests to the same server. We may make this configurable later or @@ -1494,8 +1516,12 @@ SendReceiveBlockingLock(const unsigned int xid, struct cifs_tcon *tcon, return -EIO; } - if (server->tcpStatus == CifsExiting) + spin_lock(&cifs_tcp_ses_lock); + if (server->tcpStatus == CifsExiting) { + spin_unlock(&cifs_tcp_ses_lock); return -ENOENT; + } + spin_unlock(&cifs_tcp_ses_lock); /* Ensure that we do not send more than 50 overlapping requests to the same server. We may make this configurable later or @@ -1553,10 +1579,12 @@ SendReceiveBlockingLock(const unsigned int xid, struct cifs_tcon *tcon, (server->tcpStatus != CifsNew))); /* Were we interrupted by a signal ? */ + spin_lock(&cifs_tcp_ses_lock); if ((rc == -ERESTARTSYS) && (midQ->mid_state == MID_REQUEST_SUBMITTED) && ((server->tcpStatus == CifsGood) || (server->tcpStatus == CifsNew))) { + spin_unlock(&cifs_tcp_ses_lock); if (in_buf->Command == SMB_COM_TRANSACTION2) { /* POSIX lock. We send a NT_CANCEL SMB to cause the @@ -1595,7 +1623,9 @@ SendReceiveBlockingLock(const unsigned int xid, struct cifs_tcon *tcon, /* We got the response - restart system call. */ rstart = 1; + spin_lock(&cifs_tcp_ses_lock); } + spin_unlock(&cifs_tcp_ses_lock); rc = cifs_sync_mid_result(midQ, server); if (rc != 0) diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index 8ab81ea13424..dae5df88ab10 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -52,6 +52,7 @@ #define QNX6_SUPER_MAGIC 0x68191122 /* qnx6 fs detection */ #define AFS_FS_MAGIC 0x6B414653 + #define REISERFS_SUPER_MAGIC 0x52654973 /* used by gcc */ /* used by file system utilities that look at the superblock, etc. */ @@ -60,6 +61,9 @@ #define REISER2FS_JR_SUPER_MAGIC_STRING "ReIsEr3Fs" #define SMB_SUPER_MAGIC 0x517B +#define CIFS_SUPER_MAGIC 0xFF534D42 /* the first four bytes of SMB PDUs */ +#define SMB2_SUPER_MAGIC 0xFE534D42 + #define CGROUP_SUPER_MAGIC 0x27e0eb #define CGROUP2_SUPER_MAGIC 0x63677270 |