bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface)
{ int i;
spin_lock(&ses->chan_lock); for (i = 0; i < ses->chan_count; i++) { if (ses->chans[i].iface == iface) {
spin_unlock(&ses->chan_lock); returntrue;
}
}
spin_unlock(&ses->chan_lock); returnfalse;
}
/* channel helper functions. assumed that chan_lock is held by caller. */
int
cifs_ses_get_chan_index(struct cifs_ses *ses, struct TCP_Server_Info *server)
{ unsignedint i;
/* if the channel is waiting for termination */ if (server && server->terminate) return CIFS_INVAL_CHAN_INDEX;
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 */ if (server)
cifs_dbg(VFS, "unable to get chan index for server: 0x%llx",
server->conn_id); return CIFS_INVAL_CHAN_INDEX;
}
/* returns number of channels added */ int cifs_try_adding_channels(struct cifs_ses *ses)
{ struct TCP_Server_Info *server = ses->server; int old_chan_count, new_chan_count; int left; int rc = 0; int tries = 0;
size_t iface_weight = 0, iface_min_speed = 0; struct cifs_server_iface *iface = NULL, *niface = NULL; struct cifs_server_iface *last_iface = NULL;
spin_lock(&ses->chan_lock);
new_chan_count = old_chan_count = ses->chan_count;
left = ses->chan_max - ses->chan_count;
if (left <= 0) {
spin_unlock(&ses->chan_lock);
cifs_dbg(FYI, "ses already at max_channels (%zu), nothing to open\n",
ses->chan_max); return 0;
}
if (server->dialect < SMB30_PROT_ID) {
spin_unlock(&ses->chan_lock);
cifs_dbg(VFS, "multichannel is not supported on this protocol version, use 3.0 or above\n"); return 0;
}
if (!(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
spin_unlock(&ses->chan_lock);
cifs_server_dbg(VFS, "no multichannel support\n"); return 0;
}
spin_unlock(&ses->chan_lock);
while (left > 0) {
tries++; if (tries > 3*ses->chan_max) {
cifs_dbg(VFS, "too many channel open attempts (%d channels left to open)\n",
left); break;
}
spin_lock(&ses->iface_lock); if (!ses->iface_count) {
spin_unlock(&ses->iface_lock);
cifs_dbg(ONCE, "server %s does not advertise interfaces\n",
ses->server->hostname); break;
}
list_for_each_entry_safe_from(iface, niface, &ses->iface_list,
iface_head) { /* do not mix rdma and non-rdma interfaces */ if (iface->rdma_capable != ses->server->rdma) continue;
/* skip ifaces that are unusable */ if (!iface->is_active ||
(is_ses_using_iface(ses, iface) &&
!iface->rss_capable)) continue;
/* check if we already allocated enough channels */
iface_weight = iface->speed / iface_min_speed;
if (iface->weight_fulfilled >= iface_weight) continue;
/* take ref before unlock */
kref_get(&iface->refcount);
if (rc) {
cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n",
&iface->sockaddr,
rc);
kref_put(&iface->refcount, release_iface); /* failure to add chan should increase weight */
iface->weight_fulfilled++; continue;
}
iface->num_channels++;
iface->weight_fulfilled++;
cifs_info("successfully opened new channel on iface:%pIS\n",
&iface->sockaddr); break;
}
/* reached end of list. reset weight_fulfilled and start over */ if (list_entry_is_head(iface, &ses->iface_list, iface_head)) {
list_for_each_entry(iface, &ses->iface_list, iface_head)
iface->weight_fulfilled = 0;
spin_unlock(&ses->iface_lock);
iface = NULL; continue;
}
spin_unlock(&ses->iface_lock);
left--;
new_chan_count++;
}
return new_chan_count - old_chan_count;
}
/* * called when multichannel is disabled by the server. * this always gets called from smb2_reconnect * and cannot get called in parallel threads.
*/ void
cifs_disable_secondary_channels(struct cifs_ses *ses)
{ int i, chan_count; struct TCP_Server_Info *server; struct cifs_server_iface *iface;
spin_lock(&ses->chan_lock);
chan_count = ses->chan_count; if (chan_count == 1) goto done;
ses->chan_count = 1;
/* for all secondary channels reset the need reconnect bit */
ses->chans_need_reconnect &= 1;
for (i = 1; i < chan_count; i++) {
iface = ses->chans[i].iface;
server = ses->chans[i].server;
/* * remove these references first, since we need to unlock * the chan_lock here, since iface_lock is a higher lock
*/
ses->chans[i].iface = NULL;
ses->chans[i].server = NULL;
spin_unlock(&ses->chan_lock);
if (iface) {
spin_lock(&ses->iface_lock);
iface->num_channels--; if (iface->weight_fulfilled)
iface->weight_fulfilled--;
kref_put(&iface->refcount, release_iface);
spin_unlock(&ses->iface_lock);
}
if (server) { if (!server->terminate) {
server->terminate = true;
cifs_signal_cifsd_for_reconnect(server, false);
}
cifs_put_tcp_session(server, false);
}
spin_lock(&ses->chan_lock);
}
done:
spin_unlock(&ses->chan_lock);
}
/* update the iface for the channel if necessary. */ void
cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server)
{ unsignedint chan_index;
size_t iface_weight = 0, iface_min_speed = 0; struct cifs_server_iface *iface = NULL; struct cifs_server_iface *old_iface = NULL; struct cifs_server_iface *last_iface = NULL; struct sockaddr_storage ss; int retry = 0;
if (ses->chans[chan_index].iface) {
old_iface = ses->chans[chan_index].iface; if (old_iface->is_active) {
spin_unlock(&ses->chan_lock); return;
}
}
spin_unlock(&ses->chan_lock);
spin_lock(&server->srv_lock);
ss = server->dstaddr;
spin_unlock(&server->srv_lock);
spin_lock(&ses->iface_lock); if (!ses->iface_count) {
spin_unlock(&ses->iface_lock);
cifs_dbg(ONCE, "server %s does not advertise interfaces\n", ses->server->hostname); return;
}
/* then look for a new one */
list_for_each_entry(iface, &ses->iface_list, iface_head) { if (!chan_index) { /* if we're trying to get the updated iface for primary channel */ if (!cifs_match_ipaddr((struct sockaddr *) &ss,
(struct sockaddr *) &iface->sockaddr)) continue;
kref_get(&iface->refcount); break;
}
/* do not mix rdma and non-rdma interfaces */ if (iface->rdma_capable != server->rdma) continue;
if (!iface->is_active ||
(is_ses_using_iface(ses, iface) &&
!iface->rss_capable)) { continue;
}
/* check if we already allocated enough channels */
iface_weight = iface->speed / iface_min_speed;
if (iface->weight_fulfilled >= iface_weight) continue;
/* see if it can be satisfied in second attempt */ if (!retry++) goto try_again;
iface = NULL;
cifs_dbg(FYI, "unable to find a suitable iface\n");
}
if (!iface) { if (!chan_index)
cifs_dbg(FYI, "unable to get the interface matching: %pIS\n",
&ss); else {
cifs_dbg(FYI, "unable to find another interface to replace: %pIS\n",
&old_iface->sockaddr);
}
spin_unlock(&ses->iface_lock); return;
}
/* now drop the ref to the current iface */ if (old_iface) {
cifs_dbg(FYI, "replacing iface: %pIS with %pIS\n",
&old_iface->sockaddr,
&iface->sockaddr);
old_iface->num_channels--; if (old_iface->weight_fulfilled)
old_iface->weight_fulfilled--;
iface->num_channels++;
iface->weight_fulfilled++;
if (iface->sockaddr.ss_family == AF_INET)
cifs_dbg(FYI, "adding channel to ses %p (speed:%zu bps rdma:%s ip:%pI4)\n",
ses, iface->speed, str_yes_no(iface->rdma_capable),
&ipv4->sin_addr); else
cifs_dbg(FYI, "adding channel to ses %p (speed:%zu bps rdma:%s ip:%pI6)\n",
ses, iface->speed, str_yes_no(iface->rdma_capable),
&ipv6->sin6_addr);
/* * Setup a ctx with mostly the same info as the existing * session and overwrite it with the requested iface data. * * We need to setup at least the fields used for negprot and * sesssetup. * * We only need the ctx here, so we can reuse memory from * the session and server without caring about memory * management.
*/
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) {
rc = -ENOMEM; goto out_free_xid;
}
/* Always make new connection for now (TODO?) */
ctx->nosharesock = true;
/* 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 * (we sign with the session key)
*/
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;
}
out: if (rc && chan->server) {
cifs_put_tcp_session(chan->server, 0);
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);
}
/* init fields common to all four types of SessSetup */ /* Note that offsets for first seven fields in req struct are same */ /* in CIFS Specs so does not matter which of 3 forms of struct */ /* that we use in next few lines */ /* Note that header is initialized to zero in header_assemble */
pSMB->req.AndXCommand = 0xFF;
pSMB->req.MaxBufferSize = cpu_to_le16(min_t(u32,
CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4,
USHRT_MAX));
pSMB->req.MaxMpxCount = cpu_to_le16(server->maxReq);
pSMB->req.VcNumber = cpu_to_le16(1);
pSMB->req.SessionKey = server->session_key_id;
/* Now no need to set SMBFLG_CASELESS or obsolete CANONICAL PATH */
/* BB verify whether signing required on neg or just auth frame (and NTLM case) */
/* copy domain */ if (ses->domainName == NULL) { /* * Sending null domain better than using a bogus domain name (as * we did briefly in 2.6.18) since server will use its default
*/
*bcc_ptr = 0;
*(bcc_ptr+1) = 0;
bytes_ret = 0;
} else
bytes_ret = cifs_strtoUTF16((__le16 *) bcc_ptr, ses->domainName,
CIFS_MAX_DOMAINNAME_LEN, nls_cp);
bcc_ptr += 2 * bytes_ret;
bcc_ptr += 2; /* account for null terminator */
/* copy domain */ if (ses->domainName != NULL) {
len = strscpy(bcc_ptr, ses->domainName, CIFS_MAX_DOMAINNAME_LEN); if (WARN_ON_ONCE(len < 0))
len = CIFS_MAX_DOMAINNAME_LEN - 1;
bcc_ptr += len;
} /* else we send a null domain name so server will default to its own domain */
*bcc_ptr = 0;
bcc_ptr++;
/* copy user */ /* BB what about null user mounts - check that we do this BB */ /* copy user */ if (ses->user_name != NULL) {
len = strscpy(bcc_ptr, ses->user_name, CIFS_MAX_USERNAME_LEN); if (WARN_ON_ONCE(len < 0))
len = CIFS_MAX_USERNAME_LEN - 1;
bcc_ptr += len;
} /* else null user mount */
*bcc_ptr = 0;
bcc_ptr++; /* account for null termination */
len = strnlen(bcc_ptr, bleft); if (len > bleft) return;
/* * No domain field in LANMAN case. Domain is * returned by old servers in the SMB negprot response * * BB For newer servers which do not support Unicode, * but thus do return domain here, we could add parsing * for it later, but it is not very important
*/
cifs_dbg(FYI, "ascii: bytes left %d\n", bleft);
} #endif/* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
int decode_ntlmssp_challenge(char *bcc_ptr, int blob_len, struct cifs_ses *ses)
{ unsignedint tioffset; /* challenge message target info area */ unsignedint tilen; /* challenge message target info area length */
CHALLENGE_MESSAGE *pblob = (CHALLENGE_MESSAGE *)bcc_ptr;
__u32 server_flags;
if (blob_len < sizeof(CHALLENGE_MESSAGE)) {
cifs_dbg(VFS, "challenge blob len %d too small\n", blob_len); return -EINVAL;
}
if (memcmp(pblob->Signature, "NTLMSSP", 8)) {
cifs_dbg(VFS, "blob signature incorrect %s\n",
pblob->Signature); return -EINVAL;
} if (pblob->MessageType != NtLmChallenge) {
cifs_dbg(VFS, "Incorrect message type %d\n",
pblob->MessageType); return -EINVAL;
}
if ((ses->ntlmssp->client_flags & (NTLMSSP_NEGOTIATE_SEAL | NTLMSSP_NEGOTIATE_SIGN)) &&
(!(server_flags & NTLMSSP_NEGOTIATE_56) && !(server_flags & NTLMSSP_NEGOTIATE_128))) {
cifs_dbg(VFS, "%s: requested signing/encryption but server did not return either 56-bit or 128-bit session key size\n",
__func__); return -EINVAL;
} if (!(server_flags & NTLMSSP_NEGOTIATE_NTLM) && !(server_flags & NTLMSSP_NEGOTIATE_EXTENDED_SEC)) {
cifs_dbg(VFS, "%s: server does not seem to support either NTLMv1 or NTLMv2\n", __func__); return -EINVAL;
} if (ses->server->sign && !(server_flags & NTLMSSP_NEGOTIATE_SIGN)) {
cifs_dbg(VFS, "%s: forced packet signing but server does not seem to support it\n",
__func__); return -EOPNOTSUPP;
} if ((ses->ntlmssp->client_flags & NTLMSSP_NEGOTIATE_KEY_XCH) &&
!(server_flags & NTLMSSP_NEGOTIATE_KEY_XCH))
pr_warn_once("%s: authentication has been weakened as server does not support key exchange\n",
__func__);
ses->ntlmssp->server_flags = server_flags;
memcpy(ses->ntlmssp->cryptkey, pblob->Challenge, CIFS_CRYPTO_KEY_SIZE); /* * In particular we can examine sign flags * * BB spec says that if AvId field of MsvAvTimestamp is populated then * we must set the MIC field of the AUTHENTICATE_MESSAGE
*/
tioffset = le32_to_cpu(pblob->TargetInfoArray.BufferOffset);
tilen = le16_to_cpu(pblob->TargetInfoArray.Length); if (tioffset > blob_len || tioffset + tilen > blob_len) {
cifs_dbg(VFS, "tioffset + tilen too high %u + %u\n",
tioffset, tilen); return -EINVAL;
} if (tilen) {
kfree_sensitive(ses->auth_key.response);
ses->auth_key.response = kmemdup(bcc_ptr + tioffset, tilen,
GFP_KERNEL); if (!ses->auth_key.response) {
cifs_dbg(VFS, "Challenge target info alloc failure\n"); return -ENOMEM;
}
ses->auth_key.len = tilen;
}
return 0;
}
staticint size_of_ntlmssp_blob(struct cifs_ses *ses, int base_size)
{ int sz = base_size + ses->auth_key.len
- CIFS_SESS_KEY_SIZE + CIFS_CPHTXT_SIZE + 2;
/* these fields should be null in negotiate phase MS-NLMP 3.1.5.1.1 */
cifs_security_buffer_from_str(&sec_blob->DomainName,
NULL,
CIFS_MAX_DOMAINNAME_LEN,
*pbuffer, &tmp,
nls_cp);
/* * Build ntlmssp blob with additional fields, such as version, * supported by modern servers. For safety limit to SMB3 or later * See notes in MS-NLMP Section 2.2.2.1 e.g.
*/ int build_ntlmssp_smb3_negotiate_blob(unsignedchar **pbuffer,
u16 *buflen, struct cifs_ses *ses, struct TCP_Server_Info *server, conststruct nls_table *nls_cp)
{ int rc = 0; struct negotiate_message *sec_blob;
__u32 flags; unsignedchar *tmp; int len;
/* these fields should be null in negotiate phase MS-NLMP 3.1.5.1.1 */
cifs_security_buffer_from_str(&sec_blob->DomainName,
NULL,
CIFS_MAX_DOMAINNAME_LEN,
*pbuffer, &tmp,
nls_cp);
/* send version information in ntlmssp authenticate also */
flags = ses->ntlmssp->server_flags | NTLMSSP_REQUEST_TARGET |
NTLMSSP_NEGOTIATE_TARGET_INFO | NTLMSSP_NEGOTIATE_VERSION |
NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED;
/* 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[3];
};
#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY staticint
sess_alloc_buffer(struct sess_data *sess_data, int wct)
{ int rc; struct cifs_ses *ses = sess_data->ses; struct smb_hdr *smb_buf;
sess_data->iov[0].iov_base = (char *)smb_buf;
sess_data->iov[0].iov_len = be32_to_cpu(smb_buf->smb_buf_length) + 4; /* * 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;
/* 2000 big enough to fit max user, domain, NOS name etc. */
sess_data->iov[2].iov_base = kmalloc(2000, GFP_KERNEL); if (!sess_data->iov[2].iov_base) {
rc = -ENOMEM; goto out_free_smb_buf;
}
/* * Zero the session data before freeing, as it might contain sensitive info (keys, etc). * Note that iov[1] is already freed by caller.
*/ if (sess_data->buf0_type != CIFS_NO_BUFFER && iov[0].iov_base)
memzero_explicit(iov[0].iov_base, iov[0].iov_len);
/* set case sensitive password length after tilen may get * assigned, tilen is 0 otherwise.
*/
pSMB->req_no_secext.CaseSensitivePasswordLength =
cpu_to_le16(ses->auth_key.len - CIFS_SESS_KEY_SIZE);
} else {
pSMB->req_no_secext.CaseSensitivePasswordLength = 0;
}
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, "incorrect version of cifs.upcall (expected %d but got %d)\n",
CIFS_SPNEGO_UPCALL_VERSION, msg->version);
rc = -EKEYREJECTED; goto out_put_spnego_key;
}
/* * 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;
}
ses->ntlmssp->sesskey_per_smbsess = false;
/* If true, rc here is expected and not an error */ if (sess_data->buf0_type != CIFS_NO_BUFFER &&
smb_buf->Status.CifsError ==
cpu_to_le32(NT_STATUS_MORE_PROCESSING_REQUIRED))
rc = 0;
/* Build security blob before we assemble the request */
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, server,
sess_data->nls_cp); if (rc) goto out_free_ntlmsspblob;
sess_data->iov[1].iov_len = blob_len;
sess_data->iov[1].iov_base = ntlmsspblob;
pSMB->req.SecurityBlobLength = cpu_to_le16(blob_len); /* * Make sure that we tell the server that we are using * the uid that it just gave us back on the response * (challenge)
*/
smb_buf->Uid = ses->Suid;
rc = _sess_auth_rawntlmssp_assemble_req(sess_data); if (rc) goto out_free_ntlmsspblob;
rc = sess_sendreceive(sess_data); if (rc) goto out_free_ntlmsspblob;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.